I hate to depart from my usual ranting and raving and actually post some useful code, but the below is too lovely to ignore.
Before I proceed to take credit, I have to thank Google and the following poster(s): http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=228499&SiteID=1. The below is a rather elementary explanation of the code posted therein, with a generic adaptation at the end.
Subject: The mouse wheel.
The mouse wheel is an excellent widget for the scrolling with. One simply waves it over a window and, without needing to give input focus to that window, a few deft flicks of the wheel will scroll any eminently scrollable window, moving the dull, tiresome and mundane contents out of view and revealing the delicious, exciting, wonderous contents to be found just enticingly off screen. If one were extraordinarily closeted or on some serious drugs, it could even be a fulfilling experience.
In any case, this is the experience a lot of users are looking for (if you remove the parts of the above description which relate to excitement or interest in any way). Now in previous applications I’d written, this proved an issue. Mouse wheel messages are only routed by Windows to the window with input focus. Extraordinarily dumb? Why, yes, I should coco. The immediate and obvious hack is to ensure that the window under the mouse has input focus. As most users don’t have PowerToys for Windows installed (and, even if they do, rarely check the “activate the window under the mouse cursor” option), this has to be done by the application. Cheap, hideous hacks follow:
internal class MyControl : UserControl
{
…
MyControl()
{
….
this.MouseMove += new MouseEventHandler(MyControl_MouseMove);
}
private void MyControl_MouseMove(object sender, MouseEventArgs e)
{
this.Focus();
}
}
Disgusting indeed. One problem with this is that every window which wants mouse wheel input has to include this deviant hack. It also has the disadvantage of vexing behaviour in various cases, such as if you add dialogs containing text boxes into the equation. Position such a dialog over your focus-stealing control, put some text in the dialog’s text box, and ask a user to select that text. See the user drag select the text from right to left, ending up with the mouse cursor outside the dialog and over your focus-stealing control…which grabs the focus, thus deselecting the text and preventing keyboard input to the dialog until the user clicks the text field again. Better hope at that stage they keep the mouse within the dialog…
However, there is a much nicer solution, in the form of message filters. Like the filter tips on cigarettes, message filters can prevent carcinogenic messages from reaching the lungs of your controls. Unlike filter tips, they can also do more or less anything with the messages they filter out.
Behind the scenes in a Windows Forms application, each user interface thread of an application is running a message loop which resembles the following:
while ( GetMessage(&msg,0,0,0) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
This should be familiar to any seasoned Win32 programmer. So messages arrive in the message queue; take a quick slash and burn through keyboard accelerator translation; and then are dispatched to the destination window’s window procedure (WndProc). I missed out a pertinent detail, however…
while ( GetMessage(&msg,0,0,0) )
{
if ( Application.FilterMessage(&msg) ) // allow the application to filter out messages of its choosing
continue;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
So, .NET is being very obliging here. The Application class is giving us a window in which we can globally (per thread) filter out messages before they hit their destination window. How incredibly evil! And, useful.
We can use this little loophole to detect when the mouse wheel is scrolled when the focus is with any window. Then before the window gets the message, we’ll whisk it away under cover of darkness, and send it to a more deserving candidate: viz. the window actually under the mouse cursor at the time. Best of all, this requires no code whatsoever in the recipient window. It just gets mouse wheel messages, and gratefully processes them as normal.
So, we take the following steps.
1. Derive a class from IMessageFilter.
2. Do what we want with messages therein.
3. At an early stage in our application’s lifetime, call Application.AddMessageFilter( new MyMessageFilterClass() ).
4. Bingo.
To make things really easy for you, here’s some sample code in full.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace MyCompany.MyApplication.UI
{
/// <summary>
/// Ensures that all mouse wheel messages are dispatched to the
/// window under the cursor, rather than the window with input
/// focus.
/// </summary>
public class MouseWheelMessageFilter : IMessageFilter
{
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[DllImport(“user32.dll”)]
private static extern IntPtr WindowFromPoint([In] MouseWheelMessageFilter.POINT point);
[DllImport(“user32.dll”)]
public static extern int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private const int WM_MOUSEWHEEL = 0x020A;
private uint LOWORD(IntPtr value)
{
return (uint)( ( ((ulong)value.ToInt64()) & 0xffff ) );
}
private uint HIWORD(IntPtr value)
{
return (uint)( ( ((ulong)value.ToInt64()) & 0xffff0000 ) >> 16 );
}
public bool PreFilterMessage(ref Message m)
{
if ( m.Msg == WM_MOUSEWHEEL )
{
uint screenX = LOWORD(m.LParam);
uint screenY = HIWORD(m.LParam);
MouseWheelMessageFilter.POINT point = new MouseWheelMessageFilter.POINT();
point.x = (int)screenX;
point.y = (int)screenY;
IntPtr hWnd = MouseWheelMessageFilter.WindowFromPoint( point );
if ( hWnd != IntPtr.Zero )
{
MouseWheelMessageFilter.SendMessage(hWnd,(uint)m.Msg, m.WParam, m.LParam);
}
return true; // stop this message being dispatched
}
return false;
}
}
}
Let’s take a quick look through this code.
Firstly, we declare some helpful Win32 glue. We need a binarily-compatible .NET equivalent of the Win32 POINT structure. We also need to make native calls to the WindowFromPoint() and SendMessage() APIs from user32.dll, so we pull them in.
Then the meat of the message filter. We implement IMessageFilter.PreFilterMessage(). If the message is a mouse wheel message, we extract the cursor position (in screen coordinates) from the message parameters, and see which of our windows is under those coordinates. (Nowdays this function only tells you about windows owned by your thread of your process, so no need to worry about accidentally enabling this functionality across all the user’s applications.) Then we immediately route the message directly to that window’s WndProc. We call SendMessage() rather than PostMessage(), since the latter would add the message to the message queue, where it would be retrieved by .NET’s GetMessage() call and we’d go round and round to infinite. SendMessage() dispatches the message directly to the window procedure, bypassing the message queue. It’s also faster.
So there we go. Now don’t say I never give you anything.
Load comments