Best Practices for Supporting Microsoft Mouse and Keyboard Devices

 

Kevin Smith
Microsoft Corporation

April 2004

Applies to:
   Microsoft® Mouse
   Microsoft® Keyboard

Summary: Microsoft Hardware has a long history of hardware innovations that include vertical and horizontal scroll wheels on mouse devices and keyboards, and hot keys and zoom sliders on keyboards. This article outlines best practices to follow to help ensure that applications work well with the features of Microsoft input devices to provide the best possible user experience. Sample application code in the appendix demonstrates the concepts in this article. (21 printed pages)

Contents

Vertical Scrolling
Horizontal Scrolling
Mouse Back and Forward Buttons
Hot Keys and Appcommands
Keyboard Zooming
Appendix: Sample Code Demonstrates Support for Microsoft Input Devices

Vertical Scrolling

The first Microsoft® mouse with a vertical scroll wheel was introduced about 10 years ago. It had a low-resolution scroll wheel with 18 detents per revolution, which gives the wheel a ratcheting feel. Each time the wheel is rotated, a data packet is sent to the computer. This data packet is converted to a WM_MOUSEWHEEL message. The high-order WORD of the wParam parameter indicates the distance the wheel is rotated, expressed in multiples of WHEEL_DELTA, which is 120. The Microsoft Hardware team and the Windows® driver team chose the value of 120 for WHEEL_DELTA because it can be evenly divided in a number of ways in anticipation of future higher-resolution scroll wheels.

Microsoft has recently developed new mouse devices with higher-resolution scroll wheels that have no detents. Users experience more accurate scrolling that is smooth, with no ratcheting feel. To support these new mouse devices, the WM_MOUSEWHEEL message must be handled correctly. Note that some Microsoft keyboards also have scroll wheels that generate the WM_MOUSEWHEEL message.

When a WM_MOUSEWHEEL message is received, the high-order WORD of wParam indicates the distance the wheel is rotated (this is called "scroll value"). The scroll value is expressed in multiples or divisions of WHEEL_DELTA such as +120, –240, +150, or –30. A positive value indicates that the wheel was rotated forward, away from the user. A negative value indicates that the wheel was rotated backward, toward the user. The application must retrieve the current lines-to-scroll user setting (SPI_GETWHEELSCROLLLINES) by using the SystemParametersInfo API. The following formula determines how many lines to scroll.

(scroll value / WHEEL_DELTA ) × lines-to-scroll user setting = lines to scroll

The result of the formula can be a fractional value, such as 1.3 or 2.5. It is preferable for the application to scroll the view a fractional number of lines if possible. If this is not possible, the view should be scrolled a whole number of lines, and any remainder should be accumulated and added to the next lines-to-scroll result. The remainder must be zeroed when the wheel rotation switches directions or when window focus changes.

Table 1 gives an example of accumulating the scroll value for an application that cannot scroll fractional lines. In this example, the lines-to-scroll user setting is 2.

Table 1. Example of Accumulating Scroll Value

WM_MOUSEWHEEL message Scroll value Lines to scroll Total lines to scroll Whole lines to scroll Scroll remainder
          0
1 90 1.5 1.5 1 0.5
2 60 1 1.5 1 0.5
3 120 2 2.5 2 0.5
4 30 0.5 1 1 0
5 30 0.5 0.5 0 0.5
6 –60 –1 –1 –1 0
7 –30 –0.5 –0.5 0 –0.5

If the lines-to-scroll user setting is –1, applications should scroll the window one page at a time. Applications should always handle page scrolling, but note that page scrolling is never enabled when Microsoft IntelliPoint mouse software is installed.

When the lines-to-scroll user setting is –1 (that is, page scrolling is enabled) and a scroll value less than WHEEL_DELTA is received, applications should scroll a fraction of a page by using the following formula:

scroll value / WHEEL_DELTA = pages to scroll

This will give the user immediate feedback when the wheel is rotated.

If an application—such as a drawing program—does not use the paradigm of lines and pages of data, define appropriate units to represent a line and a page.

Horizontal Scrolling

Microsoft has introduced a new scroll wheel for mouse devices and keyboards that tilts side to side. The tilt wheel feature provides horizontal scrolling functionality.

