Introduction
In a Windows Forms application, the mouse wheel events will go by default to the control that has focus. So the user has to click the control before using the wheel on it. In many cases, this behaviour is annoying and counterintuitive and the application could provide a much better user experience if the mouse wheel would scroll/zoom/whatever the control under the mouse pointer. This behaviour is also known as mouse over scrolling or mouse over zooming. A classic example for this is a TreeView/ListView combination similar to a Windows Explorer.
Background
To implement this behaviour in different applications, I wrote the same code again and again using MouseEnter
, MouseLeave
, IMessageFilter
, etc. So I did want to write a class that would allow me to do it in one single line of code - and this is what I came up with. It allows me to simply attach any control to the redirector and it will immediately start receiving the mouse wheel events whenever the mouse pointer is inside its bounds.
Using the Code
- Add MouseWheelRedirector.vb to your Windows Forms Application project.
- To start receiving mouse wheel events for a control:
MouseWheelRedirector.Attach(myControl)
- To stop receiving mouse wheel events for that control (unless focused):
MouseWheelRedirector.Detach(myControl)
- To suspend redirection for all attached controls (without detaching them):
MouseWheelRedirector.Active = False
- To resume redirection for all attached controls:
MouseWheelRedirector.Active = True
Here is some example code showing the usage of the class with a TreeView
and a ListView
. To use it, just create a new Windows Application and drop the following code into the code window for Form1
:
Option Explicit On
Option Strict On
Option Infer On
Imports System.Windows.Forms
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
Dim tools = New ToolStrip
tools.Items.Add(New ToolStripButton("Suspend MouseWheelRedirector", _
Nothing, AddressOf SuspendRedirector))
tools.Items.Add(New ToolStripButton("Resume MouseWheelRedirector", _
Nothing, AddressOf ResumeRedirector))
Dim split = New SplitContainer
split.Dock = DockStyle.Fill
Dim tree = New TreeView
tree.Dock = DockStyle.Fill
Dim list = New ListView
list.Dock = DockStyle.Fill
list.View = View.List
Me.Height = 300
Me.Width = 800
split.Panel1.Controls.Add(tree)
split.Panel2.Controls.Add(list)
Me.Controls.Add(split)
Me.Controls.Add(tools)
For i = 0 To 199
tree.Nodes.Add("Tree View Node " & i)
list.Items.Add("List View Item " & i)
Next
tree.ExpandAll()
MouseWheelRedirector.Attach(tree)
MouseWheelRedirector.Attach(list)
End Sub
Private Sub SuspendRedirector(ByVal sender As Object, ByVal e As System.EventArgs)
MouseWheelRedirector.Active = False
End Sub
Private Sub ResumeRedirector(ByVal sender As Object, ByVal e As System.EventArgs)
MouseWheelRedirector.Active = True
End Sub
End Class
Points of Interest
This class uses the following techniques for the given task:
- Listen to the control's
MouseEnter
and MouseLeave
events to determine when the mouse pointer is over the control. - Implement
IMessageFilter
to catch WM_MOUSEWHEEL
messages in the application. - PInvoke the Windows API call
SendMessage
redirecting the WM_MOUSEWHEEL
message to the control's handle. - The
IMessageFilter
object is implemented as a singleton of the MouseWheelRedirector
class and accessed by the shared members Attach
, Detach
, and Active
.
Limitations
As herves pointed out in the Comments and Discussions section, if an attached control has child controls, the redirector won't work when the mouse pointer is over one of the child controls: whenever the mouse pointer hits the child control, the parent control's mouse leave event will fire and the parent control will stop receiving redirected mouse wheel events until the next mouse enter event.
History
- November 15, 2011: Added one line of code to the
Detach
function:
If instance.currentControl Is control Then instance.currentControl = Nothing
November 16, 2011: Added the Limitations section to the article.