Issues with Event Handlers not being removed (using RemoveHandler) when sender is part of another class - vb.net

I have a system where a Class of "Automation Providers" is working with some Control objects to provide some advanced monitoring functionality to them (by dynamically monitoring events).
A part of my code; A class called Automation_Provider contains a function called Browser_Navigate that takes in a reference to a System.Windows.Forms.WebBrowser instance and performs a .Navigate operation with a URL.
The special functionality it provides is that it sets an Event Handler to the Browser.DocumentCompleted to perform some actions when the event is raised.
This part actually works. What doesn't work, is I'm trying to dynamically remove the handler that causes the Subroutine to be called in the first place, but it doesn't seem to remove the Handler and if I try to call the function again, it fires twice.
The code looks like this:
Public Class Automation_Functions
Public Function Browser_Navigate(ByRef Browser As WebBrowser, ByVal Address As String) As Function_Status
'-----------------------------------------------------------------------------------
' A bunch of URL checks are performed here to make sure the "Address" is a valid URL
'-----------------------------------------------------------------------------------
AddHandler Browser.DocumentCompleted, AddressOf Browser_Navigation_Callback
Browser.Navigate(Address)
End Function
Private Sub Browser_Navigation_Callback(sender As Object, e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
'Get the browser object from the sender and remove the handler that initially called this function
Dim Browser As WebBrowser = CType(sender, WebBrowser)
RemoveHandler Browser.DocumentCompleted, AddressOf Browser_Navigation_Callback
'------------------------------------------------------------------------------------------
' A bunch of operations are performed here related to telling the caller of the original
' function about the performance of the web page - stuff like load time, etc.
'------------------------------------------------------------------------------------------
End Sub
End Class
However the Past Handlers are never actually removed from the Browser_Navigation_Callback function - I know this because calling the Browser_Navigate function a second time results in Browser_Navigation_Callback function being called twice, then if you call it a third time, it gets called Three times! - The Handlers just compound on top of each other since they never actually get removed.
I've been unable to find any reason as to why this is happening - I'm assuming it might have something to do with the fact that the WebBrowser object is actually inside of another class and casting the sender as a WebBrowser object doesn't refer it back to the original WebBrowser instance, but creates a new instance. However I have no idea on how to confirm that this is actually what is happening since I can't really see the attached event handlers in debug mode.
Other than that - this code should be working. Any help would be appreciated!

One of the features of VB when Option Strict is not set is that it will use "relaxed delegate conversion". This means that when a delegate argument does not exactly match the required signature but is compatible with it, VB will automagically insert an anonymous wrapper that converts from the provided delegate to the required signature.
This has a negative interaction with AddHandler and RemoveHandler. VB creates a new anonymous wrapper on each call, and each instance is distinct. As a practical matter, that means that it is impossible to remove a handler that has been added when relaxed delegate conversion is used.
With Option Strict on, relaxed delegate conversion is disabled, so it turns into a compiler error when the delegate argument does not exactly match the required signature. I have not found an individual compiler switch that will turn off relaxed delegate conversion, so the only way to deal with this is to turn on Option Strict for the file.

Related

What is the purpose of calling Invoke from the specific control that's affected?

Say I've got the following sub that simply adds passed items to a ListView control:
Private Sub AddListItem(ByVal item As ListViewItem)
UIList.Items.Add(item)
End Sub
And I use that from a BackgroundWorker thread, like so:
UIList.BeginInvoke(Sub() AddListItem(lvItem))
Well quite by accident I've just discovered that it doesn't seem to matter which control is used to call the Invoke\BeginInvoke method, or even if I omit a control altogether and just call the method directly – which I assume just uses Me.<Method> behind the scenes – it doesn't seem to matter. The code still works.
So, is using the affected control to call the method just a way to make following the code easier? What, if any, are the other advantages? And are there certain pitfalls one needs to be aware of when using a different control?
Using ILSpy and digging down the Control.Invoke method, an excerpt is
...
UnsafeNativeMethods.PostMessage(
new HandleRef(this, this.Handle),
Control.threadCallbackMessage,
IntPtr.Zero,
IntPtr.Zero);
...
In addition, MSDN states:
The Invoke method searches up the control's parent chain until it
finds a control or form that has a window handle if the current
control's underlying window handle does not exist yet. If no
appropriate handle can be found, the Invoke method will throw an
exception. Exceptions that are raised during the call will be
propagated back to the caller.
So usually it shouldn't matter which control you post to.
Personally, I use the "nearest" control I can get to call the Invoke method.

Access event in a user control created in code behind?

I'm trying to create a user control in my code behind, and then respond to events in that control. Presumably because the control doesn't exist at compile time, Visual Studio can't compile the handler subroutine I created to catch my control's event. Importantly, I want to decide the type of control at runtime (which is why I'm not just hard-coding it).
[before going on, the controls work correctly, including events and event handlers when used in the 'normal' way of creating the controls in XAML. I want to create the control instances in code behind so I can avoid duplicating pages that are 99% identical]
This 'works' (but doesn't give me the flexibility I need):
Public WithEvents AnswerPanel As MyControls.ScrollerControl
... (and the initialisation in the New() sub):
AnswerPanel = New MyControls.ScrollerControl
ItemStack3.Children.Add(AnswerPanel)
AddHandler AnswerPanel.GuessMade, AddressOf CheckAnswer
... (this is the handler sub responding to a custom event in the ScrollerControl)
Public Sub CheckAnswer(answer As String) Handles AnswerPanel.GuessMade
With the code above everything works as I expect: the control is created at runtime and its event is handled correctly.
What I want to achieve is to be able to choose a different user control when I initialise my control (e.g. ScrollerControl2, ScrollerControl3, etc.) I can create the controls this way by changing the first line to:
Public WithEvents AnswerPanel As UserControl
But once that change is made I can no longer reference the custom event in my handler as (presumably) the compiler sees it as a generic UserControl, which doesn't include my custom GuessMade event. The compiler errors on the event name and tells me it doesn't exist.
I'm sure I'm doing something wrong here. I think it's a theory/concept issue rather than my code.
Am I on the right track or going about this in the wrong way?
If I am reading this right, you have a user control that fires an event and you want the parent page to catch that even? If so, you need to raise the event, which will cause the event to bubble to the the parent. IE:
Partial Class user_controls_myControl
Inherits System.Web.UI.UserControl
Public Event DataChange As EventHandler
End Class
This creates a control with a public event called DataChange. Now, if you look at the code in the parent page that instantiates the user control, you will see that it has an event called "OnDataChange". Just like an onCLick event, you can assign this a method in the parent page. Now, you just need to raise the event in the user control. This can be added in some event in the control, like a button click or radio button change event:
RaiseEvent DataChange(Me, New EventArgs)
This takes two objects, the sender and event arguments. Typically I pass ME, which is the user control. This is great because you can use reflection to get all the controls public properties. You can also use this to cast objects to your control type. I rarely pass event arguments but you certainly could.
I answered a similar question here: Handling events of usercontrols within listview
If this is not what you had in mind, let me know
EDIT: To add a user control dynamically and attach the event:
First, in the page that will be using the control, you will need to add a place holder:
<asp:PlaceHolder ID="placeholder1" runat="server"></asp:PlaceHolder>
as well as a reference to the user control at the head of the page (depending on how the page is setup, you may not need this. If you get a page directive error, remove it):
<%# Reference="" Control="~/user_controls/myControl.ascx"%>
In the parent page, you can then create a user control and add it to the place holder. You must declare the user control with events like this:
Private WithEvents myNewControl As New user_controls_myControl
then, in some method you can add it to the page like this:
Dim getPh As New PlaceHolder
'create an instance of the user control
newMyControl = CType(LoadControl("~/user_controls/myControl.ascx"), user_controls_myControl)
'get a handle on the place holder
getPh = me.placeHolder1
'add the user control to the place holder
getPh.Controls.Add(newMyControl)
Then, make sure you have event method:
Protected Sub myEvent(ByVal sender As Object, ByVal e As EventArgs) Handles myNewControl.DataChange
End Sub
So, if you added the RaiseEvent to the user control like I suggested earlier, this should work for you.
I have an answer to this now. As I suspected I was sort of thinking about the problem from the wrong angle.
In a nutshell I was trying to raise an event from my user controls, but I needed to be raising the events in the base class and calling that from my user controls.
So my base class (which my user controls inherit from), now contains the following:
Public Event GuessMade(answer As String)
Protected Sub RaiseGuessEvent(answer As String)
RaiseEvent GuessMade(answer)
End Sub
Then, in my user control(s), when I need to raise the event, I simply call the RaiseGuessEvent sub like this:
Me.RaiseGuessEvent(CurrentValue)
And additionally, I had to remove the event from my subclasses/user controls, of course.

Cannot Change ObservableCollection during an CollectionChanged Event

Here's the problem:
I have a CollectionChanged Handler Method:
Private Sub LayersChanged(sender As Object, e As Collections.Specialized.NotifyCollectionChangedEventArgs)
If e.ItemCollectionHasNewItems IsNot Nothing Then
SomethingRelatedToE.execute
End If
End Sub
I cant call SomethingRelatedToE.excecute because it effects e's collection which causes a runtime error.
However if the LayersChanged method has completed I can then call SomethingRelatedToE.execute from another method without effecting it.
Is there a way for me to directly move to another method after the LayersChanged method has finished, like a Goto function or another solution to this?
The reason this type of action isn't allowed is because it will usually result in a circular reference. When the collection is modified in the CollectionChanged event, the event will once again be raised, which will again modify the collection.
I would not recommend trying to work around this because it's unusual to have a collection modify itself. Try thinking of a way to do it another way.
If you need to, though, you can try using ThreadPool.QueueUserWorkItem to do your task in another thread. You'll still want to check for possible circular references and watch for race conditions.

VB.NET Form.Show from another thread hanging form

I have a series of methods being called for my networking code. An event gets fired from the networking thread. Inside this event, which I've hooked into from a singleton class, I route messages to form level methods which they register on form load to handle certain messages they care about. Inside of these form message hooks I need to close the current form (which I was able to do) but also show a different one (which is giving me the trouble).
The new form sort of shows but it's hanging/not updating. I'm sure this has something to do with that form because it's .Show() was basically called from another thread (sort of) doesn't have a message loop, but I'm not sure how else to solve this. The network message that gets received indicates on the client machine what forms to close and show.
The flow might be confusing so I'll try better to explain.
Login form attaches user defined functions inside that form to a singleton class list of messages. For example when a message called LOGIN_STATUS is fired I assign a function from the Login form to a list defined in this singleton class.
The singleton class has the network class defined in it which actually runs on another thread, but this is all handled inside the class. In the private ctor I subscribe to the OnData event of this network class.
When OnData gets fired from the network class to the singleton class it passes to it the type of data. I loop through the list of function pointers to see if any of them are linked to LOGIN_STATUS and if so call them. This will call the Login forms function. Inside that function I need to close the Login form and open the Lobby form. That's when the Lobby form shows, but is hung up and not updating.
Hope that makes sense.
This is all being done in VB.NET where I have the "close when last form closed" setting on which is what I want. VB.NET also makes it easier to manage forms since I can just for formname.Show() instead of having to keep a list of the forms and manage them myself like in C# so if that's still possible with the solution that would be ideal.
If you want to ensure all forms are created on the same thread, and hence the same message loop, use the main from's Invoke method. The Form.Invoke and Form.BeginInvoke methods cause the code to run from the form's message loop. BeginInvoke allows the event calling thread to return immediately, where-as Invoke blocks the event thread until the method is complete. It depends how time sensitive your code is.
Private Sub OpenFormEvent(sender As Object, e As EventArgs)
If MainForm.InvokeRequired Then
Dim args As Object() = {sender, e}
MainForm.BeginInvoke(New EventHandler(AddressOf OpenFormEvent), args)
Else
Dim SecondForm As New Form()
SecondForm.Show()
End If
End Sub

Force multi-threaded VB.NET class to display results on a single form

I have a windows form application that uses a Shared class to house all of the common objects for the application. The settings class has a collection of objects that do things periodically, and then there's something of interest, they need to alert the main form and have it update.
I'm currently doing this through Events on the objects, and when each object is created, I add an EventHandler to maps the event back to the form. However, I'm running into some trouble that suggests that these requests aren't always ending up on the main copy of my form. For example, my form has a notification tray icon, but when the form captures and event and attempts to display a bubble, no bubble appears. However, if I modify that code to make the icon visible (though it already is), and then display the bubble, a second icon appears and displays the bubble properly.
Has anybody run into this before? Is there a way that I can force all of my events to be captured by the single instance of the form, or is there a completely different way to handle this? I can post code samples if necessary, but I'm thinking it's a common threading problem.
MORE INFORMATION: I'm currently using Me.InvokeRequired in the event handler on my form, and it always returns FALSE in this case. Also, the second tray icon created when I make it visible from this form doesn't have a context menu on it, whereas the "real" icon does - does that clue anybody in?
I'm going to pull my hair out! This can't be that hard!
SOLUTION: Thanks to nobugz for the clue, and it lead me to the code I'm now using (which works beautifully, though I can't help thinking there's a better way to do this). I added a private boolean variable to the form called "IsPrimary", and added the following code to the form constructor:
Public Sub New()
If My.Application.OpenForms(0).Equals(Me) Then
Me.IsFirstForm = True
End If
End Sub
Once this variable is set and the constructor finishes, it heads right to the event handler, and I deal with it this way (CAVEAT: Since the form I'm looking for is the primary form for the application, My.Application.OpenForms(0) gets what I need. If I was looking for the first instance of a non-startup form, I'd have to iterate through until I found it):
Public Sub EventHandler()
If Not IsFirstForm Then
Dim f As Form1 = My.Application.OpenForms(0)
f.EventHandler()
Me.Close()
ElseIf InvokeRequired Then
Me.Invoke(New HandlerDelegate(AddressOf EventHandler))
Else
' Do your event handling code '
End If
End Sub
First, it checks to see if it's running on the correct form - if it's not, then call the right form. Then it checks to see if the thread is correct, and calls the UI thread if it's not. Then it runs the event code. I don't like that it's potentially three calls, but I can't think of another way to do it. It seems to work well, though it's a little cumbersome. If anybody has a better way to do it, I'd love to hear it!
Again, thanks for all the help - this was going to drive me nuts!
I think it is a threading problem too. Are you using Control.Invoke() in your event handler? .NET usually catches violations when you debug the app but there are cases it can't. NotifyIcon is one of them, there is no window handle to check thread affinity.
Edit after OP changed question:
A classic VB.NET trap is to reference a Form instance by its type name. Like Form1.NotifyIcon1.Something. That doesn't work as expected when you use threading. It will create a new instance of the Form1 class, not use the existing instance. That instance isn't visible (Show() was never called) and is otherwise dead as a doornail since it is running on thread that doesn't pump a message loop. Seeing a second icon appear is a dead give-away. So is getting InvokeRequired = False when you know you are using it from a thread.
You must use a reference to the existing form instance. If that is hard to come by (you usually pass "Me" as an argument to the class constructor), you can use Application.OpenForms:
Dim main As Form1 = CType(Application.OpenForms(0), Form1)
if (main.InvokeRequired)
' etc...
Use Control.InvokeRequired to determine if you're on the proper thread, then use Control.Invoke if you're not.
You should look at the documentation for the Invoke method on the Form. It will allow you to make the code that updates the form run on the thread that owns the form, (which it must do, Windows forms are not thread safe).
Something like
Private Delegate Sub UpdateStatusDelegate(ByVal newStatus as String)
Public sub UpdateStatus(ByVal newStatus as String)
If Me.InvokeRequired Then
Dim d As New UpdateStatusDelegate(AddressOf UpdateStatus)
Me.Invoke(d,new Object() {newStatus})
Else
'Update the form status
End If
If you provide some sample code I would be happy to provide a more tailored example.
Edit after OP said they are using InvokeRequired.
Before calling InvokeRequired, check that the form handle has been created, there is a HandleCreated property I belive. InvokeRequired always returns false if the control doesn't currently have a handle, this would then mean the code is not thread safe even though you have done the right thing to make it so. Update your question when you find out. Some sample code would be helpful too.
in c# it looks like this:
private EventHandler StatusHandler = new EventHandler(eventHandlerCode)
void eventHandlerCode(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(StatusHandler, sender, e);
}
else
{
//do work
}
}