Currently, there is no operating system support for the tilt wheel. Microsoft IntelliType Pro or IntelliPoint software—for keyboards and mouse devices, respectively—must be installed to enable this functionality. With the software installed, horizontal scrolling works for many applications.

Horizontal scrolling will generally work if the following are true:

  • The window that has focus is a standard Windows control with an internal horizontal scroll bar.
  • A horizontal scroll bar is associated with a window and it is placed near the bottom of the window. Ideally, it will be placed below and adjacent to the window, and between the left and right sides of the window.

Horizontal scrolling will not work, or is likely not to work, in the following scenarios:

  • The application uses custom horizontal scroll bars.
  • The application has no horizontal scroll bars.
  • The horizontal scroll bar is physically placed in a nonstandard way relative to the window it is associated with.

The upcoming Microsoft operating system, code-named "Longhorn," will implement the WM_MOUSEHWHEEL message to support tilt wheels. It is recommended that applications implement support for this message now, for the following reasons:

  • Longhorn will provide native support, and IntelliType Pro or IntelliPoint will not need to be installed for horizontal scrolling to work.
  • Future versions of IntelliType Pro and IntelliPoint may emulate the WM_MOUSEHWHEEL message on Windows® 2000 and Windows XP.
  • Applications will have full control over horizontal scrolling.

In general, there will be feature parity and symmetry between horizontal and vertical scrolling. The new WM_MOUSEHWHEEL message has the same parameters as the WM_MOUSEWHEEL message. One difference is that TRUE must be returned when a WM_MOUSEHWHEEL message is handled. This allows IntelliType Pro or IntelliPoint to be notified that the message was handled (if future versions of IntelliType Pro and IntelliPoint emulate this message); otherwise, the horizontal scroll action may be repeated. Refer to the sample code in the appendix for #defines needed for horizontal scrolling in Longhorn.

The high-order WORD of wParam indicates the distance the wheel is tilted, expressed in multiples or divisions of WHEEL_DELTA (this is called "tilt value"). A positive tilt value indicates that the wheel was tilted to the right; a negative value indicates that the wheel was tilted left.

When an application receives a WM_MOUSEHWHEEL message, it is responsible for retrieving the characters-to-scroll user setting (SPI_GETWHEELSCROLLCHARS) by using the SystemParametersInfo API. This setting will not be available on Windows 2000 and Windows XP, so use the value of 1. IntelliType Pro and IntelliPoint will maintain a substitute value for the characters-to-scroll user setting and send the correct number of WM_MOUSEHWHEEL messages.

(tilt value / WHEEL_DELTA) × characters-to-scroll user setting = characters to scroll

As with vertical scrolling, applications should scroll partial character widths if possible. If an application can only scroll by whole character widths, it must accumulate any remainder. The remainder must be zeroed when the tilt direction changes or when a window or control loses focus.

If an application—such as a computer-aided design (CAD) program—does not use the paradigm of character and page widths, define an appropriate unit to provide the best user experience.

Mouse Back and Forward Buttons

Some Microsoft mouse devices have five buttons. The fourth and fifth buttons typically navigate back and forward in Web browsers such as Internet Explorer. These buttons are referred to as X buttons and are supported natively in Windows 2000, Windows Millennium Edition, and Windows XP.

Six new messages have been added for these mouse buttons: WM_XBUTTONDBCLK, WM_XBUTTONDOWN, WM_XBUTTONUP, WM_NCXBUTTONDBCLK, WM_NCXBUTTONDOWN, and WM_NCXBUTTONUP. The messages are similar to those provided for the first three mouse buttons, the main difference being that the high-order WORD of wParam specifies which button the message refers to.

When an X button is pressed, an X button message is first generated and a corresponding Back or Forward WM_APPCOMMAND message is later sent to the application. Therefore, if an application implements Back and Forward functionality, it is recommended that the WM_APPCOMMAND message be handled rather than the X button messages. This will allow Back and Forward functionality from mouse, keyboard, and other devices.

If an application uses the X buttons for a mouse-related action, such as activating a tool or menu in a CAD program, the X button messages should be handled directly.

An application may have to listen both to X button messages and WM_APPCOMMAND messages to achieve the desired behavior. For example, the X buttons may activate a tool, and the Back and Forward hot keys on a Microsoft keyboard may navigate the view like a Web browser.

Hot Keys and Appcommands

