Raise Events Custom Control Class which Inherits Base Class - vb.net

I'm trying to design a "pass-through" for the axShockwaveFlash control. This control does not have .MouseDown, .MouseUp or .MouseMove events associated with it. Sadly, I need to detect these events on any shockwave controls as they are used in another class (which detects these events on a variety of different controls).
So, I tried designing a custom class, modifying the axShockwaveFlash class to include these events. However, I get the following error:
"Derived classes cannot raise base class events."
on the line:
RaiseEvent MouseDown(Me, EventArgs.Empty)
Full code (minus the up and move functions):
Imports System.Runtime.InteropServices
Imports System.Threading
Public Class MousedFlash
Inherits AxShockwaveFlashObjects.AxShockwaveFlash
'WndProc Inputs:
Private Const WM_RBUTTONDOWN As Integer = &H204
Private Const WM_LBUTTONDOWN As Integer = &H201
Protected Overloads Sub OnMouseDown()
RaiseEvent MouseDown(Me, EventArgs.Empty)
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Select Case m.Msg
Case WM_RBUTTONDOWN
Debug.WriteLine("MOUSE FLASH - right down!")
Call OnMouseDown()
Return
Case WM_LBUTTONDOWN
Debug.WriteLine("MOUSE FLASH - left down!")
Call OnMouseDown()
Return
End Select
MyBase.WndProc(m)
End Sub
End Class
I think the Base Class is AxShockwaveFlashObjects - but I can't see the mouse events defined in here. Bit out my depth on this one - any help appreciated.

Having a look at this page it looks like instead of trying to raise the base class event, you should call the Sub which handles that event directly. So try replacing
Protected Overloads Sub OnMouseDown()
RaiseEvent MouseDown(Me, EventArgs.Empty)
End Sub
with
Protected Overloads Sub OnMouseDown()
MyBase.MouseDown(Me, EventArgs.Empty)
End Sub

Thanks. In the end a mixture of carelessness on my part and the wrong type of over-ride. Final working replacement:
Shadows Event MouseDown As System.EventHandler
Protected Overridable Sub OnMouseDownTrigger()
RaiseEvent MouseDown(Me, EventArgs.Empty)
End Sub

Related

Listbox breaks when setting it to a virtual instance from a class

