I have this code in my Parser and I want to pass text to Form1 so I can update some Labels or whatever.
(My structure is as follows: Form1 -> Engine -> Parser)
Sometimes I need to pass 2 strings, sometimes more.
Public Class Parser
Public Event NewInfo(<[ParamArray]()> Byval strArray() as String)
Public Sub SomeParser(ByVal m As Match)
Dim strArray() As String = {"Word1", "Word2"}
RaiseEvent NewInfo(strArray)
End Sub
End Class
Then I have this another class. I pass the Array to Engine and after that, to Form1, finally:
Public Class Engine
Private parent as Form1
Private WithEvents Parser As New Parser
Private Sub New(ByRef parent as Form1)
Me.parent = parent
EndSub
Private Sub ChangeLabel(ByVal str() As String) Handles Parser.NewInfo
parent.UpdateTextLabel(str)
End Sub
And then I have this in Form1:
Public Class Form1
Private WithEvents Engine as New Engine(Me)
Public Delegate Sub UpdateTextLabelDelegate(<[ParamArray]()> ByVal text() As String)
Public Sub UpdateTextLabel(ByVal ParamArray str() As String)
If Me.InvokeRequired Then
Me.Invoke(New UpdateTextLabelDelegate(AddressOf UpdateTextLabel), str())
Else
(Do stuff here)
End Sub
End Class
The code stops at Me.invoke(New UpdateTextLabelDelegate).... -line.
Exception is something like: System.Reflection.TargetParameterCountException
So it means something like wrong amount of parameters.. How to do this properly?
I would be very pleased if someone could explain and if I could understand how to do this.
I don't think you need <[ParamArray]()> in your code since it's already an array that you are passing:
Public Delegate Sub UpdateTextLabelDelegate(ByVal text() As String)
And as far as passing the data through the invoke, don't use str(), just str
Public Sub UpdateTextLabel(ByVal str() As String)
If Me.InvokeRequired Then
Me.Invoke(New UpdateTextLabelDelegate(AddressOf UpdateTextLabel), str)
Else
'(Do stuff here)
End If
End Sub
Finally managed to solve this problem. It wasn't that difficult but my mistake was something inside my own head.
I made no changes to Parser.vb so the above code is Ok.
Also, no changes to Engine.vb.
Changes to Form1.vb are here:
Public Class Form1
Private WithEvents Engine as New Engine(Me)
Public Delegate Sub UpdateTextLabelDelegate(<[ParamArray]()> ByVal text() As String)
Public Sub UpdateTextLabel(ByVal str() As String)
If Me.InvokeRequired Then
Me.Invoke(New UpdateTextLabelDelegate(AddressOf UpdateTextLabel), New Object() {str})
Else
'(Do stuff here)
End Sub
End Class
So, all I did was to insert New Object() {args} to the invoke line and removed ParamArray from the Public Sub UpdateTextLabel -line..
But thanks for Kicking my head so I had the reason to go forward! :)
Related
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
I was beginning to think that I was getting good at VB.net, but not this one has me stumped.
Code looks something like this
Public Class MyServer
.....
Public myMQTTclient = New MqttClient("www.myserv.com")
.....
Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles
MyBase.Load
<some code>
StartMQTT()
<some more code>
MQTT_Publish(.....)
End Sub
Public Function StartMQTT()
' Establish a connection
Dim code As Byte
Try
code = myMQTTclient.Connect(MQTT_ClientID)
Catch ex As Exception
<error handling code>
End Try
Return code
End Function
Public Sub MQTT_Publish(ByVal DeviceID As String, ByVal Channel As String, ByVal ChannelType As String, ByVal Value As String, ByVal Unit As String)
Dim myTopic As String = "MyTopic"
Dim myPayload As String = "My Payload"
Dim msgId As UShort = myMQTTclient.Publish(myTopic, Encoding.UTF8.GetBytes(myPayload), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, False)
End Sub
As this stands it works 100% OK. The coding may seem a bit odd, but the intent is as follows :
a) create an object 'myMQTTclient' at module level so it has scope throughout the module
b) run StartMQTT() - It can still see the object.
c) within main program call MQTT_Publish many times - I can still see the object
Now the issue is this... it all goes well until "www.myserv.com" fails DNS, then the underlying winsock code throws an exception.
So ... I'm thinking - no problem - just wrap the declaration in a try block or check that www.myserv.com exists before launching the declaration.
Ah, but you can't put code at module level, it has to be in a sub or function.
Hmmm... now I'm stumped. There has to be a 'proper' way to do this, but I'll be darned if I can figure it out.
Anyone able to help ?
I'd follow the advice from #djv about declaring it just as you need it. To wrap that in a Try... Catch block you can do that in an Init method.
Public Class MyServer
Implements IDisposable ' As per djv recommendation which I second...
Private myMQQTclient As MqttClient
Public Sub Init()
Try
myMQQTClient = New MqttClient("<your url>")
Catch ex As Exception
' Do whatever
End Try
End Sub
' more code and implement the Dispose method...
End Class
You can then go on and implement the IDisposble interface to ensure that you release the resources.
Ok, I am not sure if I have the right library. But I found this Nuget package: OpenNETCF.MQTT which seems to have the class you are using.
I would do it this way
Public Class MyServerClass
Implements IDisposable
Public myMQTTclient As MQTTClient
'Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles MyBase.Load
' ' <some code>
' StartMQTT()
' ' <some more code>
' ' MQTT_Publish(.....)
'End Sub
Public Sub New(brokerHostName As String)
myMQTTclient = New MQTTClient(brokerHostName)
End Sub
Public Function StartMQTT()
' Establish a connection
Dim code As Byte
Try
code = myMQTTclient.Connect(MQTT_ClientID)
Catch ex As Exception
'<error handling code>
End Try
Return code
End Function
Public Sub MQTT_Publish(ByVal DeviceID As String, ByVal Channel As String, ByVal ChannelType As String, ByVal Value As String, ByVal Unit As String)
Dim myTopic As String = "MyTopic"
Dim myPayload As String = "My Payload"
Dim msgId As UShort = myMQTTclient.Publish(myTopic, Encoding.UTF8.GetBytes(myPayload), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, False)
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
myMQTTclient.Dispose()
End If
End If
disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
End Sub
#End Region
End Class
And now you can see the usage when IDisposable is implemented, in a Using block:
Module Module1
Sub Main()
Using myserver As New MyServerClass("www.myserv.com")
myserver.StartMQTT()
myserver.MQTT_Publish(...)
End Using
End Sub
End Module
This makes it so your object is only in scope in the Using, and the object's Dispose method will automatically be called on End Using
I don't know what the base class was originally and why this was declared Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles MyBase.Load. It seems like it was possibly a form? You should keep your server code separate from Form code if that was the case. I suppose you could paste the Using into your form load, but then you would be blocking your UI thread. The referenced library has Async support so it might be a good idea to leverage that if coming from a UI.
I've made many assumptions, so I'll stop to let you comment and see how close relevant my answer is.
I have a shared array that needs a calculation for each index. Right now, the array is initialized in New() and executes each time an object is created, which is redundant. How can I set it up so that the array is only initialized once, when it is created? Something like a static block in Java?
I didn't initially include the code because I thought this was just a simple question. The constructor is called 64 times (working with values of bits) and he relevant code is :
' Decimal value of each bit
Public Shared bitValue(63) As Long
Public Sub New()
' Other non-relevant code here.
For index = 0 To 63
bitValue(index) = 1L << index
Next
End Sub
Just in case you are wondering what the heck I'm doing, I'm rewriting a Java program that I did that works with casting and bitwise operators. As part of learning VB, I'm working on a translation. You can get an idea of where it is headed by going to http://sourceforge.net/projects/javabitwise/.
You can initialize it right away
Class Test1
Private Shared _list As New List(Of String) From {"1", "2"}
End Class
Or do it in a shared constructor
Class Test2
Private Shared _list As List(Of String)
Shared Sub New()
_list = New List(Of String)
_list.Add("1")
_list.Add("2")
End Sub
End Class
Or check if the value is initialized or not in the new
Class Test3
Private Shared _list As List(Of String)
Public Sub New()
' Use synclock
If _list Is Nothing Then
_list = New List(Of String)
_list.Add("1")
_list.Add("2")
End If
End Sub
End Class
Or put your list in a singleton
Here's a quick example of the Singleton approach. We access the class via its Instance() function:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Debug.Print(KibblesAndBits.Instance.bitValue(10))
End Sub
End Class
Public Class KibblesAndBits
Private Shared _Kibbles As KibblesAndBits = Nothing
Public Shared Function Instance() As KibblesAndBits
If IsNothing(_Kibbles) Then
_Kibbles = New KibblesAndBits ' the private constructor will now fire and initialize
End If
Return _Kibbles
End Function
Public bitValue(63) As Long
Private Sub New() ' Private Constructor means you can't instantiate it directly
For index = 0 To bitValue.Length - 1
bitValue(index) = index
Next
End Sub
' ... other standard Class code ...
End Class
I know how to use BackgroundWorker (gui object in WinForms designer), and to manually instantiate Threads that elevate the custom event to the UI, however, I am having some trouble figuring out how to use the ThreadPool object (simplest form) to handle elevating an event to the form for "safe" UI manipulation.
Example is as follows :
Form1.vb
Public Class Form1
WithEvents t As Tools = New Tools
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
t.Unzip("file 1", "foo")
t.Unzip("file 2", "foo")
t.Unzip("file 3", "foo")
t.Unzip("file 4", "foo")
t.Unzip("file 5", "foo")
t.Unzip("file 6", "foo")
t.Unzip("file 7", "foo")
t.Unzip("file 8", "foo")
t.Unzip("file 9", "foo")
End Sub
Private Sub t_UnzipComplete(ZipInfo As Tools.ZipInfo) Handles t.UnzipComplete
TextBox1.Text = TextBox1.Text & ZipInfo.ZipFile & vbCr
End Sub
End Class
( add a multiline textbox, and a button to this form for the demo )
Tools.vb
Imports System
Imports System.Threading
Imports System.IO.Compression
Public Class Tools
#Region "Zip"
Private _zip As System.IO.Compression.ZipFile
Public Shared Event UnzipComplete(ByVal ZipInfo As ZipInfo)
Public Shared Event ZipComplete(ByVal ZipInfo As ZipInfo)
Public Class ZipInfo
Public Property ZipFile As String
Public Property Path As String
End Class
Public Sub Unzip(ByVal ZipFile As String, ByVal Destination As String)
Dim _ZipInfo As New Tools.ZipInfo
_ZipInfo.ZipFile = ZipFile
_ZipInfo.Path = Destination
ThreadPool.QueueUserWorkItem(AddressOf ThreadUnzip, _ZipInfo)
End Sub
Public Sub Zip(ByVal Folder As String, ByVal ZipFile As String)
Dim _ZipInfo As New Tools.ZipInfo
_ZipInfo.ZipFile = ZipFile
_ZipInfo.Path = Folder
ThreadPool.QueueUserWorkItem(AddressOf ThreadUnzip, _ZipInfo)
End Sub
Shared Sub ThreadUnzip(ZipInfo As Object)
RaiseEvent UnzipComplete(ZipInfo)
End Sub
Shared Sub ThreadZip(ZipInfo As Object)
RaiseEvent ZipComplete(ZipInfo)
End Sub
#End Region
End Class
What this code should do, is as follows :
On Button1_Click, add 9 items to the ThreadPool
On each thread completion (order is irrelevant), raise an event that elevates to Form1
The event being raised on Form1 should be UI safe, so I can use the information being passed to the ZipCompleted / UnzipCompleted events in the Textbox. This should be generic, meaning the function that raises the event should be reusable and does not make calls to the form directly. (aka, I do not want a "custom" sub or function in Tools.vb that calls specific elements on Form1.vb . This should be generic and reusable by adding the class to my project and then entering any "custom" form code under the event being raised (like when Button1_Click is raised, even though it's threaded, the other form interactions are not part of the Button1 object/class -- they are written by the coder to the event that is raised when a user clicks.
If you want to ensure that an object that has no direct knowledge of your UI raises its events on the UI thread then use the SynchronizationContext class, e.g.
Public Class SomeClass
Private threadingContext As SynchronizationContext = SynchronizationContext.Current
Public Event SomethingHappened As EventHandler
Protected Overridable Sub OnSomethingHappened(e As EventArgs)
RaiseEvent SomethingHappened(Me, e)
End Sub
Private Sub RaiseSomethingHappened()
If Me.threadingContext IsNot Nothing Then
Me.threadingContext.Post(Sub(e) Me.OnSomethingHappened(DirectCast(e, EventArgs)), EventArgs.Empty)
Else
Me.OnSomethingHappened(EventArgs.Empty)
End If
End Sub
End Class
As long as you create your instance of that class on the UI thread, its SomethingHappened event will be raised on the UI thread. If there is no UI thread then the event will simply be raised on the current thread.
Here's a more complete example, which includes a simpler method for using a Lambda Expression:
Imports System.Threading
Public Class Form1
Private WithEvents thing As New SomeClass
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.thing.DoSomethingAsync()
End Sub
Private Sub thing_DoSomethingCompleted(sender As Object, e As IntegerEventArgs) Handles thing.DoSomethingCompleted
MessageBox.Show(String.Format("The number is {0}.", e.Number))
End Sub
End Class
''' <summary>
''' Raises events on the UI thread after asynchronous tasks, assuming the instance was created on a UI thread.
''' </summary>
Public Class SomeClass
Private ReadOnly threadingContext As SynchronizationContext = SynchronizationContext.Current
Public Event DoSomethingCompleted As EventHandler(Of IntegerEventArgs)
''' <summary>
''' Begin an asynchronous task.
''' </summary>
Public Sub DoSomethingAsync()
Dim t As New Thread(AddressOf DoSomething)
t.Start()
End Sub
Protected Overridable Sub OnDoSomethingCompleted(e As IntegerEventArgs)
RaiseEvent DoSomethingCompleted(Me, e)
End Sub
Private Sub DoSomething()
Dim rng As New Random
Dim number = rng.Next(5000, 10000)
'Do some work.
Thread.Sleep(number)
Dim e As New IntegerEventArgs With {.Number = number}
'Raise the DoSomethingCompleted event on the UI thread.
Me.threadingContext.Post(Sub() OnDoSomethingCompleted(e), Nothing)
End Sub
End Class
Public Class IntegerEventArgs
Inherits EventArgs
Public Property Number() As Integer
End Class
You should register from the Form to events of the Tools class (you already have these events defined), of course the actual event will be fired under a non-UI thread, so the code it executes during the callback will only be able to update the UI via an Invoke()
You want to simply raise the event in the Tools class, the Invoke needs to be done because you want to update the UI, the Tools class should be concerned about that.
Change your event handling like so:
Private Sub t_UnzipComplete(ZipInfo As Tools.ZipInfo) Handles t.UnzipComplete
TextBox1.Invoke(Sub () t_UnzipComplete(ZipInfo))
End Sub
To register to the event from the view: (this would go in the Button1_Click event
AddHandler t.UnzipComplete, AddressOf t_UnzipComplete
Make sure you only register to the event one time
Does this solve your issue?
Private Sub t_UnzipComplete(ZipInfo As Tools.ZipInfo) Handles t.UnzipComplete
If TextBox1.InvokeRequired Then
TextBox1.Invoke(Sub () t_UnzipComplete(ZipInfo))
Else
TextBox1.Text = TextBox1.Text & ZipInfo.ZipFile & vbCr
End If
End Sub
You could create a callback to do the invoking in a safer way. Something like this:
Public Sub Unzip(ByVal ZipFile As String, ByVal Destination As String, _
ByVal SafeCallback As Action(Of ZipInfo))
And then the calling code does this:
t.Unzip("file 1", "foo", Sub (zi) TextBox1.Invoke(Sub () t_UnzipComplete(zi)))
Personally I think it is better - and more conventional - to invoke on the event handler, but you could do it this way.
Okay, so here is what I came up with using a combination of the information from everyone contributing to this question -- all excellent and VERY helpful answers, which helped lead me to the final solution. Ideally, I would like this as a straight "class", but I can accept a UserControl for this purpose. If someone can take this and do exactly the same thing with a class, that would definitely win my vote. Right now, I will really have to consider which one to vote for.
Here is the updated Tools.vb
Imports System
Imports System.Threading
Imports System.Windows.Forms
Imports System.IO.Compression
Public Class Tools
Inherits UserControl
#Region "Zip"
Private _zip As System.IO.Compression.ZipFile
Private threadingContext As SynchronizationContext = SynchronizationContext.Current
Private Delegate Sub EventArgsDelegate(ByVal e As ZipInfo)
Public Shared Event UnzipComplete(ByVal ZipInfo As ZipInfo)
Public Shared Event ZipComplete(ByVal ZipInfo As ZipInfo)
Public Class ZipInfo
Public Property ZipFile As String
Public Property Path As String
End Class
Public Sub Unzip(ByVal ZipFile As String, ByVal Destination As String)
Dim _ZipInfo As New Tools.ZipInfo
_ZipInfo.ZipFile = ZipFile
_ZipInfo.Path = Destination
ThreadPool.QueueUserWorkItem(AddressOf ThreadUnzip, _ZipInfo)
End Sub
Public Sub Zip(ByVal Folder As String, ByVal ZipFile As String)
Dim _ZipInfo As New Tools.ZipInfo
_ZipInfo.ZipFile = ZipFile
_ZipInfo.Path = Folder
ThreadPool.QueueUserWorkItem(AddressOf ThreadUnzip, _ZipInfo)
End Sub
Private Sub ThreadUnzip(ZipInfo As Object)
If Me.InvokeRequired Then
Me.Invoke(New EventArgsDelegate(AddressOf ThreadUnzip), ZipInfo)
Else
RaiseEvent UnzipComplete(ZipInfo)
End If
End Sub
Private Sub ThreadZip(ZipInfo As Object)
If Me.InvokeRequired Then
Me.Invoke(New EventArgsDelegate(AddressOf ThreadZip), ZipInfo)
Else
RaiseEvent ZipComplete(ZipInfo)
End If
End Sub
#End Region
End Class
If you drop this on Form1.vb, and select/activate the UnzipComplete/ZipComplete events, you will find that they will interact with the UI thread without having to pass a Sub, or Invoke, etc, from the Form. It is also generic, meaning it is unaware of what form elements you will be interacting with so explicit invoking such as TexBox1.Invoke() or other element specific calls are not required.
I find myself calling functions from lambdas frequently as the provided delegate does not match or does not have sufficient parameters. It is irritating that I cannot do lambda on subroutines. Each time I want to do this I have to wrap my subroutine in a function which returns nothing. Not pretty, but it works.
Is there another way of doing this that makes this smoother/prettier?
I have read that this whole lambda inadequacy will probably be fixed in VS2010/VB10 so my question is more out of curiosity.
A simple Example:
Public Class ProcessingClass
Public Delegate Sub ProcessData(ByVal index As Integer)
Public Function ProcessList(ByVal processData As ProcessData)
' for each in some list processData(index) or whatever'
End Function
End Class
Public Class Main
Private Sub ProcessingSub(ByVal index As Integer, _
ByRef result As Integer)
' (...) My custom processing '
End Sub
Private Function ProcessingFunction(ByVal index As Integer, _
ByRef result As Integer) As Object
ProcessingSub(index, result)
Return Nothing
End Function
Public Sub Main()
Dim processingClass As New ProcessingClass
Dim result As Integer
' The following throws a compiler error as '
' ProcessingSub does not produce a value'
processingClass.ProcessList( _
Function(index As Integer) ProcessingSub(index, result))
' The following is the workaround that'
' I find myself using too frequently.'
processingClass.ProcessList( _
Function(index As Integer) ProcessingFunction(index, result))
End Sub
End Class
If you find that you are doing it too often and generally with the same type of data, you can wrap the delegate in a class.
Create a base class that converts to the delegate:
Public MustInherit Class ProcessDataBase
Public Shared Widening Operator CType(operand As ProcessDataBase) as ProcessingClass.ProcessData
Return AddressOf operand.Process
End Sub
Protected MustOverride Sub Process(index As Integer)
End Class
Inherit from the class:
Public Class ProcessResult
Inherits ProcessDataBase
Public Result As Integer
Protected Overrides Sub Process(index as Integer)
' Your processing, result is modified.
End SUb
End Class
Use it:
Public Class Main()
Public Sub Main()
Dim processingClass As New ProcessingClass
Dim processor As New ProcessResult
processingClass.ProcessList(processor)
Dim result as integer=processor.Result
End Sub
End Class
It IS fixed in VB10, the VS10 Beta is available, if it's an option for you to use it. In VB10 you have lambdas without a return value, and inline subs/functions.
For now, maybe you could just forget lambdas and work with delegates instead? Something like:
processingClass.ProcessList(AddressOf ProcessingSub)