Microsoft keyboards have sets of hot keys that send multimedia, browser, and productivity events to the computer. Some hot keys are natively supported by Windows 2000, Windows Millennium Edition, and Windows XP, and others require Microsoft IntelliType Pro to be installed.

When a keyboard hot key is pressed, an appcommand is sent to the window that has focus via a WM_APPCOMMAND message. Appcommands can also be generated from mouse and other devices. Note that Microsoft keyboards only generate a subset of the appcommands listed in the Software Development Kit (SDK) documentation for WM_APPCOMMAND, and not all Microsoft hot keys generate appcommands.

The following appcommands are natively supported by Windows 2000, Windows Millennium Edition, and Windows XP. Browsers should support the seven browser appcommands and media players should support the four media appcommands. Because media players are generally skinned, they should always handle the media hot keys; otherwise, these hot keys are difficult or impossible for IntelliType Pro to support.

  • APPCOMMAND_BROWSER_BACKWARD
  • APPCOMMAND_BROWSER_FORWARD
  • APPCOMMAND_BROWSER_REFRESH
  • APPCOMMAND_BROWSER_STOP
  • APPCOMMAND_BROWSER_SEARCH
  • APPCOMMAND_BROWSER_FAVORITES
  • APPCOMMAND_BROWSER_HOME
  • APPCOMMAND_MEDIA_NEXTTRACK
  • APPCOMMAND_MEDIA_PREVIOUSTRACK
  • APPCOMMAND_MEDIA_STOP
  • APPCOMMAND_MEDIA_PLAY_PAUSE

The following productivity appcommands are only sent to an application if IntelliType Pro is installed. Mail applications should support all the productivity appcommands and general applications should support all applicable appcommands except the mail appcommands.

  • APPCOMMAND_HELP
  • APPCOMMAND_NEW
  • APPCOMMAND_OPEN
  • APPCOMMAND_CLOSE
  • APPCOMMAND_SAVE
  • APPCOMMAND_PRINT
  • APPCOMMAND_UNDO
  • APPCOMMAND_REDO
  • APPCOMMAND_COPY
  • APPCOMMAND_CUT
  • APPCOMMAND_PASTE
  • APPCOMMAND_SPELL_CHECK
  • APPCOMMAND_REPLY_TO_MAIL
  • APPCOMMAND_FORWARD_MAIL
  • APPCOMMAND_SEND_MAIL

Applications should handle all applicable appcommands for the following reasons:

  • Applications have more control handling hot keys.
  • Applications can provide better support for hot keys than IntelliType Pro can.
  • IntelliType Pro does not support hot keys for all applications
  • IntelliType Pro is designed to control media players in the background, by sending WM_APPCOMMAND messages. This only works if the media player handles the media WM_APPCOMMAND messages.

When a WM_APPCOMMAND message is received, the lParam parameter is cracked by using the GET_APPCOMMAND_LPARAM macro to recover the appcommand. For a list of appcommands the application can receive, see the WM_APPCOMMAND API documentation. To get the newest appcommands, install the Windows XP SDK.

If an appcommand is handled, TRUE must be returned; otherwise, return FALSE. This will ensure that when WM_APPCOMMAND messages are not handled, they are bubbled up to the window's parent. The parent has a chance to handle the message, and the bubbling process continues until the top-level window is reached. If the top-level window does not handle the message, DefWindowProc will call a shell hook with the hook code equal to HSHELL_APPCOMMAND.

IntelliType Pro registers for this shell hook notification, so if the WM_APPCOMMAND message is not handled, IntelliType Pro will attempt to execute the hot key's function. For example, if the user presses the New hot key when a word-processing application is in the foreground, IntelliType Pro can bring up the New dialog box in many cases.

If an application does not handle appcommands, follow these guidelines to make application-specific support through IntelliType Pro possible and easier.

  • Use standard Windows menus.
  • Do not use different shortcuts for the same function between different windows, views, and versions of your software. For example, if you use the CTRL+O shortcut for Open in one view or window, use it for the Open shortcut everywhere.
  • Use standard recommend shortcuts listed in Table 2.

Table 2 lists shortcuts that are nearly universal across applications and languages.

Table 2. Common Shortcuts

