Getting Event Handler from Instance of Class - vb.net

I'm just getting started with custom classes so I wrote a button strip. It inherits a panel and populates it with buttons from strings passed to my .Add().
ButtonStrip1.Add("Button","Texts","Go Here")
I'd like for it to be able to naturally grab ButtonStrip1.Click's handler and pass it on to child buttons and delete it from ButtonStrip1.
I haven't been able to figure that out, so I've been doing
Public Class ButtonStrip
Inherits Panel
Public Property Innermargin As Integer = 5
Dim Offset As Integer = Innermargin
Dim Buttons = New List(Of ButtonStrip_Button)
Dim StoredFN As EventHandler
Public Sub New()
End Sub
Function Add(fn As EventHandler, ParamArray ByVal Values As String())
StoredFN = fn
For Each V In Values
Dim B As New ButtonStrip_Button
Buttons.Add(B)
Me.Controls.Add(B)
B.Text = V
B.Left = Offset + Innermargin
B.Top = Innermargin
Offset = B.Left + B.Width
AddHandler B.Click, fn
Next
RemoveHandler Me.Click, fn
Me.Width = Offset + Innermargin
Me.Height = Buttons(0).height + Innermargin * 2
End Function
Function Add(ParamArray ByVal Values As String())
If StoredFN Is Nothing Then
Throw New Exception("First Add() must supply a function")
End If
Me.Add(StoredFN, Values)
End Function
End Class
Public Class ButtonStrip_Button
Inherits System.Windows.Forms.Button
Public Sub New()
AutoSize = True
AutoSizeMode = AutoSizeMode.GrowAndShrink
End Sub
End Class
which is called by
ButtonStrip1.Add(AddressOf ButtonStrip1_Click,"Button","Texts","Go Here")
What I'd basically like to do is (psuedo-code)
Function Add(fn As EventHandler, ParamArray ByVal Values As String())
If StoredFN is Nothing Then StoredFN = Me.Click
...
AddHandler B.Click, Me.Click
Next
RemoveHandler Me.Click, Me.Click
...
End Function
I've tried changing a few things and googled a lot. I've also tried using CallByName(Me,"Click",CallType.Method) and with CallType.Get, but the error I get is Expression 'Click' is not a procedure, but occurs as the target of a procedure call. It also returns this exact same message for unhandled events, such as ButtonStrip1 has no MouseDown Event.
I've also tried using MyClass.
Not seen here is an alternative .Add() that add StoredFN to B.click
For instance, this click event works with my code
Private Sub ButtonStrip1_Click(sender As Object, e As EventArgs) Handles ButtonStrip1.Click
msgbox("You clicked " & sender.text & ".")
End Sub

What I was suggesting was something like this:
Public Class ButtonStrip
Inherits Panel
Public Event ButtonClick As EventHandler(Of ButtonClickEventArgs)
Protected Overridable Sub OnButtonClick(e As ButtonClickEventArgs)
RaiseEvent ButtonClick(Me, e)
End Sub
Private Sub Buttons_Click(sender As Object, e As EventArgs)
OnButtonClick(New ButtonClickEventArgs(DirectCast(sender, Button)))
End Sub
Public Sub Add(ParamArray values As String())
Dim btn As New Button
AddHandler btn.Click, AddressOf Buttons_Click
'...
End Sub
End Class
Public Class ButtonClickEventArgs
Inherits EventArgs
Public ReadOnly Property Button As Button
Public Sub New(button As Button)
Me.Button = button
End Sub
End Class
Now there's no need to pass event handlers around. When a Button is clicked, the ButtonStrip handles that event and then raises its own ButtonClick event. Neither the ButtonStrip nor the Buttons have to care about any methods that handle that event as it will be handled the same way any other event is. In a form, you'd then handle the ButtonClick event of the ButtonStrip and get the Button that was clicked from e.Button. You could also add an Index property if you wanted to know the position of the Button rather than the Button itself.

Related

ONMOUSEMOVE wont TRIGGER after selecting BUTTON (VB.NET)

