摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P255

设计一个更好的捕鼠器,人们自然会不辞辛苦来到你的家门前。”我的母亲曾经这样教导我,不知不觉中阐释了 Emerson 的思想。当然,而今设计一个更好的“鼠标”也许更有价值。

        微软的 IntelliMouse 系列鼠标增加了传统鼠标的功能,在两个按钮中间 添加了一个小滚轮。按下这个滚轮,这是它相当于鼠标的中键;中指滑动这个滚轮,这时它会产生一个特殊的消息,名为 WM_MOUSEWHEEL。程序利用鼠标滚轮,相应的滚动或缩放文档。最初,这听上去就像 一个毫无必要的噱头,但是我必须承认,我很快就习惯了使用鼠标滚轮来滚动浏览 Word 文档和 Internet Explorer。我不想专门去讨论如何使用鼠标滚轮,实际上,这里我只讲述如何给一个现有的程序(比如 SYSMETS4 程序)增加鼠标滚轮处理能力,以便在客户区内滚动数据。最后的 SYSMETS 程序如下所示。

/*----------------------------------------------------
   SYSMETS.C -- Final System Metrics Display Program
                 (c) Charles Petzold, 1998
  ----------------------------------------------------*/

#include <windows.h>
#include "sysmets.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("SysMets") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }

     hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics"),
                          WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static int  cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxWidth ;
     static int  iDeltaPerLine, iAccumDelta;       // for mouse wheel logic
     HDC         hdc ;
     int         i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintEnd ;
     PAINTSTRUCT ps ;
     SCROLLINFO  si ;
     TCHAR       szBuffer [10] ;
     TEXTMETRIC  tm ;
     ULONG       ulScrollLines;         // for mouse wheel logic

     switch (message)
     {
     case WM_CREATE:
          hdc = GetDC (hwnd) ;

          GetTextMetrics (hdc, &tm) ;
          cxChar = tm.tmAveCharWidth ;
          cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
          cyChar = tm.tmHeight + tm.tmExternalLeading ;

          ReleaseDC (hwnd, hdc) ;

                // Save the width of the three columns

          iMaxWidth = 40 * cxChar + 22 * cxCaps;

                // Fall through for mouse wheel information

     case WM_SETTINGCHANGE:
        SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &ulScrollLines, 0);

                // ulScrollLines usually equals 3 or 0 (for no scrolling)
                // WHEEL_DELTA equals 120, so iDeltaPerLine will be 40

        if (ulScrollLines)
            iDeltaPerLine = WHEEL_DELTA / ulScrollLines;
        else
            iDeltaPerLine = 0;

        return 0;

     case WM_SIZE:
          cxClient = LOWORD (lParam) ;
          cyClient = HIWORD (lParam) ;

                // Set Vertical scroll bar range and page size

          si.cbSize     = sizeof (si) ;
          si.fMask      = SIF_RANGE | SIF_PAGE ;

          si.nMin       = 0 ;
          si.nMax       = NUMLINES - 1 ;
          si.nPage      = cyClient / cyChar ;
          SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

                // Set horizontal scroll bar range and page size

          si.cbSize     = sizeof (si) ;
          si.fMask      = SIF_RANGE | SIF_PAGE ;
          si.nMin       = 0;
          si.nMax       = 2 + iMaxWidth / cxChar ;
          si.nPage      = cxClient / cxChar ;
          SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
          return 0 ;

     case WM_VSCROLL:
                // Get all the vertical scroll bar information

          si.cbSize     = sizeof (si);
          si.fMask      = SIF_ALL ;
          GetScrollInfo(hwnd, SB_VERT, &si);

                // Save the position for comparison later on
          iVertPos = si.nPos;
          switch (LOWORD (wParam))
          {
          case SB_TOP:
               si.nPos = si.nMin ;
               break ;
          case SB_BOTTOM:
               si.nPos = si.nMax ;
               break ;
          case SB_LINEUP:
               si.nPos -= 1 ;
               break ;
          case SB_LINEDOWN:
               si.nPos += 1 ;
               break ;
          case SB_PAGEUP:
               si.nPos -= si.nPage ;
               break ;
          case SB_PAGEDOWN:
               si.nPos += si.nPage ;
               break ;
          case SB_THUMBTRACK:
               si.nPos = si.nTrackPos ;
               break ;
          default:
               break ;
          }
                // Set the position and then retrieve it. Due to adjustments
                // by Windows it may not be the same as the value set.

          si.fMask = SIF_POS ;
          SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
          GetScrollInfo(hwnd, SB_VERT, &si);

                // If the position has changed, scroll the window and update it
          if (si.nPos != iVertPos)
          {
              ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos),
                                    NULL, NULL) ;
              UpdateWindow(hwnd) ;
          }
          return 0 ;
     case WM_HSCROLL:
                // Get all the horizontal scroll bar information

          si.cbSize = sizeof (si) ;
          si.fMask  = SIF_ALL ;

                    // Save the position for comparison later on

          GetScrollInfo(hwnd, SB_HORZ, &si);
          iHorzPos = si.nPos ;

          switch (LOWORD (wParam))
          {
          case SB_LINELEFT:
               si.nPos -= 1 ;
               break ;
          case SB_LINERIGHT:
               si.nPos += 1 ;
               break ;
          case SB_PAGELEFT:
               si.nPos -= si.nPage ;
               break ;
          case SB_PAGERIGHT:
               si.nPos += si.nPage ;
               break ;
          case SB_THUMBPOSITION:
               si.nPos = si.nTrackPos ;
               break ;
          default:
               break ;
          }
                // Set the position and then retrieve it. Due to adjustments
                // by Windows it may not be the same as the value set.

          si.fMask = SIF_POS ;
          SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
          GetScrollInfo(hwnd, SB_HORZ, &si);

                // If the position has changed, scroll the window
          if (si.nPos != iHorzPos)
          {
              ScrollWindow(hwnd, cxChar * (iHorzPos - si.nPos), 0,
                                    NULL, NULL) ;
          }
          return 0;

     case WM_KEYDOWN:
          switch (wParam)
          {
          case VK_HOME:
            SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0);
            break;

          case VK_END:
            SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);
            break;

          case VK_PRIOR:
            SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
            break;

          case VK_NEXT:
            SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
            break;

          case VK_UP:
            SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
            break;

          case VK_DOWN:
            SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
            break;

          case VK_LEFT:
            SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0);
            break;

          case VK_RIGHT:
            SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
            break;
          }
          return 0;

     case WM_MOUSEWHEEL:
          if (iDeltaPerLine == 0)
              break;

          iAccumDelta += (short) HIWORD(wParam);        // 120 or -120

          while (iAccumDelta >= iDeltaPerLine)
          {
              SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0);
              iAccumDelta -= iDeltaPerLine;
          }

          while (iAccumDelta <= -iDeltaPerLine)
          {
              SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
              iAccumDelta += iDeltaPerLine;
          }

          return 0;

     case WM_PAINT :
          hdc = BeginPaint (hwnd, &ps) ;

                // Get vertical scroll bar position

          si.cbSize = sizeof (si);
          si.fMask = SIF_POS;
          GetScrollInfo(hwnd, SB_VERT, &si);
          iVertPos = si.nPos;

                // Get horizontal scroll bar position

          GetScrollInfo(hwnd, SB_HORZ, &si);
          iHorzPos = si.nPos;

                // Find painting limits

          iPaintBeg = max (0, iVertPos + ps.rcPaint.top / cyChar) ;
          iPaintEnd = min (NUMLINES - 1, iVertPos + ps.rcPaint.bottom / cyChar);
          for (i = iPaintBeg ; i <= iPaintEnd; i++)
          {
               x = cxChar * (1 - iHorzPos) ;
               y = cyChar * (i - iVertPos) ;

               TextOut (hdc, x, y,
                        sysmetrics[i].szLabel,
                        lstrlen (sysmetrics[i].szLabel)) ;

               TextOut (hdc, x + 22 * cxCaps, y,
                        sysmetrics[i].szDesc,
                        lstrlen (sysmetrics[i].szDesc)) ;

               SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;

               TextOut (hdc, x + 22 * cxCaps + 40 * cxChar, y, szBuffer,
                        wsprintf (szBuffer, TEXT ("%5d"),
                             GetSystemMetrics (sysmetrics[i].iIndex))) ;

               SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
          }
          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY :
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}



       

