Listview MouseLeave event: the Header is not included - vb.net

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

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.

Getting Event Handler from Instance of Class

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.

Raise Events Custom Control Class which Inherits Base Class

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

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.

How can I catch the autosize double-click event on a listview in VB.NET?

I am using Visual Studio 2008 and VB.NET. I've got a listview control on my form and I've added columns using the windows forms designer. As you know, if you double-click on the sizer or divider or whatever you want to call it between two columns, the column on the left will autosize (unless you disable that). How can I catch this specific event? The ColumnWidthChanged event and the DoubleClick event are likely candidates, but in the ColumnWidthChanged event, there's no way I can see to determine if it was an autosize. Similarly, there's no simple way to catch what was clicked exactly with the DoubleClick event. Does anyone have any ideas how I can catch this specific event type?
Detecting events on a listview's header is quite tricky.
You will need to create your own header to replace the one that it normally uses, and then listen to the appropriate messages. There aren't any specific ones for column resize handles, as far as I know.
The following class subclasses ListView and adds a handler that detects a double-click between columns. That is as close as it gets, I think.
I hope it will help you out somewhat.
Class MyListView
Inherits ListView
Protected Overrides Sub CreateHandle()
MyBase.CreateHandle()
New HeaderControl(Me)
End Sub
Private Class HeaderControl
Inherits NativeWindow
Private _parent As ListView = Nothing
<DllImport("User32.dll", CharSet := CharSet.Auto, SetLastError := True)> _
Public Shared Function SendMessage(hWnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function
Public Sub New(parent As ListView)
_parent = parent
Dim header As IntPtr = SendMessage(parent.Handle, (&H1000 + 31), IntPtr.Zero, IntPtr.Zero)
Me.AssignHandle(header)
End Sub
Protected Overrides Sub WndProc(ByRef message As Message)
Const WM_LBUTTONDBLCLK As Integer = &H203
Select Case message.Msg
Case WM_LBUTTONDBLCLK
Dim position As Point = Control.MousePosition
Dim relative As Point = _parent.PointToClient(position)
Dim rightBorder As Integer = 0
For Each c As ColumnHeader In _parent.Columns
rightBorder += c.Width
If relative.X > (rightBorder - 6) AndAlso relative.X < (rightBorder + 6) Then
MessageBox.Show([String].Format("Double-click after column '{0}'", c.Text))
End If
Next
Exit Select
End Select
MyBase.WndProc(message)
End Sub
End Class
End Class
You will need to include a using System.Runtime.InteropServices; statement for this to work.