Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Reverse Decorator in VB.NET (WinForms) or Chain of Responsibility

0.00/5 (No votes)
18 Jul 2024 1  
How to use the decorator pattern in WinForms, VB.NET, with async/await and/or multi threading (update 8/10/20 - this is more of a Chain of Responsibility pattern than a Decorator)
In this post, I describe the architecture we will be building for Reverse Decorator. I write a new application, a multi-threaded one that will compose backwards and run forward.

Introduction

This article is a continuation of my last article, Decorator Pattern in VB.NET Winforms. I love using that pattern for all the reasons given in the article, but I ran up against a wall whenever I would try to do anything with multi-threading or async. The problem is how I implemented it. The way the classes are linked, the last class begins, then starts the previous and on up the line. Because the last class has started execution, it is impossible to move it to another thread and has trouble waiting for an async process. I tried a few different ways to get it to work. The best way I found is to flip the composition upside down. It makes it a bit less self documenting, but allows it to be used in multi-threading and async operations. I call it the Reverse Decorator.

The finished example application can be found on GitHub here.

Reverse Decorator

I will describe here the architecture we will be building. I'll write a new application, a multi-threaded one that will compose backwards and run forward. In contrast to my last article, it will work like this:

The Code

I had to convert an image into the base64 string to be used in an HTML email. I'll use that as an excuse to demonstrate how to build an application with the reverse decorator pattern. I'll assume that you are familiar with my last article on the decorator pattern.

The actions I'll be implementing are the following:

  1. Get File Path
  2. Convert To Base 64 String
  3. Long Running Task (I added this to make the need to be multi-threaded more pronounced)

The implementation of those actions will be trivial. Most will be one line of code, so I'm going to forgo testing. We are just focusing on how to implement the reverse decorator pattern.

I created this UI, just the button and a big text box to put the image converted to text. I also put the progress bar so that the user can see we are doing something in the background.

I added a class folder called ConvertFileToBase64 and added this interface:

VB.NET
Public Interface IConvertFileToBase64
  Sub RunMe(ByVal dataObj As ConvertFileToBase64Vals)
End Interface

You'll notice that this interface's method is a Sub and not a Function. We will modify how it is called and because we use the value class for state changes, we don't need it to return a value. Also, less problems with syncing the values on different threads if it doesn't have to return a value.

Our value class looks like this:

VB.NET
Public Class ConvertFileToBase64Vals

  Public Sub New()
    ErrObj = New ErrorObj()
  End Sub
  
  Public Property ErrObj As ErrorObj
  Public Property FilePathAndName As String
  Public Property Base64String As String

End Class

It has the ErrObj as in the previous example, as well as spots to hold the file name and the base 64 string. Nothing really exciting.

The first action, I'll name GetFilePath. I'll implement the interface and add the reference to that interface in the constructor. This is how the actions get linked together at run time. Currently, the class looks like this:

VB.NET
Public Class GetFilePath
  Implements IConvertFileToBase64

  Private _runMeNext As IConvertFileToBase64

  Public Sub New(ByVal runMeNext As IConvertFileToBase64)
    _runMeNext = runMeNext
  End Sub

  Public Sub RunMe(dataObj As ConvertFileToBase64Vals) Implements IConvertFileToBase64.RunMe

  End Sub

End Class