Command Shortcut for all languages
Cut CTRL+X
Copy CTRL+C
Paste CTRL+V
Browser Back ALT+LEFT ARROW
Browser Forward ALT+RIGHT ARROW
Browser Stop ESC
Close CTRL+F4
Exit ALT+F4
Help F1
Spelling F7
Undo CTRL+Z
Zoom in CTRL+rotate wheel forward
Zoom out CTRL+rotate wheel backward

Table 3 lists standard shortcuts for the languages that IntelliType Pro supports.

Table 3. Shortcuts for IntelliType ProSupported Languages

  English, French, German, Italian, Japanese, Simplified Chinese, Traditional Chinese, Korean Portuguese (Portugal) Portuguese (Brazil) Spanish
New CTRL+N CTRL+O CTRL+O CTRL+U
Open CTRL+O CTRL+A CTRL+A CTRL+A
Print CTRL+P CTRL+P CTRL+P CTRL+P
Redo CTRL+Y CTRL+R F4 CTRL+Y
Save CTRL+S CTRL+G CTRL+B CTRL+Y
Find CTRL+F CTRL+L CTRL+L CTRL+B

Keyboard Zooming

Some Microsoft keyboards have a zoom slider control. When pushed forward, away from the user, the control zooms in the view of the foreground. When pushed backward, toward the user, the view zooms out. Zooming will only work when IntelliType Pro is installed.

In order for IntelliType Pro to provide zooming support, applications should implement zooming by using the scroll wheel when the CTRL key is pressed. Of course, other methods to enable zooming may be exposed, but CTRL+wheel is standard in many applications and will provide a consistent user experience; in addition, it will allow the zoom slider control on the keyboard to behave the same as the scroll wheel on the mouse.

It is recommended that applications zoom in the same direction as Microsoft Office applications do. Office applications zoom out when the scroll wheel is rotated toward the user and the CTRL key is pressed, and they zoom in when the wheel is rotated away from the user. Be sure to use the same zoom direction for all windows, views, and versions of an application.

Appendix: Sample Code Demonstrates Support for Microsoft Input Devices

This sample code demonstrates how to handle messages from mouse and keyboard input devices to implement vertical and horizontal scrolling, zooming, and support for X buttons and hot keys. The sample handles the following messages: WM_MOUSEWHEEL, WM_MOUSEHWHEEL, WM_XBUTTONDOWN, and WM_APPCOMMAND.

#include <windows.h>

// Forward function declarations
LRESULT CALLBACK WindowProc( HWND, UINT, WPARAM, LPARAM );
LRESULT OnClose( HWND );
LRESULT OnMouseWheel( short, WORD, short, short );
LRESULT OnHorizontalMouseWheel( short, WORD, short, short );
LRESULT OnXButtonDown( WORD, WORD, short, short );
LRESULT OnAppCommandBrowserBackward( HWND, WORD, WORD ); 
LRESULT OnAppCommandBrowserForward( HWND, WORD, WORD ); 
LRESULT OnSettingChange( UINT );
LRESULT DoScroll( float, int, int );
LRESULT DoZoom( float, int, int );
LRESULT DoHScroll( float, int, int );
UINT GetLinesToScrollUserSetting();
UINT GetCharsToScrollUserSetting();

// These values must be defined to handle the WM_MOUSEHWHEEL on 
// Windows 2000 and Windows XP, the first two values will be defined 
// in the Longhorn SDK and the last value is a default value 
// that will not be defined in the Longhorn SDK but will be needed for 
// handling WM_MOUSEHWHEEL messages emulated by IntelliType Pro
// or IntelliPoint (if implemented in future versions).
#define WM_MOUSEHWHEEL                            0x020E
#define SPI_GETWHEELSCROLLCHARS                   0x006C
#define SPI_SETWHEELSCROLLCHARS                   0x006D
#define DEFAULT_CHARS_TO_SCROLL_WITH_EMULATION    0x01

// Default values for SPI_GETWHEELSCROLLLINES and 
// SPI_GETWHEELSCROLLCHARS
#define DEFAULT_LINES_TO_SCROLL                   0x03
#define DEFAULT_CHARS_TO_SCROLL                   0x03

// Cache the SPI_GETWHEELSCROLLLINES and SPI_GETWHEELSCROLLCHARS 
// settings
static UINT linesToScrollUserSetting;
static UINT charsToScrollUserSetting;


