DataGridView mouse scrolling button with IBM/UltraNav driver

As part of my work I use an IBM Thinkpad laptop a lot (T40 model) and one thing that has bugged me a lot is that the mouse wheel button on the laptop keyboard (center button) doesn't work with the DataGridView. Below is the story of my investigation and how I got this to work, including the code necessary for you to get this to work.

After a little investigation I found that the UltraNav mouse wheel didn't work with many of the Windows Forms controls which use scroll bar controls vs. using the scroll bar window style. This means that controls which use the AutoScroll feature work with the UltraNav mouse wheel, but controls that have a horizontal and/or vertical scroll bar as a child control do not work. Well, I for one wanted to find out how I could get it to work for the DataGridView, so I dug deeper in my investigation and into Win32.

The first thing I did was to take a Form with AutoScroll set to true (showing scrollbars) and set a breakpoint on the WM_VSCROLL/WM_HSCROLL windows messages in the WndProc. Sure enough, using the UltraNav mouse wheel the scroll messages are sent. This is different than a normal mouse wheel whereby a WM_MOUSEWHEEL message is sent. So I knew that the UltraNav mouse wheel system was doing things a bit different. Looking up WM_VSCROLL\WM_HSCROLL messages in MSDN says that these messages are sent by the scrollbars to its parent. When I use the UltraNav mouse wheel on a control that doesn't use AutoScroll the scroll messages are not sent. So I had a hunch that the UltraNav attempts to detect if the window has scroll bars or not to know if it sends the scroll messages. With this, I investigated the difference between a scrollbar in Outlook vs. a Windows Forms scroll bar.

Using Spy++ on the Outlook inbox view (where the UltraNav mouse wheel does work) shows me that Outlook uses a separate scrollbar control (vs. window style). Spy++ told me that there wasn't anything special about that scroll bar either. I compared the window styles and window class details between Outlook and a Windows Forms scrollbar (below screen shots - Outlook on left, Windows Forms on the right. Click on the images for a larger view).

 

The first thing I noticed was that the Outlook scrollbar had the SBS_VERT window style where the Windows Forms scroll bar has 00000001 -- what's going on here? I looked at the scrollbar code and saw that we do set the SBS_VERT window style, but Spy++ doesn't show it. I cracked the Windows.h file included in the platform SDK and noticed that SBS_VERT equals 00000001. Why does Spy++ show it as SBS_VERT for the Outlook scrollbar but as 00000001 for the Windows Forms scrollbar? I chewed on this a bit and came to a conclusion -- Spy++ only knows that 00000001 is SBS_VERT based upon the window class of Scrollbar and the Windows Forms scrollbar class name is a munged class name to make the class name unique. I found out that Windows Forms does this because we need to modify details of that window class and we can't just modify the scrollbar window class or else all scrollbars in the system would be modified (thanks Brian for this info!). So I decided that if Spy++ doesn't think a Windows Forms scrollbar is a scrollbar (enough to show SBS_VERT) then maybe UltraNav doesn't detect this as well. Based upon my thinking that UltraNav sends the scroll messages when it detects a scroll bar I thought that this is where the breakdown is occurring.

So my thinking was that if I create a native scroll bar window as a child of the DataGridView UltraNav would send the WM_VSCROLL/WM_HSCROLL windows messages. Off to Win32 land a bit more. Using the NativeWindow class, I constructed a native scroll bar window making use of some p/invoke calls (Click here for the native scroll bar code)

Ok- so I tested this out by creating a custom DataGridView class and wrote code that creates the native scroll bar window as a child of the DataGridView, and sure enough UltraNav started to send the WM_VSCROLL/WM_HSCROLL windows messages (even though I only create a native vertical scroll bar). Now, remember that the scroll bar messages are sent to the parent of the scroll bar, this means that the WM_VSCROLL/WM_HSCROLL windows messages are sent to the DataGridView's WndProc. Now things were almost working -- all I had to do was cause the DataGridView to scroll in response to the scroll messages.

Now, I could take the scroll bar messages and crack the message and perform the correct action, just as if the user used the normal DataGridView scroll bars, but I realized that would require a lot more code than what I wanted to do, so I thought - what if I send the scroll messages to the DataGridView scroll bars? My first try with this was to p/invoke SendMessage and just send the scroll messages to the correct (horizontal/vertical) scrollbar. This didn't work. I found out that when a scroll messages is generated by a normal scroll bar, they are generated first in the scrollbar's WndProc then they are "reflected" back to the parent. In the scrollbar WndProc the message isn't WM_VSCROLL/WM_HSCROLL, but WM_VSCROLL + WM_REFLECT and WM_HSCROLL + WM_REFLECT and they then end up in the scrollbar's parent WndProc as WM_VSCROLL/WM_HSCROLL. Well, so I thought -- why don't I just reflect the messages back to the scrollbar. And this worked! In fact, Windows Forms has a great static function on Control called ReflectMessage. 

So it's done -- using a custom native scroll bar class and a custom DataGridView, my Thinkpad UltraNav mouse scroll button works! I think I'll use this in other custom controls that I write! You can download the complete code listing here.

Anyway, hopefully this will be useful to someone else as much as it is to me! Maybe in the future we can be IBM to update UltraNav to work better with Windows Forms so a hack like this isn't necessary.

-mark