Now, we add the check to see if we need to run the _runMeNext function. It will go after any action code (although there is no action code, so you can't see it yet).

VB.NET
Public Sub RunMe(dataObj As ConvertFileToBase64Vals) Implements IConvertFileToBase64.RunMe

  If Not IsNothing(_runMeNext) Then
    _runMeNext.RunMe(dataObj)
  End If

End Sub

Notice there is no return value so we never have to return to this method. We add the error checking and a try/catch block and then we are ready to add our action code.

VB.NET
  Public Sub RunMe(dataObj As ConvertFileToBase64Vals) Implements IConvertFileToBase64.RunMe

  If Not dataObj.ErrObj.HasError Then

    Try

    Catch ex As Exception
      dataObj.ErrObj.HasError = True
      dataObj.ErrObj.Message = "GetFilePath: " & ex.Message
    End Try

  End If

  If Not IsNothing(_runMeNext) Then
    _runMeNext.RunMe(dataObj)
  End If

End Sub

We add the action code and complete our class. The whole class is as follows:

VB.NET
Public Class GetFilePath
  Implements IConvertFileToBase64

  Private _runMeNext As IConvertFileToBase64

  Public Sub New(ByVal runMeNext As IConvertFileToBase64)
    _runMeNext = runMeNext

  End Sub

  Public Sub RunMe(dataObj As ConvertFileToBase64Vals) Implements IConvertFileToBase64.RunMe

    If Not dataObj.ErrObj.HasError Then

      Try

        Dim OpenFileDialog1 = New OpenFileDialog()
        If OpenFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
          dataObj.FilePathAndName = OpenFileDialog1.FileName
        End If

      Catch ex As Exception
        dataObj.ErrObj.HasError = True
        dataObj.ErrObj.Message = "GetFilePath: " & ex.Message
      End Try

    End If

    If Not IsNothing(_runMeNext) Then
      _runMeNext.RunMe(dataObj)
    End If

  End Sub

End Class

Similarly, we add the other two actions, I named them ConvertToBase64 and LongRunningTask. You can see them in the source code.

I added two other subs to the form, TurnOnWait and TurnOffWait. To turn on and off the wait cursor and progress bar. Further, I added the sub btnConvert_Click_FinishUp. It has the job of getting the result and writing it to the text box.

OK, let's add an action to move this to a new thread. I'll name it MoveToNewThread. We start with the same format we used for the other actions, then add the background worker functionality. Microsoft has documentation on the backgroundworker here. Take a look at that documentation and see if what I did in the MoveToNewThread class makes sense:

VB.NET
Public Class MoveToNewThread
  Implements IConvertFileToBase64

  Private WithEvents _bgw As BackgroundWorker
  Private _nextSub As IConvertFileToBase64
  Private _callMeWhenDone As Action(Of ConvertFileToBase64Vals)

  Public Sub New(ByRef callMeWhenDone As Action(Of ConvertFileToBase64Vals), _
                 ByRef nextSub As IConvertFileToBase64)

    _callMeWhenDone = callMeWhenDone
    _nextSub = nextSub
    If IsNothing(_bgw) Then
      _bgw = New BackgroundWorker()
      _bgw.WorkerReportsProgress = True
      _bgw.WorkerSupportsCancellation = True
    End If

  End Sub 'New

  Private Sub bgw_DoWork(ByVal sender As System.Object, ByVal e As DoWorkEventArgs) _
   Handles _bgw.DoWork

    Dim locDataObj As ConvertFileToBase64Vals = TryCast(e.Argument, ConvertFileToBase64Vals)
    If IsNothing(locDataObj) Then
      Throw New System.Exception("GetServerInfo: No locDataObj passed. Ending Execution.")
      Exit Sub
    End If

    e.Result = locDataObj

    If Not IsNothing(_nextSub) Then
      _nextSub.RunMe(locDataObj)
    End If

  End Sub 'bgw_DoWork

  Private Sub bgw_RunWorkerCompleted(ByVal sender As Object, _
         ByVal e As RunWorkerCompletedEventArgs) Handles _bgw.RunWorkerCompleted

    Dim dataObj As ConvertFileToBase64Vals = TryCast(e.Result, ConvertFileToBase64Vals)

    _callMeWhenDone(dataObj)

  End Sub 'bgw_RunWorkerCompleted

  Public Sub RunMe(dataObj As ConvertFileToBase64Vals) Implements IConvertFileToBase64.RunMe

    If Not dataObj.ErrObj.HasError AndAlso Not _bgw.IsBusy Then
      _bgw.RunWorkerAsync(dataObj)
    ElseIf Not IsNothing(_nextSub) Then
      _nextSub.RunMe(dataObj)
    End If

  End Sub

End Class

Notice that in the constructor, callMeWhenDone as Action is passing a method that takes a ConvertFileToBase64Vals object. This method is called when the thread is finished, as you see in the bgw_RunWorkerCompleted method.

We need to write the method that we will pass in the constructor. Since it will be the finishing up part to the btnConvert_Click event, I name it btnConvert_Click_FinishUp. We also need it to take a ConvertFileToBase64Vals as a parameter. This is what I came up with:

VB.NET
Private Sub btnConvert_Click_FinishUp(ByVal dataObj As ConvertFileToBase64Vals)

    If dataObj.ErrObj.HasError Then
      Me.txtResults.Text = "ERROR: " & dataObj.ErrObj.Message
    Else
      Me.txtResults.Text = dataObj.Base64String
    End If

    TurnOffWait()

  End Sub

Now it is time to compose our classes together. We have to do it in reverse order of how we want them to run. So we have this:

VB.NET
Dim runMe As IConvertFileToBase64 = Nothing
runMe = New LongRunningTask(runMe)
runMe = New ConvertToBase64String(runMe)
runMe = New MoveToNewThread(AddressOf btnConvert_Click_FinishUp, runMe)
runMe = New GetFilePath(runMe)

Next, we run it like this:

VB.NET
Dim dataObj As New ConvertFileToBase64Vals()
runMe.RunMe(dataObj)

Go ahead and give it a try. In the code, I've included a .jpg file if you need an example one to convert to string. You can also use your own, only don't use too large an image as it will be too big and hang forever or throw an error.

Let me know what you think about this article. Did I make some obvious mistake that would have made everything easier? Does it make sense? I only used this example because it was handy and I wanted to write this article quickly. Let me know if I need to do another one with a better example to make the pattern more clear. This same pattern can be used for async actions too.

I've also used this reverse decorator pattern in the JavaScript article here. There are certain things about JavaScript that make this pattern work really well.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here