//
// Simple WinMain() function to create a window to receive messages
//
int WINAPI WinMain( HINSTANCE hInstance, 
                    HINSTANCE hPrevInstance, 
                    LPSTR lpCmdLine, 
                    int nCmdShow )
{
    // Initialize scroll user settings
    linesToScrollUserSetting = GetLinesToScrollUserSetting();
    charsToScrollUserSetting = GetCharsToScrollUserSetting();

    // Register the window class
    WNDCLASS wc = { 0 };
    wc.style = CS_VREDRAW | CS_HREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground= CreateSolidBrush( GetSysColor(COLOR_BACKGROUND) );
    wc.lpszMenuName =  NULL;
    wc.lpszClassName= L"MouseWheel";
    if( !RegisterClass(&wc) )
        return FALSE;

    // Create and show the window 
    HWND hwndMain = CreateWindow( L"MouseWheel", 
                                  L"MouseWheel", 
                                  WS_CAPTION | WS_SYSMENU | 
                                  WS_THICKFRAME | WS_MINIMIZEBOX | 
                                  WS_MAXIMIZEBOX, 
                                  0, 
                                  0, 
                                  350, 
                                  200, 
                                  0, 
                                  0, 
                                  hInstance, 
                                  0 );
    ShowWindow( hwndMain, SW_SHOWNORMAL );

    // Message Loop
    MSG msg;
    while( GetMessage(&msg, NULL,0,0) ) 
    {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
    }

    return (int)msg.wParam;
} 


//
// Callback function that implements the window procedure
//
LRESULT CALLBACK WindowProc( HWND hwnd, 
                             UINT msg, 
                             WPARAM wParam, 
                             LPARAM lParam )
{
    switch( msg )
    {
        // Vertical scroll message 
        case WM_MOUSEWHEEL:
            return OnMouseWheel( HIWORD(wParam),   // scroll value
                                 LOWORD(wParam),   // key state 
                                 LOWORD(lParam),   // mouse xpos
                                 HIWORD(lParam) );   // mouse ypos

        // Horizontal scroll message
        case WM_MOUSEHWHEEL:
            return OnHorizontalMouseWheel
                               ( HIWORD(wParam),   // tilt value 
                                 LOWORD(wParam),   // key state 
                                 LOWORD(lParam),   // mouse xpos
                                 HIWORD(lParam) );   // mouse ypos

        // X button message generated by mouse buttons four and five
        case WM_XBUTTONDOWN:
            return OnXButtonDown( HIWORD(wParam),   // X button ID 
                                  LOWORD(wParam),   // key state 
                                  LOWORD(lParam),   // mouse xpos
                                  HIWORD(lParam) );   // mouse ypos

        // Appcommand message 
        case WM_APPCOMMAND:
            switch( GET_APPCOMMAND_LPARAM(lParam) )
            { 
                case APPCOMMAND_BROWSER_BACKWARD:
                    return OnAppCommandBrowserBackward( (HWND)wParam, 
                        GET_KEYSTATE_LPARAM(lParam), 
                        GET_DEVICE_LPARAM(lParam) );

                case APPCOMMAND_BROWSER_FORWARD:
                    return OnAppCommandBrowserForward( (HWND)wParam, 
                        GET_KEYSTATE_LPARAM(lParam), 
                        GET_DEVICE_LPARAM(lParam) );
                
                // Handle all remaining applicable appcommands
                // case APPCOMMAND_NEW:
                // and so on...
            }
            break;

        // Settings change message notifies us when change is made 
        // through the SystemParametersInfo() API
        case WM_SETTINGCHANGE:
            return OnSettingChange( (UINT)wParam );

        // Close message 
        case WM_CLOSE:
            return OnClose( hwnd );
    }

    // Clean up any unused messages by calling DefWindowProc
    return DefWindowProc( hwnd, msg, wParam, lParam );
}


//
// Close the application
//
LRESULT OnClose( HWND hwnd )
{
    DestroyWindow( hwnd );
    PostQuitMessage( 0 );
    return 0;
}


//
// Helper function for OnMouseWheel
//
int GetCurrentLinesPerPage()
{
    // Simply return 20 but in a real-world application
    // this value would change to reflect the actual 
    // lines in the current view.
    return 20;
}


