Use of delegates and invoke to call different sub depending on some variable - vb.net

I would like to organize a class as follows (pseudocode)
Class MyClass
sub New(MethodEnabled as integer)
if MethodEnabled = 0
make MyMethod() to be Sub0()
else
make MyMethod() to be Sub1()
end if
end sub
sub MyMethod()
invoke(either sub0 or sub1 depending on "MethodEnabled" value)
end sub
sub Sub0()
some code here
end sub
sub Sub1()
some code here
end sub
End Class
What I wish is to have the sub "MyMethod()" to invoke (or "to be") either Sub0() or Sub1(), depending on how the class was constructed (either with MethodEnabled=0, or MethodEnabled=1).
I gather intuitively that I can do that by using delegates and invoke, but I am not much clear on how to actually do it, in practice.
Can anybody show me how I can possibly do this in the most elegant way. C# of VB.NET examples would be equally great. Thank you!

You need to either declare a delegate type or use System.Action, and then use an instance of that delegate type:
Class [MyClass]
Private Delegate Sub myDelegate()
Private myDelegateInstance As myDelegate
'or you could just leave out 'myDelegate' and use 'System.Action'
Sub New(ByVal MethodEnabled As Integer)
If MethodEnabled = 0 Then
myDelegateInstance = AddressOf Sub0
Else
myDelegateInstance = AddressOf Sub1
End If
End Sub
Sub MyMethod()
myDelegateInstance()
End Sub
Sub Sub0()
'some code here
End Sub
Sub Sub1()
'some code here
End Sub
End Class

Related

Multi-Threading (Calling Sub From WorkerThread)

