Programming the IntelliMouse from 32-bit VB

by Gary Nelson

One of Microsoft's hardware innovations is sure to do more for killing old mice than an alley cat on steroids. Once you get your hands on an IntelliMouse, the mouse with a wheel, your old mouse will go the way of the dinosaur. But how can you make your program react to it? In this article, we'll demonstrate.

The cat's meow

The Microsoft IntelliMouse SDK contains one mouse, the mouse driver, the ZMOUSE.H file, and a couple of icons. ZMOUSE.H is a C header file, and it's only 8 KB in size. Since Windows 95 doesn't natively support the IntelliMouse pointing device, you must use the RegisterWindowMessage API call to generate a message ID. Then, you use the SendMessage API call to get some details about the mouse, such as how many lines to scroll each time the user rolls the wheel.

Next, you need to capture the message from the mouse when the user rotates the wheel. In order to do so, you must use a message hook. I tried three message hooks, but in the end opted for Msghoo32.OCX, created by Mabry Software (www.mabry.com) and Zane Thomas. I chose this OCX for four reasons: It's small (28 KB), it's easy to use, it works, and it's free. You can download the file MSGHOOK.EXE from the Inside Visual Basic Web site, www.cobb.com/ivb; click the Source Code hyperlink and download the nov97.zip file. Double-click on MSGHOOK.EXE to install the OCX. (If you wish, VB 5.0 will let you write your own message hook instead of using a commercial OCX.)

The message hook creates a new event in your form. Our sample code traps two events: the movement of the mouse wheel and a change in system configuration. You need to check the system configuration because if the user changes the number of lines to scroll for each turn of the mouse wheel, you must adjust your program to the new value.

The main subroutine is Setup_MsWheel. You pass the routine the form you're using; as a result, you can use the routine with any form in the project. In this design, you must place a Msghook control on each form that will trap the event. It's also possible to use just one Msghook control and then reassign it, but we've chosen the multi-Msghook approach for the sake of simplicity.

You also pass the variable Loading. If you don't include this variable, then the program will only check to see how many lines to scroll. If you pass Loading with a value of 0, the program will capture changes in the system configuration and mouse scrolling. (We use this value for the project's main form, since it isn't necessary to check for changes in system configuration on each form.) If you pass Loading with a value of 1, then only messages from the mouse will be captured.

The Setup_MsWheel subroutine also assigns a value to the global variable WHEEL_SCROLL_LINES. If the returned number is 0, then no lines are to be scrolled. If it's -1, then a page at a time will be scrolled. If it's a positive number, then that number of lines will be scrolled.

Bait and switch

I encountered a couple of problems along the way while developing this technique, but I was able to solve them. First, I found that the Msghook control has a bug--it won't allow a message number greater than 32,767. Mabry Software provided a solution: Use a hexadecimal number. As you'll see in the code, you convert the mouse message number to hexadecimal so the Msghook control will accept it.

Next, when I installed the program on Windows NT 4.0, it just didn't work. So, back to the drawing board. Generally, I do all of my developing on Windows 95, then perform a test run on NT--but to solve this problem, I also had to install 32-bit VB 4.0 on NT. The zmouse.h file offered only this information: "Wheel is supported natively in WinNT4.0, please refer to the NT4.0 SDK for more info on providing support for IntelliMouse in NT4.0."

Releasing the trap

The next point is what to do with the trapped event. The event itself looks like this:

Private Sub Msghook1_Message(ByVal msg As _
Long, ByVal wp As Long, ByVal lp As Long, _
result As Long)

Actually, this code is very simple. Msg is the number of the message, which you've assigned earlier. If you're hooking into several messages, then you'll have to check which message has been received and proceed accordingly. In this case, we have only two messages to check for.

Wp indicates the distance rotated in divisions of WHEEL_DELTA (120). Under Windows 95, WHEEL_DELTA returns 120 or -120, depending on whether you're scrolling up or down. Under Windows NT, it returns 7,864,200 (120 times 65,535), which I assume is a result of a bug in NT (apparently, the first two bytes and the second two bytes are swapped). Microsoft chose the number 120 to allow for a higher-resolution mouse in the future.

Lp represents the position of the mouse. The low-order word of lp contains the X coordinate of the pointer, and the high-order word of lp contains the Y coordinate of the pointer. Result is the value to return to Windows, which in this case is 0.

The big cheese

Let's work through an example that puts the IntelliMouse through its paces. To begin, create a VB project and add a module to it. Enter the code from Listing A into Module1, then enter the code from Listing B into Form1.

Listing A: Module1 code

Declare Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long

Declare Function RegisterWindowMessage Lib "user32" _
Alias "RegisterWindowMessageA" (ByVal lpString As String) As Long

Declare Function SendMessage Lib "user32" Alias _
"SendMessageA" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wp As Integer, ByVal lp As Long) As Long

Declare Function GetSystemMetrics Lib "user32" _
(ByVal nIndex As Long) As Long

Declare Function SystemParametersInfo Lib "user32" _
Alias "SystemParametersInfoA" _
(ByVal uAction As Long, ByVal uParam As Long, _
lpvParam As Any, ByVal fuWinIni As Long) As Long

Public WHEEL_SCROLL_LINES As Long