//
// Handle the WM_MOUSEWHEEL message and either scroll or zoom.
// This function demonstrates how to handle WM_MOUSEWHEEL when the 
// window/view can only scroll whole lines.  
//
// It is preferable to scroll partial lines, if possible, as 
// shown in the alternate code below.
// 
LRESULT OnMouseWheel( short scrollValue, 
                      WORD keyState, 
                      short xpos, 
                      short ypos )
{
    // Save the last scroll direction and the scroll remainder.
    static bool isScrollDirectionForward = true;
    static float scrollRemainder = 0;

    // if the scroll direction reversed, save the new direction and 
    // zero the remainder.
    bool isCurrentScrollDirectionForward = scrollValue > 0;
    if( isCurrentScrollDirectionForward != isScrollDirectionForward )
    {
        isScrollDirectionForward = !isScrollDirectionForward;
        scrollRemainder = 0;
    }

    // If the setting indicates page scrolling then adjust the value 
    // to equal the number of lines per page.
    if( linesToScrollUserSetting == -1 )
    {
        linesToScrollUserSetting = GetCurrentLinesPerPage();
    }

    // Calculate the lines to scroll and scroll remainder.  For example 
    // purposes, every value shown in Table 1 is calculate here.
    float linesToScroll = ((float)scrollValue / WHEEL_DELTA) * 
        linesToScrollUserSetting;
    float totalLinesToScroll = linesToScroll + scrollRemainder;
    int wholelinesToScroll = (int)totalLinesToScroll;
    scrollRemainder = totalLinesToScroll - wholelinesToScroll;

    // If the CTRL key is down, zoom instead of scroll.
    LRESULT returnValue = 0; 
    if( keyState & MK_CONTROL )
    {
        // Zero the scroll remainder when transitioning to zooming.
        scrollRemainder = 0;

        // Send the linesToScroll value to DoZoom to 
        // indicate direction and amount to zoom.
        returnValue = DoZoom( linesToScroll, xpos, ypos );
    }
    else
    {
        returnValue = DoScroll( (float)wholelinesToScroll, xpos, ypos );
    }

    return returnValue;
}


//
// An alternate function to handle the WM_MOUSEWHEEL message.  
// This version demonstrates how to handle the message when the 
// window/view can scroll partial lines. This is preferable.
//
LRESULT OnMouseWheelAlternate( short scrollValue, 
                               WORD keyState, 
                               short xpos, 
                               short ypos)
{
    // If page scrolling is set, adjust the lines-to-scroll 
    // setting accordingly.
    if( linesToScrollUserSetting == -1 )
    {
        linesToScrollUserSetting = GetCurrentLinesPerPage();
    }

    // Calculate the amount to scroll as a float, because 
    // the view can scroll partial lines.
    float linesToScroll = ((float)scrollValue / WHEEL_DELTA) * 
        linesToScrollUserSetting;

    // If the CTRL key is down, Zoom instead of scroll.
    LRESULT returnValue = 0; 
    if( keyState & MK_CONTROL )
    {
        // Send the linesToScroll value to DoZoom to 
        // indicate direction and amount to zoom.
        returnValue = DoZoom( linesToScroll, xpos, ypos );
    }
    else
    {
        returnValue = DoScroll( linesToScroll, xpos, ypos );
    }

    return returnValue;
}


//
// Handle the WM_MOUSEHWHEEL message and scroll horizontally. This  
// demonstrates how to handle the message when the window/view can 
// scroll partial characters. This is preferable.
//
LRESULT OnHorizontalMouseWheel( short tiltValue, 
                                WORD keyState, 
                                short xpos, 
                                short ypos )
{
    // Calculate the amount to scroll.
    float charsToScroll = ((float)tiltValue / WHEEL_DELTA) * 
        charsToScrollUserSetting;

    return DoHScroll( charsToScroll, xpos, ypos );
}


//
// Handle the WM_XBUTTONDOWN message. Only handle the X button messages
// if the mouse X buttons (buttons four and five) are used for a mouse-
// related action, such as activating a tool in a CAD program. If the X 
// buttons are used for Back and Forward functionality, handle the 
// WM_APPCOMMAND message.
//
LRESULT OnXButtonDown( WORD XButtonID, 
                       WORD keyState, 
                       short xpos, 
                       short ypos )
{
    if( XButtonID == XBUTTON1 )
    {
        // Do the mouse-related action associated with XButton1.
    }
    else if( XButtonID == XBUTTON2 )
    {
        // Do the mouse-related action associated with XButton2.
    }

    // Return TRUE to indicate message was handled.
    return TRUE;
}