发送给具有输入焦点的窗口(而不是鼠标指针下面的窗口)。和通常一样,参数 lParam 包含鼠标的位置信息;但是,这些做你是相对于屏幕左上角的坐标,而不是相对客户区的坐标。同样,参数 wParam 的低位字包含一系列标志(flag)。用于指明鼠标按钮、Shift 键和 Ctrl 键的状态。

        新的信息出现在参数 wParam 的高位字。这是一个“增量” (delta)数值,当前它可能等于 120 或 -120,取决于滚轮向前滚动(也就是说,朝鼠标的前端,即带按钮和鼠标线的一端)还是向后滚动。数值 120 或 -120 表示文档分别向上或向下滚动三行。这里有一种构想:未来的鼠标滚动能够实现比当前鼠标更好的移动梯度,比如可以产生增量数值为 40 和 -40 的 WM_MOUSEWHEEL 消息。这些数值能够使文档像上或向下滚动一行。

为了保持程序的通用性,SYSMETS 程序在处理 WM_CREATE 消息和 WM_SETTINGCHANGE 消息时调用了参数为 SPI_GETWHEELSCROLLLINES 的 SystemParametersInfo 函数。这个参数表示了每个增量数值能够滚动多少行,其中增量数值用定义在WINUSER.H 头文件中的 WHEEL_DELTA来标识。WHEEL_DELTA 等于 120,且默认情况下 SystemParametersInfo 返回值为 3,因此,每滚动一行的增量是 40。SYSMETS 程序将这个值保存在 iDeltaPerLine 中。

        在处理 WM_MOUSEWHEEL 消息的过程中,SYSMETS 程序给静态变量 iAccumDelta 增加了一个增量数值。然后,如果 iAccumDelta 大于等于 iDeltaPerLine(或小于等于 -iDeltaPerLine)时,SYMETS 程序就会利用 SB_LINEUP 或 SB_LINEDOWN 产生 WM_VSCROLL 消息。对于每个 WM_VSCROLL 消息,iAccumDelta 都会相应地减去(或加上)iDeltaPerLine 值。此代码允许增量值大于、小于或等于滚动一行所需的增量值。