Public Sub Setup_MsWheel(Target As Form, Optional LOADING)
If GetSystemMetrics(75) Then 
' NT 4.0 - IntelliMouse is natively supported
  If Not IsMissing(LOADING) Then
    ' turn hook on 
    Target.Msghook1.HwndHook = Target.hwnd  
    ' check configuration 
    If LOADING = 0 Then Target.Msghook1.MESSAGE(&H1A) = True 
    ' check for mouse wheel scrolling 
    Target.Msghook1.MESSAGE(&H20A&) = True  
  End If
  ' Find out how many lines to scroll 
  aa& = SystemParametersInfo(104, 0, a2%, 0) 
  ' Set lines to scroll to global variable 
  WHEEL_SCROLL_LINES = a2% 

' Windows 95 or NT 3.5X - 
' IntelliMouse is not natively supported 
Else 
  hdlMsWheel& = FindWindow("MouseZ", "Magellan MSWHEEL")
  puiMsh_MsgMouseWheel& = RegisterWindowMessage("MSWHEEL_ROLLMSG")
  puiMsh_Msg3DSupport& = _
  RegisterWindowMessage("MSH_WHEELSUPPORT_MSG")
  puiMsh_MsgScrollLines& = _
  RegisterWindowMessage("MSH_SCROLL_LINES_MSG")
    
  If puiMsh_Msg3DSupport& Then
    pf3DSupport& = SendMessage(hdlMsWheel&, _
    puiMsh_Msg3DSupport&, 0, 0)
  Else
    pf3DSupport& = False
  End If
    
  ' Find out how many lines to scroll 
  If puiMsh_MsgScrollLines& Then  
    piScrollLines& = SendMessage(hdlMsWheel&, _
    puiMsh_MsgScrollLines&, 0, 0)
  Else
    piScrollLines& = 3  ' default
  End If

  ' Set lines to scroll to global variable 
  WHEEL_SCROLL_LINES = piScrollLines& 
  If Not IsMissing(LOADING) Then
    If hdlMsWheel& Then  ' IntelliMouse Found
      ' turn hook on 
      Target.Msghook1.HwndHook = Target.hwnd  
      ' check configuration 
      If LOADING = 0 Then _
      Target.Msghook1.MESSAGE(&H1A) = True 

      ' check for mouse wheel scrolling 
      Target.Msghook1.MESSAGE("&H" & _
      Hex$(puiMsh_MsgMouseWheel&)) = True 
    End If
  End If
End If
End Sub

Function InBounds(lp As Long, frmname As Form, _
ctlname As Control) As Boolean
InBounds = False
mousex% = lp And 65535
mousey% = lp \ 65535
HeadingHeight& = frmname.height - frmname.ScaleHeight

If mousex% < (frmname.Left + ctlname.Left) / _
Screen.TwipsPerPixelX Then
ElseIf mousex% > (frmname.Left + frmname.Width) / _
Screen.TwipsPerPixelX Then
ElseIf mousey% < (frmname.Top + ctlname.Top + _
HeadingHeight&) / Screen.TwipsPerPixelY Then
ElseIf mousey% > (frmname.Top + ctlname.Top + _
ctlname.height + HeadingHeight&) / Screen.TwipsPerPixelY Then
Else: InBounds = True
End If
End Function

Next, choose Tools | Custom Controls... from VB's main menu, select Msghook OLECustom Control Module, as shown in Figure A, and click OK. Place a Msghook control and a ListBox control on the form.

Now, save the project as INTELL.VPB and run it. When you do so, the list box will display a list of 100 test items. You'll find that by spinning your IntelliMouse wheel, you can scroll easily through the list.

Add the Msghook control to your project.

Notes

I chose 14 as the number of lines for a full-page scroll. Since I assigned this number arbitrarily, you should calculate the correct number of lines for a full-page scroll in your program.

Also, perhaps because I'm a bit of an iconoclast, I didn't follow Microsoft's custom of assigning constants; instead, I used literal values. For example, you might prefer to make the line

If GetSystemMetrics(75) then

instead read 

If GetSystemMetrics(SM_MOUSEWHEELPRESENT) then

If you'd rather declare the constants and use them, you're welcome to do so.

Conclusion

Visual Basic doesn't internally support Microsoft's IntelliMouse. However, the explanation and code we've provided in this article should help you get that mouse wheel rolling in your VB projects.

Listing B: Form1 code

Private Sub Form_Load()
Setup_MsWheel Me, 0
For x = 1 To 100
  List1.AddItem "Test " & x
Next
End Sub

Private Sub Msghook1_Message(ByVal msg As Long, _
ByVal wp As Long, ByVal lp As Long, result As Long)

' lp = mouse position
Select Case msg
  Case &H1A  ' WM_SETTINGCHANGE = &H1A
    ' reset value of WHEEL_SCROLL_LINES 
    Setup_MsWheel Me  

  ' Wheel has scrolled 
  Case Else 
    ' checks to see if the mouse is over the window 
    If InBounds(lp, Me, List1) Then 
      ' scroll one page 
      If WHEEL_SCROLL_LINES = -1 Then 
        newval& = List1.TopIndex - (wp / Abs(wp) * 14) 
      Else
        If WHEEL_SCROLL_LINES > 14 Then
          lines_to_scroll% = 14
        Else
          lines_to_scroll% = WHEEL_SCROLL_LINES
        End If
        newval& = List1.TopIndex - (wp / Abs(wp) * lines_to_scroll%)
      End If
      If newval& < 0 Then newval& = 0
      If newval& > List1.ListCount - 1 Then ewval& = List1.ListCount - 1
      List1.TopIndex = newval&
    End If
End Select
End Sub

Gary Nelson is head analyst and programmer for Isla Soft S.L., which specializes in accounting software for Spain and Portugal. His program ContaWin was chosen by PC World (Spain) in 1995 as best program designed in Spain. You can reach Gary on CompuServe at 72660,1222.