How to remove all event handlers from an event? - vb.net

I have the following class
Public Class SimpleClass
Public Event SimpleEvent()
Public Sub SimpleMethod()
RaiseEvent SimpleEvent()
End Sub
End Class
I instanciate it like
Obj = New SimpleClass
AddHandler Obj.SimpleEvent, Sub()
MsgBox("Hi !")
End Sub
And i'm trying to remove the event handler dynamically-created using the code in : Code Project
(I assume a complex application where it's difficult to use : RemoveHandler Obj.Event, AddressOf Me.EventHandler)
In their code there is the following method
Private Shared Sub BuildEventFields(t As Type, lst As List(Of FieldInfo))
For Each ei As EventInfo In t.GetEvents(AllBindings)
Dim dt As Type = ei.DeclaringType
Dim fi As FieldInfo = dt.GetField(ei.Name, AllBindings)
If fi IsNot Nothing Then
lst.Add(fi)
End If
Next
End Sub
But when calling this code using my object type, the next line returns nothing
Dim fi As FieldInfo = dt.GetField(ei.Name, AllBindings)
means that somehow my event is not recognized as a field.
Does anyone know how to remove all event handlers from an event ?
Cheers in advance.

It is the lambda expression that is getting you into trouble here. Don't dig a deeper hole, just use AddressOf and a private method instead so you can trivially use the RemoveHandler statement.
If you absolutely have to then keep in mind that the VB.NET compiler auto-generates a backing store field for the event with the same name as the event with "Event" appended. Which makes this code work:
Dim Obj = New SimpleClass
AddHandler Obj.SimpleEvent, Sub()
MsgBox("Hi !")
End Sub
Dim fi = GetType(SimpleClass).GetField("SimpleEventEvent", BindingFlags.NonPublic Or BindingFlags.Instance)
fi.SetValue(Obj, Nothing)
Obj.SimpleMethod() '' No message box
I'll reiterate that you should not do this.

Related

Passing Download complete Event with parameter to another class

I have an application whose main window upon click of a button gives users an option to load a list of files in the cloud.
Private Sub ImportCloudContent()
Dim cloudForm As Form_CloudImport
cloudForm = New Form_CloudImport()
cloudForm.Show()
cloudForm.populateDataGrid()
AddHandler cloudForm._DownloadComplete, New EventHandler(AddressOf OpenProject)
cloudForm.DownloadNotifier(FullPathOfContent)
End Sub
Ideally I should be able to get the value of the FullPathOfContent variable and pass it onto Open Project, but I am not sure how to go about it.
In the new Window users can click and download the file they want. Below is the section of code that handles the download in the Form_CloudImport class :
Private Async Sub Btn_download_Click(sender As Object, e As EventArgs) Handles Btn_download.Click
Dim fileNameRows As DataGridViewSelectedRowCollection = datagridview_cloudContent.SelectedRows
Dim fileName As String
Dim fileType As String = Cloud.CONTENT
Dim FullPathOfContent As String
For Each fileNameRow As DataGridViewRow In fileNameRows
fileName = fileNameRow.Cells(0).Value.ToString() & ".zip"
Try
FullPathOfContent = CloudToCCT(fileName, fileType)
Catch ex As Exception
CSMessageBox.ShowError("Content Import failed : ", ex)
End Try
Next
Me.Close()
DownloadNotifier(FullPathOfContent)
End Sub
Once the download is complete, the main window needs to call some of its methods. I am new to VB and have created a custom event to facilitate this(again in the Form_CloudImport class)
Public Event _DownloadComplete(e As String)
Public Sub DownloadNotifier(FullPathOfContent As String)
RaiseEvent _DownloadComplete(FullPathOfContent)
End Sub
According to what have read, once the download method is complete, it will fire the DownloadNotifier method, which will raise the _DownloadComplete event and the MainWindow should trigger the following events.
However, I receive the below errors in the MainWindow part of the code :
Value of type 'MainWindow.EventHandler' cannot be converted to 'Form_CloudImport._DownloadCompleteEventHandler'
and
'FullPathOfContent' is not declared. It may be inaccessible due to its protection level.
This question seems to be very long but any help would be appreciated. Thank you in advance!
First things first, you should create a type and event with proper names and signature and raise it properly.
Public Class CloudImportForm
Public Event DownloadComplete As EventHandler(Of DownloadCompleteEventArgs)
Protected Overridable Sub OnDownloadComplete(e As DownloadCompleteEventArgs)
RaiseEvent DownloadComplete(Me, e)
End Sub
'...
End Class
Public Class DownloadCompleteEventArgs
Inherits EventArgs
Public Sub New(contentPath As String)
Me.ContentPath = contentPath
End Sub
Public ReadOnly Property ContentPath As String
End Class
In that form, you would have code that performed a download and then raised that event.
'...
Dim contentPath = GetContentPath()
'Perform download here.
'Raise event.
OnDownloadComplete(New DownloadCompleteEventArgs(contentPath))
In your main form you would create and configure the download form, which includes handling the event, and then display it.
Dim cloudForm As New CloudImportForm
AddHandler cloudForm.DownloadComplete, AddressOf CloudImportForm_DownloadComplete
cloudForm.PopulateDataGrid()
cloudForm.Show()
The method you specify as the event handler should have the appropriate signature and it should retrieve the content path from the e parameter.
Private Sub CloudImportForm_DownloadComplete(sender As Object, e As DownloadCompleteEventArgs)
Dim contentPath = e.ContentPath
'Use contentPath here.
End Sub

VB.NET Add Any EventHandler Type with Delegate

I've found a few posts (not many) that I thought might solve my problem but in all my reading I still don't have a solution.
What I'm trying to do is basically create a method that will bind any given control's event to any given object's method using Reflection.EventInfo and Reflection.MethodInfo. I'm using Winforms, I'd love to just use WPF but that's not an option unfortunately.
I have an abstract BoundControl class that is just an empty canvas for inherited controls. In that class is the function below:
Public Sub CallMethod(eventName As String, sender As Object, e As EventArgs)
...
End Sub
This is what I want to be called whenever a given control's event is raised. The logic in that method calls the correct method on the data context (I've set this up to mimic WPF). This is working fine, my problem is actually binding the above method to a control's event.
I bind using the method below (in the same class as the method above). Note that I've removed the unimportant logic, stuff like my custom binding tag class and anything else unrelated to my problem:
Public Sub SetEventBind(ByRef ctrl as Control)
Dim ctrlStr As String = "EventName"
Dim ctrlEvent as Reflection.EventInfo = ctrl.GetType.GetEvent(ctrlStr)
Dim eh As EventHandler = (Sub(sender, e) CallMethod(ctrlStr, sender, e))
ctrlEvent.AddEventHandler(ctrl, eh)
End Sub
I'm trying to run my code on a LinkLabel for the LinkClicked event but I want this to work for any control's events. What ends up happening is EventHandler type cannot be converted to LinkLabelLinkClickedEventHandler. So just to test I tried the code below and it DID work:
Public Sub SetEventBind(ByRef ctrl as Control)
Dim ctrlStr As String = "EventName"
Dim ctrlEvent as Reflection.EventInfo = ctrl.GetType.GetEvent(ctrlStr)
Dim eh As LinkLabelLinkClickedEventHandler = (Sub(sender, e) CallMethod(ctrlStr, sender, e))
ctrlEvent.AddEventHandler(ctrl, eh)
End Sub
But the problem is LinkLabelLinkClickedEventHandler won't work for, say, a button click or a checkbox checked change. I also tried the code below and it didn't work:
Public Sub SetEventBind(ByRef ctrl as Control)
Dim ctrlStr As String = "EventName"
Dim ctrlEvent as Reflection.EventInfo = ctrl.GetType.GetEvent(ctrlStr)
Dim eh As [Delegate] = [Delegate].CreateDelegate(ctrlevent.EventHandlerType, Me, (Sub(sender, e) CallMethod(ctrlStr, sender, e)).Method)
ctrlEvent.AddEventHandler(ctrl, eh)
End Sub
I guess my question is multi-part. I think if I could dynamically create a delegate of the same type as ctrlEvent.EventHandlerType then I could get this working. Is it possible to dynamically set variable's type? If not, is there another way to dynamically bind any control's event to a method?
I found an article that was helpful (below). What I had to do was create a separate function that would convert and return a delegate to the correct delegate type.
Public Sub SetEventBind(ByRef ctrl As IBoundControl, pBindingTag As BindingTag, pDoAdd As Boolean)
If pBindingTag.BindingType <> BindType.EventBind Then Exit Sub
Dim objStr As String = pBindingTag.DataContextBindName
Dim ctrlStr As String = pBindingTag.ControlBindName
If Not (String.IsNullOrEmpty(objStr) OrElse String.IsNullOrEmpty(objStr)) Then
Dim ctrlEvent As Reflection.EventInfo = ctrl.GetType.GetEvent(ctrlStr)
If Not ctrlEvent Is Nothing Then
Dim eventDel As [Delegate] = Sub(sender, e)
CallMethod(ctrlStr, sender, e)
End Sub
Dim convertedDel = CastDelegate(eventDel, ctrlEvent.EventHandlerType)
ctrlEvent.RemoveEventHandler(ctrl, convertedDel)
If pDoAdd Then ctrlEvent.AddEventHandler(ctrl, convertedDel)
End If
End If
End Sub
Private Function CastDelegate(source As [Delegate], type As Type) As [Delegate]
Dim delegates As [Delegate]() = source.GetInvocationList()
Return [Delegate].CreateDelegate(type, delegates(0).Target, delegates(0).Method)
End Function
The article that helped can be found here:
Casting Delegates

