VB.NET Add Any EventHandler Type with Delegate - vb.net

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

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 Subclassing ComboBox - trying to create a SelectedIndexChanging event that can be cancelled

I'm currently trying to implement the second response from this thread How can I handle ComboBox selected index changing? in vb (the response that suggests subclassing ComboBox to introduce new SelectedIndexChangingEvent). The event handler
Private Sub MyComboBox1_SelectedIndexChanging(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles MyComboBox1.SelectedIndexChanging
MsgBox("Changing")
End Sub
never gets hit. I'm thinking it has something to do with the way I'm initializing the selectedIndexChanging (lowercase first letter) variable. Any thoughts?
Imports System.ComponentModel
Public Class MyComboBox
Inherits ComboBox
Public Event SelectedIndexChanging as CancelEventHandler
Public LastAcceptedSelectedIndex As Integer
Public Sub New()
LastAcceptedSelectedIndex = -1
End Sub
Protected Sub OnSelectedIndexChanging(e As CancelEventArgs)
Dim selectedIndexChanging as CancelEventHandler = SelectedIndexChanging
If Not SelectedIndexChanging Is Nothing Then
selectedIndexChanging(Me, e)
End If
End Sub
Protected Overrides Sub OnSelectedIndexChanged(e As EventArgs)
If LastAcceptedSelectedIndex <> SelectedIndex Then
Dim cancelEventArgs = New CancelEventArgs
OnSelectedIndexChanging(cancelEventArgs)
If Not cancelEventArgs.Cancel Then
LastAcceptedSelectedIndex = SelectedIndex
MyBase.OnSelectedIndexChanged(e)
Else
SelectedIndex = LastAcceptedSelectedIndex
End If
End If
End Sub
End Class
VB handles event declaration a bit different than C#. The VB RaiseEvent keyword effectively generates code you attempted to translate for the `OnSelectedIndexChanging' method.
The correct VB implementation would be:
Protected Sub OnSelectedIndexChanging(e As CancelEventArgs)
RaiseEvent SelectedIndexChanging(Me, e)
End Sub
You could follow the original pattern, by using the hidden variable VB creates that is the real CancelEventHandler variable. These hidden variables follow the naming pattern of eventNameEvent. So the real CancelEventHandler variable is named: SelectedIndexChangingEvent.
Protected Sub OnSelectedIndexChanging(e As CancelEventArgs)
Dim selectedIndexChanging As CancelEventHandler = Me.SelectedIndexChangingEvent
If Not selectedIndexChanging Is Nothing Then
selectedIndexChanging(Me, e)
End If
End Sub

Bind DataSource to new DevExpress Report Designer?

I'm trying to figure out how to set my DataSource as the default when a user clicks New Report, or for any new report, in the DevExpress User Data Report Designer.
Right now, the Blank Report I have load on Form_Load has my DataSources just fine, but anytime I hit New Report, they're gone.
I've googled and followed the docs, but they all seem to be geared towards opening a specific report (as above).
Can anyone help?
0. ICommandHandler interface
You need to handle the ReportCommand.NewReport command by implementing the ICommandHandler interface. You must pass an object that implementing this interface to the XRDesignMdiController.AddCommandHandler method. You can get XRDesignMdiController object from ReportDesignTool.DesignForm.DesignMdiController property or from ReportDesignTool.DesignRibbonForm.DesignMdiController property according to what type of form you want to use.
Here is example:
Private Sub ShowReportDesigner()
Dim tool As New ReportDesignTool(CreateReport)
Dim controller = tool.DesignRibbonForm.DesignMdiController
Dim handler As New NewCommandHandler(controller, AddressOf CreateReport)
controller.AddCommandHandler(handler)
tool.ShowRibbonDesigner()
End Sub
Private Function CreateReport() As XtraReport
Dim report As New XtraReport
report.DataSource = YourDataSourceObjectHere
Return report
End Function
Public Class NewCommandHandler
Implements ICommandHandler
Private ReadOnly _controller As XRDesignMdiController
Private ReadOnly _createReport As Func(Of XtraReport)
Public Sub New(controller As XRDesignMdiController, createReport As Func(Of XtraReport))
_controller = controller
_createReport = createReport
End Sub
Public Function CanHandleCommand(command As ReportCommand, ByRef useNextHandler As Boolean) As Boolean Implements ICommandHandler.CanHandleCommand
useNextHandler = command <> ReportCommand.NewReport
Return Not useNextHandler
End Function
Public Sub HandleCommand(command As ReportCommand, args() As Object) Implements ICommandHandler.HandleCommand
_controller.OpenReport(_createReport())
End Sub
End Class
1. DesignPanelLoaded event
The another way is to subscribe to XRDesignMdiController.DesignPanelLoaded event. In this event you can check where the DataSource of report in loaded panel is empty and set it to your data source.
Here is example:
Private Sub ShowReportDesigner()
Dim report As New XtraReport
report.DataSource = YourDataSourceObjectHere
Dim tool As New ReportDesignTool(New XtraReport)
Dim controller = tool.DesignRibbonForm.DesignMdiController
AddHandler controller.DesignPanelLoaded, AddressOf mdiController_DesignPanelLoaded
tool.ShowRibbonDesigner()
End Sub
Private Sub mdiController_DesignPanelLoaded(ByVal sender As Object, ByVal e As DesignerLoadedEventArgs)
Dim panel = DirectCast(sender, XRDesignPanel)
Dim report = panel.Report
If IsNothing(report.DataSource) Then
report.DataSource = YourDataSourceObjectHere
End If
End Sub

How to remove all event handlers from an event?

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.

how to know when a work in a thread is complete?

I need to create multiple threads when a button is clicked and i've done that with this:
Dim myThread As New Threading.Thread(AddressOf getFile)
myThread.IsBackground = True
myThread.Start()
but i need to update a picture box with the downloaded file, buy if i set an event in the function getFile and raise it to notify that the files was downloaded and then update the picturebox.
Use an AsyncResult, and either check it periodically for completion, or provide a delegate to be called when the thread has completed its work.
A complete example in VB can be found here.
You need to make use of MethodInvoker deligate.
Public Sub GetFile()
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(GetFile))
End If
End Sub
Now you can handle any event in your specified class.
You can achive that using the Asyncallback, ...
Dim sinctotal As New Del_sinc(AddressOf sincronizar)
Dim ar As IAsyncResult = sinctotal.BeginInvoke(_funcion, type, New AsyncCallback(AddressOf SincEnd), cookieobj)
The cookieobj is this
Class Cookie
Public id As String
Public AsyncDelegate As [Delegate]
Sub New(ByVal id As String, ByVal asyncDelegate As [Delegate])
Me.id = id
Me.AsyncDelegate = asyncDelegate
End Sub
End Class
When the delegate finish it will call the funcion Sincend (in this example), then you could use a event to update your picture.
Hope this helps!