Need to call a sub that is coded written inside the block of form1 form an external worker thread. This is what I have written:
In Form1:
Public Delegate Sub UpdateControlDelegate(ByVal C As Label, ByVal txt As String)
Private Sub UpdateControl(ByVal C As Label, ByVal txt As String)
If C.InvokeRequired Then
C.Invoke(New UpdateControlDelegate(AddressOf UpdateControl), New Object() {C, txt})
Else
C.Text = txt
End If
End Sub
Public Sub DoStuff()
'we do some stuff then when it comnes time update a certain control:
Call UpdateControl(MyLabel, "My Text For The Label)
End Sub
In The workerThread that is located in a class:
Public Class MyClass
Public Sub UpdateData
Call Form1.DoStuff
End Sub
End Class
Does this look correct? The most simplest terms on what I am trying to achieve:
WorkerThread to call a Sub that is located in Class Form1
and that sub contains code that updates a couple controls in Form1.
After doing a little more research. I have figured it out. The initial code I have written is correct. The only thing missing is a reference to the form I need to update.
Here is the COMPLETE solution when needing to run a SUB from the UI that is called from the Worker Thread:
Public Class MyClass
'working thread is being within the subs of this class
Public MyForm1111 As Form1 '<------ The variable in this class that will reference to the form1 that we need
Public Sub MySubThatIsOnAWorkerThread
MyForm1111.DoStuff '<==== must call MyForm1111.DoStuff and NOT Form1.DoStuff
End Sub
End Class
The Sub Located In Form1:
Public Class Form1
Public Delegate Sub UpdateControlDelegate(ByVal C As Label, ByVal txt As String) 'Required Delegate
Private Sub UpdateControl(ByVal C As Label, ByVal txt As String) 'Sub to update controls
If C.InvokeRequired Then
C.Invoke(New UpdateControlDelegate(AddressOf UpdateControl), New Object() {C, txt})
Else
C.Text = txt
End If
End Sub
Public Sub DoStuff() 'the sub we need to call from the worker thread
'do some calculations and code
Call UpdateControl(MyLabel, "Some Text For Label")
End Sub
Private Sub Form1_Load()
MyClass.MyForm1111 = Me <==== Set the reference here in your Form1_Load
End Sub
End Class

Collect Generic Delegates in a Dictionary

I try to collect generic delegates in a dictionary. I made a little example, knowing that this example doesn't make sense. When I try to add the delegate to the dictionary I get a implizit conversion warning and I don't know how to solve this.
This is the line which shows the warning:
m_MyDoSomethings.Add(1, AddressOf myCustomDoSomething)
Hint: You have to activate implizit conversion warnings in your project, otherwise you won't get this error. It is a implizit conversion error, which tells me that it is not possible to convert the type "BaseDoSomething" to "CustomDoSomething". I don't understand why he is converting this way and not backwards.
Public Class Form1
Private Delegate Sub DoSomethingDelegate(Of T As BaseDoSomething)(p_DoSomethingClass As T)
Private m_MyDoSomethings As New Dictionary(Of Short, DoSomethingDelegate(Of BaseDoSomething))
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
m_MyDoSomethings.Add(1, AddressOf myCustomDoSomething)
End Sub
Private Sub myCustomDoSomething(p_CustomDoSomething As CustomDoSomething)
p_CustomDoSomething.echo()
p_CustomDoSomething.echo2()
End Sub
End Class
Public Class BaseDoSomething
Public Overridable Sub echo()
MsgBox("echo")
End Sub
End Class
Public Class CustomDoSomething
Inherits BaseDoSomething
Public Overrides Sub echo()
MsgBox("custom echo")
End Sub
Public Sub echo2()
MsgBox("hello world")
End Sub
End Class

Access variable in Shared Sub

is there a way to access a variable in Form_Load from an event handler?
Please dont mind the code, this is just a representation of my question.
Public Class Form
Public Sub Form_Load()
Dim x as string
x = MyClass.MethodGetValue()
End Sub
Private Shared Sub OnChanged()
MyClass2.MethodGetValue(x)
End Sub
End Class
It's about the scope of the variable. In your situation you need a class variable. This allows it to be used anywhere inside of this class.
Public Class Form1
Private x As Object 'pick the datatype that matches your needs
Public Sub Form_Load()
x = MyClass.MethodGetValue()
End Sub
Private Sub OnChanged()
MyClass2.MethodGetValue(x)
End Sub
End Class

Elegant pattern for multiple constructors

Assuming an object with the following code...
Public Sub New()
Me.Name = "Default Name"
Initialize()
End Sub
Public Sub New(CustomName as String)
Me.Name = CustomName
Initialize()
End Sub
Private Sub Initialize()
'Initialize some other properties
End Sub
Is there a more elegant pattern for this use case? Some way where one constructor could call the other constructor and eliminate the need for the Initialize() method?
Yes, you could write one constructor with all the needed parameters and then write the rest with a call to Me.Constructor() without anything else in the method block that supplies the defaults.
Public Sub New(CustomName as String)
Me.Name = CustomName
End Sub
Public Sub New()
Me.New("Default Name")
End Sub
I'm not sure if there's a VB syntax for constructor chaining in the C# sense, but if I remember correctly VB can invoke other constructors internally by calling Me.New(). Which is kind of the same thing. So you should be able to do something like this:
Public Sub New()
Me.New("Default Name")
End Sub
Public Sub New(CustomName as String)
Me.Name = CustomName
'Initialize some other properties
End Sub

VB.net, Invoke, delegates, and threading. Can't figure out how to use them across classes

Long story short, I'm having a hell of a time trying to figure out how to use invoke and/or delegates to update the userform from a separate class when using threading. I'm quite sure it's something silly and obvious to someone with more experience. I know a delegate is probably required, but all my efforts seem to only work when it's being called from main thread. I've been looking around the internet for half the day, and there's just something I'm not getting.
Here's some pseudo-code as an example:
This option works:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t1 As New Threading.Thread(AddressOf Count)
t1.IsBackground = True
t1.Start(100)
End Sub
Private Sub Count(ByVal Max As Object)
If TypeOf Max Is Integer Then
Count(CInt(Max))
End If
End Sub
Private Sub SetLabelText(ByVal text As String)
If Label1.InvokeRequired Then
Label1.Invoke(New Action(Of String)(AddressOf SetLabelText), text)
Else
Label1.Text = text
End If
End Sub
Private Sub Count(ByVal Max As Integer)
For i = 1 To Max
SetLabelText(CStr(i))
Threading.Thread.Sleep(200)
Next
End Sub
End Class
While this (one of my 1000 efforts of slightly different variation) does not. Practically speaking, I just tried to separate one of the subs into its own class for this example, but it's otherwise the same as I could make it:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t1 As New Threading.Thread(AddressOf Count)
t1.Start(100)
End Sub
Private Sub Count(ByVal Max As Object)
If TypeOf Max Is Integer Then
Dim class2 As New class2
class2.Count(CInt(Max))
End If
End Sub
Private Delegate Sub SetTextBoxTextInvoker(text As String)
Sub SetLabelText(ByVal text As String)
'or me.label1, form1.label1 or anything else I can try!
If Me.InvokeRequired Then
Me.Invoke(New SetTextBoxTextInvoker(AddressOf SetLabelText), _
text)
Else
Me.Label1.Text = text
End If
End Sub
End Class
Public Class class2
Sub Count(ByVal Max As Integer)
For i = 1 To Max
form1.SetLabelText(CStr(i))
Threading.Thread.Sleep(200)
Next
End Sub
End Class
From what I can tell, it appears that the if statement for invokerequired in the Sub "SetLabelText" never gets triggered. My best guess is that I'm not referring to the userform correctly when checking for the invokerequired parameter? Or I need to feed something else to the delegate? I'm just getting frustrated with messing around with the million little variables I might be getting wrong. Thanks in advance for any help you can provide and let me know if you need more info.
I'm not certain I understand what you are trying to do, but building upon your code, you can set the label safely ("thread-safely") by using the following code:
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim t1 As New Threading.Thread(AddressOf Count)
t1.IsBackground = True
t1.Start(100)
End Sub
Private Sub Count(ByVal Max As Object)
If TypeOf Max Is Integer Then
Dim class2 As New Class2
class2.Count(CInt(Max), AddressOf SetLabelText)
End If
End Sub
Private Sub SetLabelText(ByVal text As String)
If Label1.InvokeRequired Then
Label1.Invoke(New SetText(AddressOf SetLabelText), text)
Else
Label1.Text = text
End If
End Sub
End Class
Public Class Class2
Sub Count(ByVal Max As Integer, SetTextMethod As SetText)
For i = 1 To Max
SetTextMethod.Invoke((CStr(i)))
Threading.Thread.Sleep(200)
Next
End Sub
End Class
Public Delegate Sub SetText(text As String)
I created a Delegate called "SetText"; when the form calls the count function in your class, you can pass an instance of the delegate that references the SetLabelText method. Within that method you can then safely set the label text either directly or indirectly via Invoke along with a new instance of the delegate.
Something you definitely don't want to do is reference your form from your class(i.e. "form1.SetLabelText(CStr(i))"); that can create a real nightmare as the project grows in size and requirements change!
If I've misunderstood your question or not answered it properly, please do post back.
First off I would suggest using the Task Parrallel Library instead of threads. It's easier to understand and work with. For example,
Dim countTask as New Task(Sub() Count(10))
Dim displayTask = countTask.ContinueWith(Sub()
Me.Invoke(Sub() Label.Text = "10"
End Sub)
countTask.Start()
This example assumes you are calling this from the form itself. You can use this method to return values from the first task to the second. If you need a more detail example and want more examples check out http://msdn.microsoft.com/en-us/library/hh228603.aspx. If you need further help I can always throw something up on GitHub or blog about it. Good Luck.