http://www-user.tu-chemnitz.de/~heha/petzold/
In the previous chapter, you saw how Windows sends keyboard messages only to the window that has the input focus. Mouse messages are different: a window procedure receives mouse messages whenever the mouse passes over the window or is clicked within the window, even if the window is not active or does not have the input focus. Windows defines 21 messages for the mouse. However, 11 of these messages do not relate to the client area. These are called "nonclient-area messages," and Windows applications usually ignore them.
When the mouse is moved over the client area of a window, the window procedure receives the message WM_MOUSEMOVE. When a mouse button is pressed or released within the client area of a window, the window procedure receives the messages in this table:
Button | Pressed | Released | Pressed (Second Click) |
Left | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_LBUTTONDBLCLK |
Middle | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_MBUTTONDBLCLK |
Right | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_RBUTTONDBLCLK |
Your window procedure receives MBUTTON messages only for a three-button mouse and RBUTTON messages only for a two-button mouse. The window procedure receives DBLCLK (double-click) messages only if the window class has been defined to receive them (as described in the section titled "Mouse Double-Clicks").
For all these messages, the value of lParam contains the position of the mouse. The low word is thex-coordinate, and the high word is the y-coordinate relative to the upper left corner of the client area of the window. You can extract these values using the LOWORD and HIWORD macros:
x = LOWORD (lParam) ; y = HIWORD (lParam) ;
The value of wParam indicates the state of the mouse buttons and the Shift and Ctrl keys. You can testwParam using these bit masks defined in the WINUSER.H header file. The MK prefix stands for "mouse key."
MK_LBUTTON Left button is down MK_MBUTTON Middle button is down MK_RBUTTON Right button is down MK_SHIFT Shift key is down MK_CONTROL Ctrl key is down
For example, if you receive a WM_LBUTTONDOWN message, and if the value
wparam & MK_SHIFT
is TRUE (nonzero), you know that the Shift key was down when the left button was pressed.
As you move the mouse over the client area of a window, Windows does not generate a WM_MOUSEMOVE message for every possible pixel position of the mouse. The number of WM_MOUSEMOVE messages your program receives depends on the mouse hardware and on the speed at which your window procedure can process the mouse movement messages. In other words, Windows does not fill up a message queue with unprocessed WM_MOUSEMOVE messages. You'll get a good idea of the rate of WM_MOUSEMOVE messages when you experiment with the CONNECT program described below.
If you click the left mouse button in the client area of an inactive window, Windows changes the active window to the window that is being clicked and then passes the WM_LBUTTONDOWN message to the window procedure. When your window procedure gets a WM_LBUTTONDOWN message, your program can safely assume the window is active. However, your window procedure can receive a WM_LBUTTONUP message without first receiving a WM_LBUTTONDOWN message. This can happen if the mouse button is pressed in one window, moved to your window, and released. Similarly, the window procedure can receive a WM_LBUTTONDOWN without a corresponding WM_LBUTTONUP message if the mouse button is released while positioned over another window.
There are two exceptions to these rules:
The CONNECT program, shown in Figure 7-1, does some simple mouse processing to let you get a good feel for how Windows sends mouse messages to your program.
Figure 7-1. The CONNECT program.
CONNECT.C/*-------------------------------------------------- CONNECT.C -- Connect-the-Dots Mouse Demo Program (c) Charles Petzold, 1998 --------------------------------------------------*/ #include <windows.h> #define MAXPOINTS 1000 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Connect") ; 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 ("Program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Connect-the-Points Mouse Demo"), WS_OVERLAPPEDWINDOW, 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 POINT pt[MAXPOINTS] ; static int iCount ; HDC hdc ; int i, j ; PAINTSTRUCT ps ; switch (message) { case WM_LBUTTONDOWN: iCount = 0 ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case WM_MOUSEMOVE: if (wParam & MK_LBUTTON && iCount < 1000) { pt[iCount ].x = LOWORD (lParam) ; pt[iCount++].y = HIWORD (lParam) ; hdc = GetDC (hwnd) ; SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0) ; ReleaseDC (hwnd, hdc) ; } return 0 ; case WM_LBUTTONUP: InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; for (i = 0 ; i < iCount - 1 ; i++) for (j = i + 1 ; j < iCount ; j++) { MoveToEx (hdc, pt[i].x, pt[i].y, NULL) ; LineTo (hdc, pt[j].x, pt[j].y) ; } ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } |
CONNECT processes three mouse messages:
Figure 7-2. The CONNECT display.
To use CONNECT, bring the mouse cursor into the client area, press the left button, move the mouse around a little, and then release the left button. CONNECT works best for a curved pattern of a few dots, which you can draw by moving the mouse quickly while the left button is depressed.
CONNECT uses three GDI function calls that I discussed in Chapter 5: SetPixel draws a black pixel for each WM_MOUSEMOVE message when the left mouse button is depressed. (On high-resolution displays, these pixels might be nearly invisible.) Drawing the lines requiresMoveToEx and LineTo.
If you move the mouse cursor out of the client area before releasing the button, CONNECT does not connect the dots because it doesn't receive the WM_LBUTTONUP message. If you move the mouse back into the client area and press the left button again, CONNECT clears the client area. If you want to continue a design after releasing the button outside the client area, press the left button again while the mouse is outside the client area and then move the mouse back inside.
CONNECT stores a maximum of 1000 points. If the number of points is P, the number of lines CONNECT draws is equal to P × (P - 1) / 2. With 1000 points, this involves almost 500,000 lines, which might take a minute or so to draw, depending on your hardware. Because Windows 98 is a preemptive multitasking environment, you can switch to other programs at this time. However, you can't do anything else with the CONNECT program (such as move it or change the size) while the program is busy. InChapter 20, we'll examine methods for dealing with problems such as this.
Because CONNECT might take some time to draw the lines, it switches to an hourglass cursor and then back again while processing the WM_PAINT message. This requires two calls to theSetCursor function using two stock cursors. CONNECT also calls ShowCursor twice, once with a TRUE parameter and the second time with a FALSE parameter. I'll discuss these calls in more detail later in this chapter, in the section "Emulating the Mouse with the Keyboard".
Sometimes the word "tracking" is used to refer to the way that programs process mouse movement. Tracking does not mean, however, that your program sits in a loop in its window procedure while attempting to follow the mouse's movements on the display. The window procedure instead processes each mouse message as it comes and then quickly returns control to Windows.
When CONNECT receives a WM_MOUSEMOVE message, it performs a bitwise AND operation on the value ofwParam and MK_LBUTTON to determine if the left button is depressed. You can also usewParam to determine the state of the Shift keys. For instance, if processing must be dependent on the status of the Shift and Ctrl keys, you might use logic that looks like this:
if (wParam & MK_SHIFT) { if (wParam & MK_CONTROL) { [Shift and Ctrl keys are down] } else { [Shift key is down] } { else { if (wParam & MK_CONTROL] { [Ctrl key is down] } else { [neither Shift nor Ctrl key is down] } }
If you want to use both the left and right mouse buttons in your program, and if you also want to accommodate those users with a one-button mouse, you can write your code so that Shift in combination with the left button is equivalent to the right button. In that case, your mouse button-click processing might look something like this:
case WM_LBUTTONDOWN: if (!(wParam & MK_SHIFT)) { [left button logic] return 0 ; } // Fall through case WM_RBUTTONDOWN: [right button logic] return 0 ;
The Window function GetKeyState (described in Chapter 6) can also return the status of the mouse buttons or shift keys using the virtual key codes VK_LBUTTON, VK_RBUTTON, VK_MBUTTON, VK_SHIFT, and VK_CONTROL. The button or key is down if the value returned fromGetKeyState is negative. Because GetKeyState returns mouse or key states as of the message currently being processed, the status information is properly synchronized with the messages. Just as you cannot useGetKeyState for a key that has yet to be pressed, you cannot use it for a mouse button that has yet to be pressed. Don't do this:
while (GetKeyState (VK_LBUTTON) >= 0) ; // WRONG !!!
The GetKeyState function will report that the left button is depressed only if the button is already depressed when you process the message during which you callGetKeyState.
A mouse double-click is two clicks in quick succession. To qualify as a double-click, the two clicks must occur in close physical proximity of one another (by default, about an area as wide as an average system font character and half as high) and within a specific interval of time called the "double-click speed." You can change that time interval in the Control Panel.
If you want your window procedure to receive double-click mouse messages, you must include the identifier CS_DBLCLKS when initializing the style field in the window class structure before callingRegisterClass:
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
If you do not include CS_DBLCLKS in the window style and the user clicks the left mouse button twice in quick succession, your window procedure receives these messages:
WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDOWN WM_LBUTTONUP
The window procedure might also receive other messages between these button messages. If you want to implement your own double-click logic, you can use the Windows functionGetMessageTime to obtain the relative times of the WM_LBUTTONDOWN messages. This function is discussed in more detail inChapter 8.
If you include CS_DBLCLKS in your window class style, the window procedure receives these messages for a double-click:
WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK WM_LBUTTONUP
The WM_LBUTTONDBLCLK message simply replaces the second WM_LBUTTONDOWN message.
Double-click messages are much easier to process if the first click of a double-click performs the same action as a single click. The second click (the WM_LBUTTONDBLCLK message) then does something in addition to the first click. For example, look at how the mouse works with the file lists in Windows Explorer. A single click selects the file. Windows Explorer highlights the file with a reverse-video bar. A double-click performs two actions: the first click selects the file, just as a single click does; the second click directs Windows Explorer to open the file. That's fairly easy logic. Mouse-handling logic could get more complex if the first click of a double-click did not perform the same action as a single click.