C# to VB.net delegate sub

I'm continuing to try to learn VB.net, and I visit here often.
So, I'm back again, with a slightly different question. I received great help previously, and hope to avail myself again.
I have this CSharp code:
public delegate void MyHandler(string DataLine);
foo.Handler = new MyHandler(MySub);
void MySub(string DataLine);
(When I hover over "MyHandler", the VS helper says "MyHandler.MyHandler(void (string) target)"
I want to set up the delegate in VB.net, but I can't seem to relate the three items so they all work together:
Private Delegate Sub MyHandler(ByRef DataLine as string)
?????? as MyHandler
Private Sub MySub (ByRef DataLine as string)
Does the "??????" need to be a Dim {something}, or a Declare {something} or ??? Or is there an error in one of the other two lines?
(Let me know if there is any missing info)
Thanks
Charlie
In VB.Net, you can use AddressOf to assign a Sub to a Delegate:
foo.Handler = AddressOf MySub
Given this:
Private Delegate Sub MyHandler(ByRef DataLine as string)
Private Sub MySub(ByRef DataLine as string)
' Do something here
End Sub
You can declare and instantiate the delegate, like this:
Dim TheHandler As MyHandler
TheHandler = AddressOf MySub

How to Pass Additional Parameters When Calling and Event VB.net

Public Event DocumentCompleted As WebBrowserDocumentCompletedEventHandler
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted, New
WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
Private Sub DoStuff(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
End Sub
How can I pass the homeTeam and guestTeam when firing the DocumentCompleted event.
I want to ge the above to values to inside the Dostuff method.
Please help.
First of all, you cannot have this hanging in the middle of nowhere:
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted,
New WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
AddHandler probably needs to be in some Initialize method, which could be inside Sub New, after InitializeComponent, or inside Form_Load, or as soon as you expect it to be triggered (after a specific event). Notice here that you are using a default event of a native .NET component, with a default event type. In this case you cannot directly consume anything other than what it already provides, when triggered. See WebBrowser.DocumentCompleted Event on MSDN.
You can, however, override all relevant classes and have your own MyWebBrowser control and your own event, with would contain additional properties. See below example:
Public Class Form1
Sub New()
' This call is required by the designer.
InitializeComponent()
Dim browser As New MyWebBrowser
AddHandler browser.MyDocumentCompleted, AddressOf DoStuff
End Sub
Private Sub DoStuff(ByVal sender As Object, ByVal e As MyWebBrowserDocumentCompletedArgs)
Dim guestTeam As String = e.GuestTeam 'guest team
Dim homeTeam As String = e.HomeTeam 'and home team are both accessible
'so you can do some processing on them
End Sub
Public Class MyWebBrowserDocumentCompletedArgs : Inherits WebBrowserDocumentCompletedEventArgs
Dim _homeTeam As String
Dim _guestTeam As String
Public ReadOnly Property HomeTeam
Get
Return _homeTeam
End Get
End Property
Public ReadOnly Property GuestTeam
Get
Return _guestTeam
End Get
End Property
Sub New(url As Uri, homeTeam As String, guestTeam As String)
MyBase.New(url)
_homeTeam = homeTeam
_guestTeam = guestTeam
End Sub
End Class
Public Class MyWebBrowser : Inherits WebBrowser
Public Delegate Sub MyWebBrowserDocumentCompletedEventHandler(e As MyWebBrowserDocumentCompletedArgs)
Public Event MyDocumentCompleted As MyWebBrowserDocumentCompletedEventHandler
Protected Overrides Sub OnDocumentCompleted(e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
MyBase.OnDocumentCompleted(e)
'homeTeam and guestTeam need to be extracted from the current instance of MyWebBrowser, and passed further
RaiseEvent MyDocumentCompleted(New MyWebBrowserDocumentCompletedArgs(e.Url, "homeTeam", "guestTeam"))
End Sub
End Class
End Class
If your project is relatively small, you can indeed have those as global variables, as #Vlad suggested in the comments.

VB.NET Delegate doesn't work

In my application, I have a MainWindow with a ToolStripProgressBar and a ToolStripStatusLabel.
This properties:
Property ProgressBarPercantage() As Integer Implements BCSXPSearchTool.Presenter.IMainView.ProgressPercentage
Get
Return Me._progressbarpercentage
End Get
Set(ByVal value As Integer)
Me._progressbarpercentage = value
Me.StatusStripCurrentProgressBar.Value = Me._progressbarpercentage
End Set
End Property
Private _progressbarpercentage As Integer = 0
Property ProgressStatusText() As String Implements BCSXPSearchTool.Presenter.IMainView.ProgressStatusText
Get
Return Me._progressstatustext
End Get
Set(ByVal value As String)
Me._progressstatustext = value
Me.StatusStripCurrentState.Text = Me._progressstatustext
End Set
End Property
Private _progressstatustext As String = "Ready"
In the MainWindowPresenter I start a new BackgroundWorker which should read from a database.
Public Sub Search()
Dim bw As New BackgroundWorker
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf runproc
If bw.IsBusy = False Then
bw.RunWorkerAsync()
End If
End Sub
Public Sub runproc()
Dim statusToSub As delegateStatusTo = AddressOf statusTo
Dim percToSub As delegatePercTo = AddressOf percTo
statusToSub.Invoke("Test")
'percToSub.Invoke(50)
End Sub
Public Sub percTo(ByVal value As Integer)
_view.ProgressPercentage = value
End Sub
Public Sub statusTo(ByVal value As String)
_view.ProgressStatusText = value
End Sub
Delegate Sub delegateStatusTo(ByVal value As String)
Delegate Sub delegatePercTo(ByVal value As Integer)
The code above is working. But if I change the sub runproc() to:
Public Sub runproc()
Dim statusToSub As delegateStatusTo = AddressOf statusTo
Dim percToSub As delegatePercTo = AddressOf percTo
' statusToSub.Invoke("Test")
percToSub.Invoke(50)
End Sub
It doesn't work. I get an exception:
InvalidOperationException
I got the text in english and can't translate it to english very well but I think something like:
The access to the control, created by another thread from another thread is not allowed.
I'm using Visual Studio 2008 Express + VB 2.0.
Thank you!
This is due to cross-thread UI access which is disallowed (but for every UI access, so your other code shouldn’t work either!). The easiest solution is to use BeginInvoke when required:
Public Sub statusTo(ByVal value As String)
If InvokeRequired Then
BeginInvoke(New Action(Of String)(AddressOf statusTo))
Return
End If
_view.ProgressStatusText = value
End Sub
Furthermore, #vulkanino’s comment is spot-on: your calls should be direct method calls, not delegate invocations.
Dim statusToSub As **new** delegateStatusTo(AddressOf WriteToDebug)
statusToSub.Invoke("Test")
Dim percToSub As **new** delegatePercTo (AddressOf percTo)
percToSub.Invoke(50)
It looks like you are attempting to access UI controls from the DoWork event handler. Remember, that event handler is running on a worker thread. You are not allowed to touch any UI control from a thread other than the one that created it. There is a ProgressChanged event that will be marshaled onto the UI thread automatically upon calling ReportProgress. You safely update the UI from this event.