//
// Handle the Browser Back appcommand
//
LRESULT OnAppCommandBrowserBackward( HWND hwnd, 
                                     WORD keyState, 
                                     WORD device )
{
    // Navigate backward.
    // Return TRUE to indicate message was handled so IntelliType 
    // Pro will not try to execute the Back command.
    return TRUE;
}


//
// Handle the Browser Forward appcommand
//
LRESULT OnAppCommandBrowserForward( HWND hwnd, 
                                    WORD keyState, 
                                    WORD device ) 
{
    // Navigate forward.
    // Return TRUE to indicate message was handled so IntelliType
    // Pro will not try to execute the Forward command.
    return TRUE;
}


//
//  Helper function for OnSettingChange
//
UINT GetLinesToScrollUserSetting()
{
    UINT userSetting;
 
    // Retrieve the lines-to-scroll user setting. 
    BOOL success = SystemParametersInfo( SPI_GETWHEELSCROLLLINES, 
                                        0, 
                                        &userSetting, 
                                        0 );

    // Use a default value if the API failed.
    if( success == FALSE )
    {
        userSetting = DEFAULT_LINES_TO_SCROLL;
    }

    return userSetting;
}


//
//  Helper function for OnSettingChange
//
UINT GetCharsToScrollUserSetting()
{
    UINT userSetting;

    // Retrieve the characters-to-scroll user setting. 
    BOOL success = SystemParametersInfo( SPI_GETWHEELSCROLLCHARS, 
                                        0, 
                                        &userSetting, 
                                        0 );

    // If SystemParametersInfo fails assign the correct value to 
    // user setting based on the OS version.
    if( success == FALSE )
    {
        // Get the OS version.
        OSVERSIONINFO version = { 0 };
        version.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
GetVersionEx( &version );

        if( version.dwPlatformId == VER_PLATFORM_WIN32_NT && 
            version.dwMajorVersion < 6 )
        {
            // On Windows 2000 and Windows XP set the value to 1 in 
            // order to work correctly with IntelliType Pro and 
            // IntelliPoint emulating the WM_MOUSEWHEEL message.
            userSetting = DEFAULT_CHARS_TO_SCROLL_WITH_EMULATION;
        }
        else
        {
            // On Longhorn and above use the default value of 3.
            userSetting = DEFAULT_CHARS_TO_SCROLL;
        }
    }

    return userSetting;
}


//
// Handle the WM_SETTINGCHANGE message and cache changes to
// SPI_GETWHEELSCROLLLINES and SPI_GETWHEELSCROLLCHARS which indicate 
// the amount to scroll when a scrolling message is handled.
//
LRESULT OnSettingChange( UINT setting )
{
    if( setting == SPI_SETWHEELSCROLLLINES )
    {
        linesToScrollUserSetting = GetLinesToScrollUserSetting();
    }
    else if( setting == SPI_SETWHEELSCROLLCHARS )
    {
        charsToScrollUserSetting = GetCharsToScrollUserSetting();
    }

    return 0;
}


//
// Scroll the view
//
LRESULT DoScroll( float linesToScroll, int xpos, int ypos )
{
    // Use the parameters as needed to scroll the view.
    if( linesToScroll > 0 )
    {
        // Scroll up.
    }
    else
    {
        // Scroll down.
    }

    // Return zero to indicate the message was handled.
    return 0;
}


//
// Zoom the view
//
LRESULT DoZoom( float zoomValue, int xpos, int ypos )
{
    // Use the parameters as needed to zoom the view.
    if( zoomValue > 0 )
    {
        // Zoom in.
    }
    else
    {
        // zoom out.
    }

    // Return zero to indicate the message was handled.
    return 0;
}


//
// Horizontally scroll the view 
//
LRESULT DoHScroll( float charsToScroll, int xpos, int ypos )
{
    // Use the parameters as needed to scroll the view horizontally.
    if( charsToScroll > 0 )
    {
        // HScroll right.
    }
    else
    {
        // Hscroll left.
    }

    // Return TRUE to indicate message was handled 
    // so IntelliType Pro or IntelliPoint will not
    // try to scroll the window/view horizontally.
    return TRUE;
}