In Forms application I open a form which uses an object to communicate with serial port. The communication object raises a class event when some data are received from serial port and are checked to be valid.
Problem: after I close the form, there should be no further communication. For instance, I want another form to communicate. Unfortunately, even after I close the form, it keeps on receiving and handling the incoming data, which in fact causes interference with other processes in the application.
The actual code is too complex to show here, but to illustrate, let's assume three different classes/forms:
Communicator class: it has method to send data, it checks incoming data by polling the serial port using timer, validates received data and once the incoming data form a valid message, it raises custom event (e.g. DataReceivedEvent). When expected message is not complete within a required time limit, it raises DataTimeoutEvent.
ConfigurationForm class: it communicates with the serial port via Communicator. It has event handlers for both the DataReceivedEvent and the DataTimeoutEvent. The messages received are basically acknowledgements of configuration commands sent to a device via serial port.
DataCollectionForm class: it communicates with the serial port via Communicator. It has event handlers for both the DataReceivedEvent and the DataTimeoutEvent. It analyses and visualises data received, but the relevant message types are different than those used by ConfigurationForm.
The problem is that once the DataCollectionForm class is instantiated and shown, it handles all messages from Communicator, even after the form is closed. The event handler sort of "hangs" even though the form object itself is no longer valid (or, rather, should no longer be valid).
You need to remove the event handler registration(s). If you have a Handles clause on your event handlers, set the field specified to Nothing. If you used AddHandler to register the event handler, you need to use RemoveHandler to unregister it. That means that you need to keep a reference to the object that raises the event. E.g.
Private WithEvents someObject As new SomeType
Private someOtherObject As SomeOtherType
Private Sub DoSomething()
someOtherObject = New SomeOtherType
AddHandler someOtherObject.SomeOtherEvent, AddressOf SomeOtherObject_SomeOtherEvent
End Sub
Private Sub SomeObject_SomeEvent(sender As Object, e As EventArgs) Handles someObject.SomeEvent
'...
End Sub
Private Sub SomeOtherObject_SomeOtherEvent(sender As Object, e As EventArgs)
'...
End Sub
Private Sub Form1_FormClosed(sender As Object, e As EventArgs) Handles Me.FormClosed
someObject = Nothing
RemoveHandler someOtherObject.SomeOtherEvent, AddressOf SomeOtherObject_SomeOtherEvent
someOtherObject = Nothing
End Sub
Related
My class watches a directory for incoming files. It does do so with a FileSystemWatcher object, only monitoring the FSW's Created events.
On a Created event, I start a potentially time-consuming process (file-deserialization is needed, sending an event to the client using my class, in which all sorts of things might happen). Thus, I start a BackgroundWorker object to do all this work, ultimately culminating in the received file's removal.
However, during all this work, new files may appear. In the Created event I check, if the BGW is still busy, and if so, I just store the fully qualified name in a queue for later consumption.
Public Sub New(Path As String)
FSM = New FileSystemWatcher
With FSW
.Path = Path
AddHandler .Created, AddressOf pFileArrived
End With
BGW = New BackgroundWorker
With BGW
.WorkerReportsProgress = False
.WorkerSupportsCancellation = False
AddHandler .DoWork, AddressOf BGW_DoWork
AddHandler .RunWorkerCompleted,
AddressOf BGW_RunWorkerCompleted
End With
End Sub
Private Sub pFileArrived(sender As Object, e As FileSystemEventArgs)
pNotifyClient(e.FullPath)
End Sub
Private Sub pNotifyClient(sFullPath As String)
If Not BGW.IsBusy Then
BGW.RunWorkerAsync(sFullPath)
Else
MyQueue.Enqueue(sFullPath)
End If
End Sub
Private Sub BGW_DoWork(ByVal sender As Object,
ByVal e As DoWorkEventArgs)
'...
End Sub
But how can I find out, when the BGW is done?
I know, that there is the RunWorkerCompleted event. However, this event is fired from a real BGW instance still existing, so I can not go on and simply call it again from within the event handler.
Private Sub BGW_RunWorkerCompleted(sender As Object,
e As RunWorkerCompletedEventArgs)
'This won't work.
If MyQueue.Count > 0 Then
BGW.RunWorkerAsync(MyQueue.Dequeue)
End If
End Sub
What is the proper way of doing such things? Initializing a timer does spring to mind, but it doesn't seem right. (How much time should I give it? Should I loop for the BGW thread's end?)
Or should I consider another approach than invoking a BGW?
If you want to nuke this problem from the orbit, put an AutoResetEvent(false) somewhere and your BGW's last task should be to evt.Set() and your main thread can do evt.WaitOne(0) to just query the status. If there's a possibility of running multiple BGWs at the same time, you need some data structure to keep track of which ARE is associated with which BGW.
A larger investment would be switch to pure producer-consumer design, which involves a queue (which you already have in a form) and consumer thread(s) to dequeue work items and process them.
Here is my situation, I have a user control that have the Leave event:
Private Sub MyControl_Leave(sender As Object, e As EventArgs) Handles Me.Leave
If Me.Enabled Then
MsgBox(Property1)
End If
End Sub
I have this to prevent Leave Event from triggering when the control is Disabled.
Then on my form, the control also has its own Leave event because I need to set some Properties that the Leave Event on the User Control needs.
Private Sub myControlOnForm_Leave(sender As Object, e As EventArgs) Handles MyControlOnForm.Leave
MyControlOnForm.Property1 = "value1"
End Sub
What happens is the first event that triggers is the one on the User Control and then the one on the form.
Now my problem is, as the code states above, I need the Form Event to trigger first before the User Control Event.
Is there any work around for this?
The form needs to call a procedure in the user control after it's finished handling the event. Just remove the Handles Me.leave statement, and the private statement. use the sub from your form to call the controls sub which was intended to handle the event.
Note that I've changed sender As object to sender As Mycontrol.
Sub MyControl_Leave(sender As Mycontrol, e As EventArgs)
If Me.Enabled Then
MsgBox(Property1)
End If
End Sub
Code on form
Private Sub myControlOnForm_Leave(sender As Mycontrol, e As EventArgs) Handles MyControlOnForm.Leave
MyControlOnForm.Property1 = "value1"
sender.MyControl_Leave(sender, e)
End Sub
What happens is the first event that triggers is the one on the User Control and then the one on the form.
Now my problem is, as the code states above, I need the Form Event to
trigger first before the User Control Event.
First off, you are using the incorrect term in that problem statement. It is not an event triggering order issue, but rather an issue in order in which the event handlers registered for the UserControl's Leave event execute.
.Net events are a form of syntactic sugar for the invocation of a multicast delegate. When an event is raised a delegate is invoked and the order in which the handlers are executed is the order in which they were added to the delegate. You can gain an understanding of this by working through the various "Walkthrough" tutorials located under Events (Visual Basic).
The Leave event is Raised by calling the Overridable OnLeave method inherited from the Control Class that is in the inheritance tree of the UserControl Class. It is considered bad form for a class to handle its own generated event; the preferred method is Override the method that raise the event.
In your case, you want the form that subscribes to the event to be notified first so that it can modify a property on the UserControl before some it performs some action in response to Leaving the UserControl.
Public Class UserControl1
Protected Overrides Sub OnLeave(e As EventArgs)
MyBase.OnLeave(e) ' this calls the base method that Raises the event
' all event handlers will run before the subsequent code
' executes
If Me.Enabled Then
'do something
End If
End Sub
End Class
I create an object of an inherited class. One of its events is to fire on change in a list. I tried writing up an example, but I found it far easier to put down my logic. By "an event is raised", I mean RaiseEvent. This is also my first time with custom events, and inheritable classes.
Create Object of Inherited Class (in its own thread)
Inherited Class runs MyBase.New()
Base Class starts listening for incoming requests
If request is received and valid, an event from Base Class is raised which is handled by the Inherited Class (with this event a list might be modified)
If list is changed, an event from Inherited Class is raised and is handled by the GUI
Base Class returns to waiting for a new request
I'm trying to avoid editing the GUI from the inherited class. The problem is the event never fires (it ignores the RaiseEvent in Step 5, and the GUI never gets an event). Possibly the separate thread is an issue?
Example of Step 5:
Private Sub RequestReceived(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.RequestReceived
'RequestReceived is from the Base Class
Dim requestType As String = sender
If requestType = "Type1" Then
RequestIsVariantOne(sender)
Else Then
'Handle other variants similarly
End If
End Sub
Private Sub RequestIsVariantOne(ByVal sender As Object)
'Conditional statements go here that determine whether or not to edit the list
'The statements will exit the Sub if it the list should not be edited.
'If Sub hasn't exited yet, now we edit the list.
ThatList.Add(sender)
'ListChanged is from the Inherited class
RaiseEvent ListChanged(ThatList, Nothing)
End Sub
Private Sub ToolStripMenuItem_Click(sender As System.Object, e As System.EventArgs) Handles ToolStripMenuItem.Click
Dim rT As New Threading.Thread(AddressOf ListenerThread)
rT.Start()
End If
End Sub
Private Sub ListenerThread()
r = New RequestListener()
*r.Listen()*
End Sub
In asterisks, this was the key element. Originally, it was called from the inherited class' constructor. This meant there was never a return to the GUI, and my class level variable was never updated. Therefore, any references to RequestListener (specifically the Events) wouldn't trigger because RequestListener is Nothing. Calling it from the GUI solved the r IS Nothing issue.
I discovered this by creating a button to raise the event manually, but was greeted with NullReferenceException. Once I started my listener from my GUI, the method handling my custom event was fired. I assume this solves my problem, but will need to work a bit more.
I've written two event handlers for the TextBox.Leave event for a TextBox1
The reason for this is that the first handler is a common one for multiple TextBox.Leave events which validates the values, and the second one is specific for the above TextBox1 which does some calculation of values.
My query is that can I know which of the two handlers will execute first when TextBox1.Leave happens?
(I know I can remove the code from the common handler to the specific one for TextBox1, but still I wish to know if there is a way.)
Thanks
As long as the event handlers are added using the AddHandler statement, the event handlers are guaranteed to be called in the same order that they were added. If, on the other hand, you are using the Handles modifier on the event handler methods, I don't think there is any way to be sure what the order will be.
Here's a simple example that demonstrates the order as determined by the order in which AddHandler is called:
Public Class FormVb1
Public Class Test
Public Event TestEvent()
Public Sub RaiseTest()
RaiseEvent TestEvent()
End Sub
End Class
Private _myTest As New Test()
Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
AddHandler _myTest.TestEvent, AddressOf Handler1
AddHandler _myTest.TestEvent, AddressOf Handler2
_myTest.RaiseTest()
RemoveHandler _myTest.TestEvent, AddressOf Handler1
RemoveHandler _myTest.TestEvent, AddressOf Handler2
End Sub
Private Sub Handler1()
MessageBox.Show("Called first")
End Sub
Private Sub Handler2()
MessageBox.Show("Called second")
End Sub
End Class
I'd recommend you change to having a single handler, and detect which textbox is being left:
Private Sub txt_Leave(sender As Object, e As System.EventArgs) Handles TextBox1.Leave, TextBox2.Leave
Dim txt As TextBox = DirectCast(sender, TextBox)
If txt Is TextBox1 Then
txt.Text = "Very important textbox!"
Else
txt.Text = "Boring textbox ho hum."
End If
End Sub
This is just additional information not related to the order of execution of multiple event handlers with the same "Handles" clause.
It may be of interest, though, to those wondering about when to use a "Handles" clause and when to use the "AddHandler" statement.
Addhandler is more useful when using a UDC (User Defined Control) to execute procedures/functions or manipulate data within a form.
UDCs know nothing about the form on which they have been attached or referenced.
They are generic and written for reuse in many projects.
Take (for example) the standard "TextBox" control.
Does TextBox know anything about the form on which it rests? No.
All its properties are available to the form but none of the form's properties is available to the Textbox.
(The Textbox is a pre-supplied "UDC", and Microsoft is the User defining it)
If you want your own UDC to operate on data in the main form, you define a public event within the UDC (let's call this event "UC_Update")
It is placed on a single line at the top of the code in the UDC:
Public Event UC_Update ' UC stands for "User Control"
When your UDC (let's call it "MyControl") wants to work with data on its parent form, it can call this event (within the UDC code) with the line:
RaiseEvent UC_Update
Within the code of the form on which this control has been placed or referenced, you attach this event belonging to your UDC to an instance of your control and "point" that event to another routine written in the form.
To do this, you use the "AddHandler" directive and an associated "AddressOf" operator.
Say the process you have written to manipulate data/controls in the form along with data/methods in your UDC, is called "MyControlUpdater".
It will be in your form and look like this:
Private Sub MyControlsUpdater()
......(code).......
end sub
The "(code)" may be lines that use data in your UDC (via public properties or public Subs/Functions) or use data or controls within your form. Your UDC can now do something with items on your form.``
You would place within your form's code the following:
AddHandler MyControl.UC_Update, AddressOf MyControlsUpdater
This directive is placed somewhere after the UDC has been instantiated:
Dim oMyControl as New MyControl
AddHandler oMyControl.UC_Update, AddressOf MyControlsUpdater
If, however, the UDC is physically on your form as "MyControl1", placed there via the Visual Studio Toolbox, then you would add the Handler in your form's "Load" procedure:
Private Sub Form1_Load(sender as object, e as eventargs) Handles Me.Load
AddHandler MyControl1.UC_Update, AddressOf MyControlsUpdater
It is important to note that you cannot pass parameters via this process (when using "AddressOf"). That is why there is no "sender" in the "MyControlsUpdater" subroutine. Values that relate to your UDC
must be obtained by way of public properties in that UDC. Public UDC functions and subroutines (subs) are also available.
If you are NOT working with a UDC (often the case) but with controls created by others such as Microsoft (and therefore the internal code of the control is not available), then you use the "Handles" clause to establish how the control is handled when a certain event arises (such as a "Click" on the control).
Private sub UpdateData(sender as object, e as eventargs) handles Textbox1.Click
........(code)......
End Sub
Of course, if your own UDC doesn't need to know anything about data on your form and you are referencing it rather than adding it physically to the form, you can just use the "Handles" delegation on your own UDC as per normal:
Dim MyControl1 as New MyControl
Private Sub UpdataData(sender as object, e as eventargs) _
handles MyControl1.Text.Leave
Here the form uses data in the control (via public properties) rather than the control using data within the form.
Okay so I've created a list of objects from a class like
For B As Integer = 0 To 5
clients.Add(New Client)
AddHandler clients(B).OnMessage, AddressOf clients_OnRec
Next
Then this is the event declaration
Public Event OnRec As EventHandler
This is my Event
Private Sub clients_OnRec(ByVal sender As Object)
'Does something
End Sub
My question is how can i determine which instance of the class in the list raised the event. I need to be able to do something like:
clients(whateveronefiredit).ExecuteMethodInClass
How can i do that?
:) Let me try explaining what is happening
Now in the list you have 5 objects of type Client and all this 5 of them call the event handler clients_OnRec on some kind of event.
When the first client raises this event the sender in the event handler signature Private Sub clients_OnRec(ByVal sender As Object) will have the client objects reference which raised the event.
so, in order to call the method ExecuteMethodInClass on the object which raised the event, you would do the following:
Private Sub clients_OnRec(ByVal sender As Object)
Dim c As Client = CType(sender, Client) 'Cast the sender object as Client object
c.ExecuteMethodInClass() 'This executes the ExecuteMethodInClass on the Client object which raised this event
End Sub
Hope that is clear.
Cheers