I have a weird problem that I can't wrap my head around.
I have the following code:
Public Class Form1
Public WithEvents MyClass1 As New MyClass
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
End Sub
Private Sub MyClass_UpdateListbox() Handles MyClass1.UpdateListbox
For Each sItem as String In MyClass1.Listbox
MsgBox(sItem) 'an MsgBox shows correct items each time.
Next sItem
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
Me.Listbox.Items.Add("event triggered") 'does nothing.
End Sub
End Class
Public Class MyClass
Public Listbox as new Listbox
Public Event UpdateListbox()
Public Sub New()
'Constructor. sub.
Me.AddItem("Populating listbox")
End Sub
Public Sub AddItem(sItem as String)
Me.Listbox.Items.Add(sItem)
RaiseEvent UpdateListbox()
End Sub
End Class
If I comment the following lines in above code, the listbox keeps adding event triggered, as expected. Of course, I don't have to remove the clear one. It will work, but then it just adds the same item. If I use a command button and call MyClass.AddItem("Something") that is correctly added too as long as the below is commented out. But if not, then once the listbox is in broken state, nothing can be added anymore.
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
How can I use a virtual listbox and assign it to my real listbox?
Also, instead of assigning one listbox to the other, I can of course use that for each loop and add each item one by one which works, but that for each look was for debugging purpose in the first place.
EDIT:
My goal with this application is to build a Todo list with features that are not in a todolist. This is a project I build for work because there I need a tool like this. I already have a todolist that I use but I built it wrong in the past. Everything was condensed in form1, no modules no extra classes. As a result I got weird bugs that I patched with workarounds. I am now rebuilding the application from the ground up, separating tasks in its own classes so I can apply business logic and have a true OOP application. The todo list will become its own class, and managing the list etc will be handeled by this class. It interacts with controls on the form, such as buttons and listboxes. If I just use form1.listbox from the class, things break at program start. I started another question and the below code was a now deleted answer. At first I did not get it working because I did not realize the listbox crashes if I assign it the virtual instance.
So my goal is to have the todolist be handled entirely by the todolist class. It does need a way to interact with controls on form1, and that is the puzzle I'm currently trying to solve.
In the original code, the main problem is that the Field that hold the instance of a Control shown if a Form is reassigned to the instance of another ListBox Control defined in a custom class:
Me.Listbox = Me.MyClass1.Listbox
From now on, Me.Listbox points another ListBox that is not show on screen, so any attempt to update the Form's child ListBox fails, except when Me.Listbox.Items.Clear() is called - in the same procedure - after it's being reassigned, because the handle of the Owner of the ObjectCollection (the object that holds the Items shown in the ListBox) has not been updated yet. It's going to fail after the current method exits nonetheless.
As noted in comments, this is a simplified method to handle a Form and its child Controls using a handler class. The contract between the class handler and a Form is sealed by an Interface (named IFormHandler here).
A Form that implements this Interface exposes the methods defined by the Interface that allow to trigger Actions and specific behaviors, depending on the Type of Control and the implementation.
I suggest to take a look at the MVP or ReactiveUI (MVVM-derived) for WinForms Patterns.
How too proceed:
Open up the ApplicationEvents class object.
If you don't have it already, select Project -> Properties -> Application and click the View Application Events button. It will generate ApplicationEvents.vb. Find it in Solution Explorer and open it up.
It should look like this (plus a bunch of comments that explain what it's for):
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
End Class
End Namespace
Paste into MyApplication these lines of code:
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
Public SomeFormHandler As MyFormHandler(Of SomeForm)
Protected Overrides Function OnStartup(e As StartupEventArgs) As Boolean
SomeFormHandler = New MyFormHandler(Of SomeForm)
Return MyBase.OnStartup(e)
End Function
End Class
End Namespace
Add an Interface that defines the Actions (or Behaviors) that a Form must implement.
Here, the GetUsersList() method specifies that a Form that implements this Interface must return the instance of a child ListBox Control.
(To add an Interface, select Project -> Add -> New Item... and select the Interface template. Name the file IFormHandler)
Extend this Interface as needed, to add more Methods or Properties that define actions and behaviors.
Public Interface IFormHandler
Function GetUsersList() As ListBox
End Interface
A Form that implements the IFormHandler Interface implements and exposes the GetUsersList() method, which returns the instance of a ListBox Control (named usersList here)
There's nothing else to do with this Form, the control is handed over to the MyFormHandler object that is initialized with this Type.
Public Class SomeForm
Implements IFormHandler
Public Sub New()
InitializeComponent()
End Sub
Public Function GetUsersList() As ListBox Implements IFormHandler.GetUsersList
Return Me.usersList
End Function
End Class
Now, to show SomeForm, you can use the MyFormHandler class object show below.
' Set the Owner if called from another Form
My.Application.SomeFormHandler.Show(Me)
' Or without an Owner
My.Application.SomeFormHandler.Show()
To close SomeForm, you can either use its handler:
My.Application.SomeFormHandler.Close()
or close it as usual:
[SomeForm Instance].Close()
If MyFormHandler determines that the instance of SomeForm has been disposed, it creates a new one when you call its Show() method again later.
To update the ListBox Control of SomeForm, use the public methods exposed by the MyFormHandler class:
' Add a new element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.AddElement, "Some Item")
' Remove an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.RemoveElement, "Some Item")
' Replace an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.ReplaceElement, "New Item", "Some Item")
' Clears the ListBox
My.Application.SomeFormHandler.ClearUsersList()
All these actions generate an event that you can subscribe to when needed.
See also the example that shows how to raise a custom event when the ListBox raises one of its stardard events; SelectedIndexChanged is handled here.
See the implementation of MyFormHandler.
Generic Form handler:
A Form needs to implement the IFormHandler Interface for the MyFormHandler class to accept it as valid.
You can of course extend the Interface, to add more Actions, or build a MyFormHandler class object that uses a different Interface, or more than one.
Public Class MyFormHandler(Of TForm As {Form, IFormHandler, New})
Implements IDisposable
Private formObject As TForm
Private IsInstanceSelfClosing As Boolean = False
Public Event UsersListUpdate(item As Object, changeType As UpdateType)
Public Event UsersListIndexChanged(index As Integer)
Public Sub New()
InitializeInstance()
Dim lstBox = formObject.GetUsersList()
AddHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
End Sub
Private Sub InitializeInstance()
formObject = New TForm()
AddHandler formObject.FormClosing, AddressOf OnFormClosing
End Sub
Private Sub OnFormClosing(sender As Object, e As FormClosingEventArgs)
IsInstanceSelfClosing = True
Dispose()
End Sub
Public Sub UpdateUsersList(updateMode As UpdateType, newItem As Object, Optional oldItem As Object = Nothing)
If newItem Is Nothing Then Throw New ArgumentException("New Item is null")
Dim lstBox = formObject.GetUsersList()
Select Case updateMode
Case UpdateType.AddElement
lstBox.Items.Add(newItem)
Case UpdateType.RemoveElement
lstBox.Items.Remove(newItem)
Case UpdateType.ReplaceElement
If oldItem Is Nothing Then Throw New ArgumentException("Replacement Item is null")
Dim index = lstBox.Items.IndexOf(oldItem)
lstBox.Items.Remove(oldItem)
lstBox.Items.Insert(index, newItem)
Case Else : Return
End Select
RaiseEvent UsersListUpdate(newItem, updateMode)
End Sub
Public Sub ClearUsersList()
formObject.GetUsersList().Items.Clear()
End Sub
Private Sub OnUsersListIndexChanged(sender As Object, e As EventArgs)
RaiseEvent UsersListIndexChanged(DirectCast(sender, ListBox).SelectedIndex)
End Sub
Public Sub Show(Optional owner As IWin32Window = Nothing)
If formObject Is Nothing OrElse formObject.IsDisposed Then InitializeInstance()
If formObject.Visible Then
formObject.WindowState = FormWindowState.Normal
formObject.BringToFront()
Else
formObject.Show(owner)
End If
End Sub
Public Sub Close()
If formObject IsNot Nothing AndAlso (Not formObject.IsDisposed) Then
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
IsInstanceSelfClosing = False
Dispose()
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If disposing Then
If formObject Is Nothing OrElse formObject.IsDisposed Then Return
Dim lstBox = formObject.GetUsersList()
RemoveHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
If Not IsInstanceSelfClosing Then formObject.Close()
IsInstanceSelfClosing = False
End If
End Sub
End Class
Enumerator used in MyFormHandler:
Public Enum UpdateType
AddElement
RemoveElement
ReplaceElement
End Enum

Remove Line Under ToolStrip VB.Net

I have added a ToolStrip to a form which is going to be used to add menus and set the background colour to match the forms background colour but it always displays a horizontal line under the ToolStrip which I find distracting.
My workaround so far is to use the StatusStrip and add dropdown buttons but ideally I would have liked to have used the ToolStrip as I believe this is the preferred tool for adding menus
Having researched this, I think it has something to do with the Render Property and I have read where it's been mentioned about creating an override.
Can anyone show me an example on how to achieve this in VB.Net please.
This is simply the VB.Net version of the code provided in this previous SO question.
Obviously, the line will be there at design-time on your form, but would be gone at run-time:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ToolStrip1.Renderer = New ToolStripRenderer
End Sub
Public Class ToolStripRenderer
Inherits ToolStripProfessionalRenderer
Public Sub New()
MyBase.New()
End Sub
Protected Overrides Sub OnRenderToolStripBorder(e As ToolStripRenderEventArgs)
If Not (TypeOf e.ToolStrip Is ToolStrip) Then
MyBase.OnRenderToolStripBorder(e)
End If
End Sub
End Class
End Class
An alternative would be to create a whole new class that inherits from ToolStrip and creates the renderer for you. Then the line would be gone at design-time as well. The new control would appear at the top of your ToolBox after you compile. Unfortunately, this means you'd have to delete the old ToolStrip and drag a new one (your version) onto the form and reconfigure it:
Public Class MyToolStrip
Inherits ToolStrip
Public Sub New()
MyBase.New
Me.Renderer = New ToolStripRenderer
End Sub
Public Class ToolStripRenderer
Inherits ToolStripProfessionalRenderer
Public Sub New()
MyBase.New()
End Sub
Protected Overrides Sub OnRenderToolStripBorder(e As ToolStripRenderEventArgs)
If Not (TypeOf e.ToolStrip Is ToolStrip) Then
MyBase.OnRenderToolStripBorder(e)
End If
End Sub
End Class
End Class
Thank you for explaining how to do that. I went with the second option as this seemed more convenient for what I wanted and I presume I can save that Class and reuse it on further projects.
I still need to learn the Class and explore what and how they can be used.
Public Class MyToolStrip
Inherits ToolStrip
Public Sub New()
MyBase.New
Me.Renderer = New ToolStripRenderer
End Sub
Public Class ToolStripRenderer
Inherits ToolStripProfessionalRenderer
Public Sub New()
MyBase.New()
End Sub
Protected Overrides Sub OnRenderToolStripBorder(e As ToolStripRenderEventArgs)
If Not (TypeOf e.ToolStrip Is ToolStrip) Then
MyBase.OnRenderToolStripBorder(e)
End If
End Sub
End Class
End Class
Here is a screenshot of what I am referring to
I have made the Toolstrip the same colour as the Panel I have put it into.
Underneath the Toolstrip is a white line which I find distracting and would like to be able to remove it.

Listview MouseLeave event: the Header is not included

I've subscribed to the MouseLeave event of my ListView. The event should be raised when the Mouse Pointer leaves the ListView bounds.
That works, but when the Mouse Pointer enters the ListView's Header and then leaves the ListView bounds, the event is not be raised.
Private Sub LV1_test_MouseLeave(sender As Object, e As EventArgs) Handles LV1_test.MouseLeave
// Not raised when the Pointer leaves the premises from the top of the ListView
End Sub
What can I do?
The ListView Header is actually a different object, its class name is SysHeader32.
The Header is shown in Details View, but it's created along with the ListView, so it's there even if you cannot see it (if you have added at least one Column, that is).
It's not a managed child Control of the ListView: the ListView.Controls collection is usually empty.
But it's a child control of the SysListView32 native control from which the managed class derives, thus, you can get its handle and read its messages; the WM_MOUSELEAVE message, in this case.
We can get its handle using FindWinDowEx or SendMessage (with LVM_GETHEADER), assign the handle to a NativeWindow class, override its WndProc and intercept the messages we need to handle. On WM_MOUSELEAVE, the NativeWindow class raises an event that the parent ListView can subscribes to, raising its own MouseLeave event as a result.
Since, as described, the Header is a distinct object, the ListView generates a MouseLeave event when the mouse pointer is moved over its Header. We need to override this behavior, so the MouseLeave event is only raised when the mouse Pointer leaves the ListView bounds completely.
We can override OnMouseLeave, verify whether the position returned by MousePosition (translated to client measures) falls within the ListView client bounds and let the method raise the MouseLeave event only when it doesn't.
EDIT:
Added WM_PARENTNOTIFY message check (for the WM_CREATE event notification) to handle the Header creation at run-time.
Custom ListView Control:
Now, if you subscribe to the MouseLeave event of this Custom Control, the event is raised only when the Mouse Pointer leaves the Client Area of the ListView, no matter where the Cursor is located.
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
<DesignerCategory("Code")>
Class ListViewCustom
Inherits ListView
Private Const LVM_GETHEADER As Integer = &H1000 + 31
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function SendMessage(hWnd As IntPtr, uMsg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function
Private sysHeader As SysHeader32 = Nothing
Private Sub AddSysHeaderHandler()
If DesignMode Then Return
If sysHeader Is Nothing Then
Dim sysHeaderHwnd = SendMessage(Me.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero)
If sysHeaderHwnd <> IntPtr.Zero Then
sysHeader = New SysHeader32(sysHeaderHwnd)
AddHandler sysHeader.SysHeaderMouseLeave,
Sub(s, evt)
Me.OnMouseLeave(evt)
End Sub
End If
End If
End Sub
Protected Overrides Sub OnHandleCreated(e As EventArgs)
MyBase.OnHandleCreated(e)
AddSysHeaderHandler()
End Sub
Protected Overrides Sub OnMouseLeave(e As EventArgs)
If Not Me.ClientRectangle.Contains(PointToClient(MousePosition)) Then
MyBase.OnMouseLeave(e)
End If
End Sub
' Handles the Header creation at run-time
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case &H210 'WM_PARENTNOTIFY
Dim msg As Integer = m.WParam.ToInt32() And &HFFFF
Select Case msg
Case &H1 ' WM_CREATE
AddSysHeaderHandler()
End Select
End Select
MyBase.WndProc(m)
End Sub
Protected Overrides Sub Dispose(disposing As Boolean)
If (disposing) Then sysHeader?.ReleaseHandle()
MyBase.Dispose(disposing)
End Sub
Private Class SysHeader32
Inherits NativeWindow
Public Event SysHeaderMouseLeave As EventHandler(Of EventArgs)
Public Sub New(handle As IntPtr)
AssignHandle(handle)
End Sub
Protected Friend Overridable Sub OnSysHeaderMouseLeave(e As EventArgs)
RaiseEvent SysHeaderMouseLeave(Me, e)
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case &H2A3 'WM_MOUSELEAVE
OnSysHeaderMouseLeave(EventArgs.Empty)
m.Result = IntPtr.Zero
Exit Select
Case Else
' NOP: Log other messages, add more cases...
End Select
MyBase.WndProc(m)
End Sub
End Class
End Class

Best Practices - Form Class Receiving Message from Class Modules

Hoping to get some best-practice advise with regards to capturing a returned message from an instantiated class on my form.
In my form (form1.vb), I have a label which reflects what is being done, with the code below.
Code in form1.vb to display message:
Public Sub DisplayMessage(ByVal Msg as String, ByVal Show as Boolean)
Application.DoEvents()
If Show Then
lblShow.Text = Msg
lblShow.Refresh()
End If
End Sub
I have came across three methods so far:
Direct Form Call. In this scenario the class directly calls the form's message routine:
form1.DisplayMessage("Show This Message", True)
RaiseEvent within class. In this scenario form1 is Friends WithEvents of the class sending the message, and the class raises the event to the form.
**Declared in Form1.vb**
Friend WithEvents Class1 as New Class1
**Declared in Class1.vb**
Public Event SetMessage(ByVal Msg As String, ByVal Show As Boolean)
**Used in Class1.vb**
RaiseEvent SetMessage("Show This Message", True)
Have an EventArgs class handle the event. In this scenario we have an EventArg.vb class which is instantiated whenever we raise the event.
**Declared in Form1.vb**
Friend WithEvents Class1 as New Class1
Private Sub class1_DisplayMessage(ByVal Msg As String, ByVal showAs Boolean, ByRef e As ProgressMessageEventArgs) Handles Class1.SetMessage
DisplayMessage(Msg, Show)
End Sub
**Declared in Class1.vb**
Public Event SetMessage(ByVal msg As String, ByVal Show As Boolean, ByRef e As ProgressMessageEventArgs)
Protected Sub CaptureMessage(ByVal msg As String, ByVal Show As Boolean)
RaiseEvent SetMessage(message, ShowList, New ProgressMessageEventArgs(message))
End Sub
**Used in Class1.vb**
RaiseEvent CaptureMessage("Show This Message", True)
**EventArg.vb created to handle ProgressMessageEventArgs class**
Public NotInheritable Class ProgressMessageEventArgs
Inherits System.EventArgs
Public txt As String
Public Sub New(ByVal txt As String)
MyBase.New()
Me.Text = txt
End Sub
End Class
Scenario 1 is seemingly the simplest, though I was advised against this and asked to raise an event instead. Over time I came across scenario 3 which involves an additional class vs scenario 2.
Therefore, the question is...
Between these three methods, which would be the "proper" way of returning a message from a class to the form? Is the additional EventArg class as per scenario 3 necessary since scenario 2 works fine as well?
Many thanks in advance.
My answer is none of the above. Consider this example
Public Class Form1
Private WithEvents myClass1 As New Class1()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
myClass1.CountTo1000()
End Sub
Private Sub MyClass1_Updated(number As Integer) Handles myClass1.Updated
Me.Label1.Text = number.ToString()
End Sub
End Class
Public Class Class1
Public Event Updated(number As Integer)
Public Sub CountTo1000()
For i = 1 To 1000
System.Threading.Thread.Sleep(1)
RaiseEvent Updated(i)
Next
End Sub
End Class
You have a form and a class, and the form has a reference to the class (the class doesn't even know the form exists). Your business logic is performed in the class, and the form is used to input and display information. CountTo1000() is being called directly from the form, which is bad because basically the UI thread is being put to sleep 1000 times, while the class is trying to update the UI by raising the event after each sleep. But the UI never has time to allow the events to happen, i.e. to be updated. Placing an Application.DoEvents() after Me.Label1.Text = number.ToString() will allow the UI to update. But this is a symptom of bad design. Don't do that.
Here is another example with multi-threading
Public Class Form1
Private WithEvents myClass1 As New Class1()
' this handler runs on UI thread
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' make a new thread which executes CountTo1000
Dim t As New System.Threading.Thread(AddressOf myClass1.CountTo1000)
' thread goes off to do its own thing while the UI thread continues
t.Start()
End Sub
' handle the event
Private Sub MyClass1_Updated(number As Integer) Handles myClass1.Updated
updateLabel(number.ToString())
End Sub
' invoke on UI thread if required
Private Sub updateLabel(message As String)
If Me.Label1.InvokeRequired Then
Me.Label1.Invoke(New Action(Of String)(AddressOf updateLabel), message)
Else
Me.Label1.Text = message
End If
End Sub
End Class
Public Class Class1
Public Event Updated(number As Integer)
Public Sub CountTo1000()
For i = 1 To 1000
System.Threading.Thread.Sleep(1)
RaiseEvent Updated(i)
Next
End Sub
End Class
This simple example shows how a thread can be created and run some code off the UI. When doing this, any method call from the non-UI thread must be invoked on the UI if it must access a UI control (Label1). The program runs smoothly since the Thread.Sleep is done on a different thread than the UI thread, with no need for Application.DoEvents, because the UI thread is otherwise doing nothing, and can handle the events being raised by the other thread.
I focused more on threading, but in both examples the design has a form with a class, and the form knows about the class, but the class doesn't know about the form. More about that can be seen here.
See also:
Why we need to check for InvokeRequired, then invoke: Control.InvokeRequired
A better option than Thread nowadays: BackgroundWorker
An even cooler option, if you can wrap your head around it: Async/Await

VB.Net Inheritance Override

I'm new to VB.Net (I'm from a foxpro background) and have had my head in a book for the last two weeks trying to get started with some of the basics.
I'm trying to master class inheritance and have what I hope is not too much of a challenging question.
I've created a class and compiled it as a DLL. It simply allows me to place a button on a form. I just want to capture the Click event - which I've managed to do but would like to override the inherited code rather than having both fire which seems to be happening at the moment.
I realise I could just double click the control and enter code directly into the MyButton1 click event but wanted to trap this programmatically instead via the handler.
I thought this would just be a case of using the overridable / overrides options.
Here's the code in my class:
Imports System.Windows.Forms
Imports System.Drawing
Public Class MyButton
Inherits Windows.Forms.Button
Sub New()
End Sub
Protected Overridable Sub MyButton_Click(sender As Object, e As System.EventArgs) Handles Me.Click
MsgBox("Base Click")
End Sub
End Class
Then I place the button on my form and name it MyButton1 and in the load event:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler MyButton1.Click, AddressOf Button_Click
End Sub
Private Sub Button_Click()
MsgBox("Actual Click")
End Sub
Problem is, both events fire and I want the option to override / turn off the base event.
I thought I could just add the 'overrides' keyword to the Button_Click routine i.e.:
Private Sub Overrides Button_Click()
but I get an error message Sub Button_Click() cannot be declared 'overrides' because it does not override a sub in a base class
So to clarify - at the moment my code fires both events so I get two messages. I want to be able to turn off / supress the base class event.
Any help would be much appreciated.
I thought this would just be a case of using the overridable /
overrides options.
The fundamental problem here is that you're trying to push a square peg into a round hole.
To override something, you need to have inheritance involved. The derived class is overriding something that was inherited from the base class. For instance, if you inherited from your MyButton class to create a new type of Button called MyButtonDerived, then you could do it as expected:
Public Class MyButton
Inherits Windows.Forms.Button
Protected Overridable Sub MyButton_Click(sender As Object, e As System.EventArgs) Handles Me.Click
MsgBox("Base Click")
End Sub
End Class
Public Class MyButtonDerived
Inherits MyButton
Protected Overrides Sub MyButton_Click(sender As Object, e As EventArgs)
' We don't call the base method...
' MyBase.MyButton_Click(sender, e)
' ... and instead do something else:
MsgBox("Derived Click")
End Sub
End Class
In contrast, when you've placed MyButton onto the Form as in your original problem description, no inheritance has taken place. Instead what you've setup is "object composition"; the form contains an instance of the button (not derived from it). While it may be possible to change what happens when the button is clicked from the form itself, this is not a case that can be solved with OOP, inheritance and overriding.
If MyButton was not designed in such a way that allows the end user to suppress its base functionality, then your options are limited in how you can use it. Here is an example of what it might look like if MyButton was designed to allow the end user to suppress its base click functionality:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
MyButton1.SuppressDefaultClick = True
End Sub
Private Sub MyButton1_Click(sender As Object, e As EventArgs) Handles MyButton1.Click
MsgBox("Form Click Code")
End Sub
End Class
Public Class MyButton
Inherits Windows.Forms.Button
Private _suppress As Boolean = False
Public Property SuppressDefaultClick As Boolean
Get
Return _suppress
End Get
Set(value As Boolean)
_suppress = value
End Set
End Property
Protected Overridable Sub MyButton_Click(sender As Object, e As System.EventArgs) Handles Me.Click
If Not SuppressDefaultClick Then
MsgBox("Base Click")
End If
End Sub
End Class
If MyButton didn't include a way to suppress its built-in click handler like above then you'd have to resort to other means to solve your problem. In that case you'd have to prevent the button from ever receiving the message that the left mouse button has been clicked at all, and instead implement your own routine. This approach would be a considered a hack, since you are working around the limitations of something and not using it in the way it was originally intended. Here's one way the hack could be implemented:
Public Class Form1
Private WithEvents TMBC As TrapMyButtonClick
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TMBC = New TrapMyButtonClick(Me.MyButton1)
End Sub
Private Sub TMBC_Click(sender As MyButton) Handles TMBC.Click
MsgBox("Form Click Code")
End Sub
Private Class TrapMyButtonClick
Inherits NativeWindow
Private _mb As MyButton
Private Const WM_LBUTTONDOWN As Integer = &H201
Public Event Click(ByVal sender As MyButton)
Public Sub New(ByVal mb As MyButton)
If Not IsNothing(mb) AndAlso mb.IsHandleCreated Then
_mb = mb
Me.AssignHandle(mb.Handle)
End If
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case WM_LBUTTONDOWN
RaiseEvent Click(Me._mb) ' raise our custom even that the form has subscribed to
Exit Sub ' Suppress default behavior
End Select
MyBase.WndProc(m)
End Sub
End Class
End Class
Public Class MyButton
Inherits Windows.Forms.Button
Protected Overridable Sub MyButton_Click(sender As Object, e As System.EventArgs) Handles Me.Click
MsgBox("Base Click")
End Sub
End Class
You are getting 2 messages because you have set 2 different event handlers for the Click event:
The MyButton_Click method defined in your MyButton class.
The Button_Click method set in your AddHandler call on the form.
As noted in a comment above, you need to override the Button.OnClick method in your MyButton class instead of creating a new method:
Imports System.Windows.Forms
Imports System.Drawing
Public Class MyButton
Inherits Windows.Forms.Button
Sub New()
End Sub
' Override the OnClick event defined in "Button" class.
Protected Overrides Sub OnClick(e As System.EventArgs)
' Call the Click event from "Button" class.
MyBase.OnClick(e)
' Some custom events.
MsgBox("MyButton Click")
End Sub
End Class
It might be a good exercise to set breakpoints in the Button_Click and MyButton.OnClick methods so you can see exactly how the stack is created.