Please help.
I have a form and a class.
Form - frmTestTool
Class - MainClass
What I am trying to do is to print text everytime the mouse cursor is moved. So the scenario is, I have a software where I embedded the custom command. So I open the custom command and the form will pop up, I need to select somewhere in the software before clicking the "PlaceText" button in the form. After clicking the "PlaceText" button it will implement btnPlaceText_Click_1 but will no longer trigger "OnMouseMove".
Scenario 1(WORKING WELL steps)
Select location in the software
Open Custom Command
Select Place Text
Move MouseCursor (prints "Hello Word" every mouse move)
Scenario 2(NOT WORKING steps)
Open Custom Command
Select location in the software
Select Place Text
Move Mouse Cursor (this time, OnMouseMove does not triggered)
Here's the Code
Partial Public Class frmTestTool
Inherits Form
Public Sub btnPlaceText_Click_1(sender As Object, e As EventArgs) Handles btnPlaceText.Click
WriteMessage("Hello World")
End Sub
End Class
Public Class TextWizard
Inherits BaseStepCommand
Private Shared ofrmTestTool As frmTestTool = New frmTestTool()
Public Overrides Sub OnSuspend()
MyBase.OnSuspend()
End Sub
Public Overrides Sub OnResume()
MyBase.OnResume()
End Sub
Public Overrides Sub OnStart(ByVal commandID As Integer, ByVal argument As Object)
MyBase.OnStart(commandID, argument)
Try
m_running = True
m_oTxnMgr = ClientServiceProvider.TransactionMgr
ofrmTestTool = New frmTestTool()
ofrmTestTool.Show()
Catch commonException As CmnException
ClientServiceProvider.ErrHandler.ReportError(ErrorHandler.ErrorLevel.Critical, MethodBase.GetCurrentMethod().Name, commonException, commandFailed)
End Try
End Sub
Protected Overrides Sub OnMouseDown(ByVal view As GraphicView, ByVal e As GraphicViewManager.GraphicViewEventArgs, ByVal position As Position)
MyBase.OnMouseDown(view, e, position)
ofrmTestTool.btnPlaceText_Click_1(Nothing, Nothing)
End Sub
End Class
I think you need to use RemoveHandler
RemoveHandler Me.MouseMove, AddressOf OnMouseMove
If you don't know the addresses of the handlers then you will need to use System.Reflection to find and remove them.
Sub RemoveEvents(Of T As Control)(Target As T, ByVal EventName As String)
Dim oFieldInfo As FieldInfo = GetType(Control).GetField(EventName, BindingFlags.[Static] Or BindingFlags.NonPublic)
Dim oEvent As Object = oFieldInfo.GetValue(Target)
Dim oPropertyInfo As PropertyInfo = GetType(T).GetProperty("Events", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim oEvenHandlerList As EventHandlerList = CType(oPropertyInfo.GetValue(Target, Nothing), EventHandlerList)
oEvenHandlerList.RemoveHandler(oEvent, oEvenHandlerList(oEvent))
End Sub
Call the Sub like this:
RemoveEvents(Me, "EventMouseMove")
' This can be used for event, just enter the string as "Event<EventName>" where the event name is the event you want to remove all handlers for on the target.

VB WindowsForm Custom Class Event Handler Issue

I created a custom class (DataGroupBoxControl) that is basically a GroupBox with one or more Panels inside.
Each Panel holds two Labels side by side as pictured below.
enter image description here
The object allows a DataTable to be passed into it, which is what determines how many rows of Panels are created and displayed.
I would like the user to be able to click on one of the Panel Labels and do 'something'.
Here is the procedure inside the class that creates the Labels.
Public Class DataGroupBoxControl
Inherits GroupBox
Public DataLabels As New List(Of DataLabelControl)
Public Sub BindData(ByVal ds As DataTable)
Dim colItem As Integer = 0
For Each col As DataColumn In ds.Columns
DataLabels.Add(New DataLabelControl)
For Each row As DataRow In ds.Rows
DataLabels(colItem).TitleLabel.Text = col.ColumnName & " " & _Separator
DataLabels(colItem).TitleLabel.Name = col.ColumnName
If IsNumeric(row.Item(colItem)) Then
If row.Item(colItem).ToString.IndexOf(".") <> -1 Then
DataLabels(colItem).ValueLabel.Text = Decimal.Parse(row.Item(colItem)).ToString("c")
Else
DataLabels(colItem).ValueLabel.Text = row.Item(colItem)
End If
End If
DataLabels(colItem).ValueLabel.Name = row.Item(colItem)
DataLabels(colItem).Top = (colItem * DataLabels(colItem).Height) + 20
DataLabels(colItem).Left = 5
Me.Controls.Add(DataLabels(colItem))
Me.Height = Me.Height + DataLabels(colItem).Height
DataLabels(colItem).Show()
Next
colItem += 1
Next
End Sub
Where and how do I create a Label Click Event Handler?
And then access that event from my main form with the following object:
Public AccountsGroupBox As New DataGroupBoxControl
Before creating DataLabelControl use AddHandler to attach event handler eg. to TitleLabel control, then you can listen for events and raise new event that can be handled in parent control.
Public Class SomeForm
Private WithEvents AccountsGroupBox As New DataGroupBoxControl
Private Sub AccountsGroupBox_ItemClick(
sender As Object,
args As ItemClickEventArgs) Handles AccountsGroupBox.ItemClick
End Sub
End Class
Public Class ItemClickEventArgs
Inherits EventArgs
Public Property Control As Object
End Class
Public Class DataGroupBoxControl
Inherits GroupBox
Public Event ItemClick(sender As Object, args As ItemClickEventArgs)
Public DataLabels As New List(Of DataLabelControl)
Private Sub OnLabelClick(sender As Object, args As EventArgs)
RaiseEvent ItemClick(Me, New ItemClickEventArgs With {.Control = object})
End Sub
Public Sub BindData(ByVal ds As DataTable)
For Each col As DataColumn In ds.Columns
Dim control = New DataLabelControl
AddHandler control.TitleLabel.Click, AddressOf OnLabelClick
DataLabels.Add(control)
' ...
Next
End Sub
End Class

Double click form event VB.NET

I'm using VB.NET and would like to know how I can double click a form to minimize it.
I have the following code but it does'nt work:
Private Sub openPROJECTS_MouseDoubleClick(ByVal sender As Object, ByVal e As MouseEventArgs) Handles Me.MouseDoubleClick
'Minimize the form
Me.WindowState = FormWindowState.Minimized
End Sub
I'm using VB.NET.
The problem is that every control has it's own double click event, so if you double click e.g. a panel on your form, your form's double click event won't get fired.
You could, however, add a MessageFilter, e.g.
Public Class Minimizer
Implements IMessageFilter
Private Const WM_LBUTTONDBLCLK As Integer = &H203
ReadOnly _form As Form
Public Sub New(f As Form)
_form = f
End Sub
Public Function PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
If m.Msg = WM_LBUTTONDBLCLK Then
_form.WindowState = FormWindowState.Minimized
End If
Return False
End Function
End Class
And a little test:
Private Sub Main()
Dim f = New Form()
f.Controls.Add(New Panel() With { .Dock = DockStyle.Fill })
Application.AddMessageFilter(New Minimizer(f))
f.ShowDialog()
End Sub
But I think it would be a strange user experience if double clicking anywhere minimizes the form...

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.

AddressOf with parameter

One way or another I need to link groupID (and one other integer) to the button I am dynamically adding.. any ideas?
What I can do;
AddHandler mybutton.Click, AddressOf PrintMessage
Private Sub PrintMessage(ByVal sender As System.Object, ByVal e As System.EventArgs)
MessageBox.Show("Dynamic event happened!")
End Sub
What I can't do, but want to;
AddHandler mybutton.Click, AddressOf PrintMessage(groupID)
Private Sub PrintMessage(ByVal groupID as Integer)
MessageBox.Show("Dynamic event happened!" & groupID .tostring)
End Sub
There is no way to do this with AddressOf itself. What you're looking for is a lambda expression.
AddHandler myButton.Click, Function(sender, e) PrintMessage(groupId)
Private Sub PrintMessage(ByVal groupID as Integer)
MessageBox.Show("Dynamic event happened!" & groupID .tostring)
End Sub
You can create your own button class and add anything you want to it
Public Class MyButton
Inherits Button
Private _groupID As Integer
Public Property GroupID() As Integer
Get
Return _groupID
End Get
Set(ByVal value As Integer)
_groupID = value
End Set
End Property
Private _anotherInteger As Integer
Public Property AnotherInteger() As Integer
Get
Return _anotherInteger
End Get
Set(ByVal value As Integer)
_anotherInteger = value
End Set
End Property
End Class
Since VB 2010 you can simply write
Public Class MyButton
Inherits Button
Public Property GroupID As Integer
Public Property AnotherInteger As Integer
End Class
You can access the button by casting the sender
Private Sub PrintMessage(ByVal sender As Object, ByVal e As EventArgs)
Dim btn = DirectCast(sender, MyButton)
MessageBox.Show( _
String.Format("GroupID = {0}, AnotherInteger = {1}", _
btn.GroupID, btn.AnotherInteger))
End Sub
These new properties can even be set in the properties window (under Misc).
The controls defined in the current project automatically appear in the toolbox.
Use the Tag property of the button.
Button1.Tag = someObject
AddressOf gets the address of a method, and thus you cannot pass parameters to it.
You can use delegate which very clear for your code follow as:
Define a delegate
Public Delegate Sub ControlClickDelegate(ByVal sender As Object, ByVal e As EventArgs)
Custom button class
Public Class CustomButton
Inherits System.Windows.Forms.Button
#Region "property delegate"
Private controlClickDelegate As ControlClickDelegate
Public Property ClickHandlerDelegate As ControlClickDelegate
Get
Return controlClickDelegate
End Get
Set(ByVal Value As ControlClickDelegate)
controlClickDelegate = Value
End Set
End Property
#End Region
Public Sub RegisterEventHandler()
AddHandler Me.Click, AddressOf OnClicking
End Sub
Private Sub OnClicking(ByVal sender As Object, e As System.EventArgs)
If (Me.controlClickDelegate IsNot Nothing) Then
Me.controlClickDelegate(sender, e)
End If
End Sub
End Class
MainForm
Public Class MainForm
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.CusButton1.ClickHandlerDelegate = AddressOf Me.btnClick
Me.CusButton1.RegisterEventHandler()
End Sub
Private Sub btnClick(ByVal sender As Object, ByVal e As EventArgs)
Me.TextBox1.Text = "Hello world"
End Sub
End Class
The below worked for me:
Dim bStart = New Button With {.Text = "START"}
AddHandler bStart.Click, Function(sender, e) TriggerProcess(any Long value)
Private Function TriggerProcess(ByVal paramName As Long) As Boolean
' any processing logic
Return True
End Function
My solution:
AddHandler menuItemYear.Items(i).MouseUp, Sub() menu_year(2019)
Private Sub menu_year(ByVal intYear As Integer)
'do something
End Sub
There are few ways to do that depending of the complexity and number of parameters required.
1. Use Tag for adding a complex structure
2. Inherit the the Button class and add the values as class members then populate them before using it. That gives you a lot more flexibility.
If you are using web version
3. You cannot add it to Tag, but for simple values assign it to index use .Attributes.Add("name"). This gets added to the HTML tags and not the Server side. You can then use the index to access a server side structure for complex systems.
4. Use sessions to store values and store the session reference to Name attribute as described above (#3).
No problem ;-)
For example:
Private ComboActionsOnValueChanged As New Dictionary(Of ComboBox, EventHandler)
'somewhere in function
dim del = Sub(theSender, eventArgs)
MsgBox(CType(theSender, ComboBox).Name & " test")
End Sub
ComboActionsOnValueChanged.Add(myCombo, del)
'somewhere else
Dim delTest = ComboActionsOnValueChanged(myCombo)
RemoveHandler myCombo.SelectedValueChanged, delTest
myCombo.DataSource = someDataSource
AddHandler myCombo.SelectedValueChanged, delTest
as we expect, event won't fire after DataSource change in this place