The WM_NCHITTEST Message

类别:VC语言 点击:0 评论:0 推荐:
  The WM_NCHITTEST Message

Before a window receives a client-area or nonclient-area mouse message, it receives a WM_NCHITTEST message accompanied by the cursor's screen coordinates. Most applications don't process WM_NCHITTEST messages, instead electing to let Windows process them. When Windows processes a WM_NCHITTEST message, it uses the cursor coordinates to determine what part of the window the cursor is over and then generates either a client-area or nonclient-area mouse message.

One clever use of an OnNcHitTest handler is for substituting the HTCAPTION hit-test code for HTCLIENT, which creates a window that can be dragged by its client area:

// In CMainWindow's message map ON_WM_NCHITTEST () UINT CMainWindow::OnNcHitTest (CPoint point) { UINT nHitTest = CFrameWnd::OnNcHitTest (point); if (nHitTest == HTCLIENT) nHitTest = HTCAPTION; return nHitTest; }

As this example demonstrates, WM_NCHITTEST messages that you don't process yourself should be forwarded to the base class so that other aspects of the program's operation aren't affected.

The WM_MOUSELEAVE and WM_MOUSEHOVER Messages

It's easy to tell when the cursor enters a window or moves over it because the window receives WM_MOUSEMOVE messages. The ::TrackMouseEvent function, which debuted in Windows NT 4.0 and is also supported in Windows 98, makes it equally easy to determine when the cursor leaves a window or hovers motionlessly over the top of it. With ::TrackMouseEvent, an application can register to receive WM_MOUSELEAVE messages when the cursor leaves a window and WM_MOUSEHOVER messages when the cursor hovers over a window.

::TrackMouseEvent accepts just one parameter: a pointer to a TRACKMOUSEEVENT structure. The structure is defined this way in Winuser.h:

typedef struct tagTRACKMOUSEEVENT { DWORD cbSize; DWORD dwFlags; HWND hwndTrack; DWORD dwHoverTime; } TRACKMOUSEEVENT;

cbSize holds the size of the structure. dwFlags holds bit flags specifying what the caller wants to do: register to receive WM_MOUSELEAVE messages (TME_LEAVE), register to receive WM_MOUSEHOVER messages (TME_HOVER), cancel WM_MOUSELEAVE and WM_MOUSEHOVER messages (TME_CANCEL), or have the system fill the TRACKMOUSEEVENT structure with the current ::TrackMouseEvent settings (TME_QUERY). hwndTrack is the handle of the window for which WM_MOUSELEAVE and WM_MOUSEHOVER messages are generated. dwHoverTime is the length of time in milliseconds that the cursor must pause before a WM_MOUSEHOVER message is sent to the underlying window. You can accept the system default of 400 milliseconds by setting dwHoverTime equal to HOVER_DEFAULT.

The cursor doesn't have to be perfectly still for the system to generate a WM_MOUSEHOVER message. If the cursor stays within a rectangle whose width and height equal the values returned by ::SystemParametersInfo when it's called with SPI_GETMOUSEHOVERWIDTH and SPI_GETMOUSEHOVERHEIGHT values, and if it stays there for the number of milliseconds returned by ::SystemParametersInfo when it's called with an SPI_GETMOUSEHOVERTIME value, a WM_MOUSEHOVER message ensues. If you want, you can change these parameters by calling ::SystemParametersInfo with SPI_SETMOUSEHOVERWIDTH, SPI_SETMOUSEHOVERHEIGHT, and SPI_SETMOUSEHOVERTIME values.

One of the more interesting aspects of ::TrackMouseEvent is that its effects are cancelled when a WM_MOUSELEAVE or WM_MOUSEHOVER message is generated. This means that if you want to receive these message anytime the cursor exits or pauses over a window, you must call ::TrackMouseEvent again whenever a WM_MOUSELEAVE or WM_MOUSEHOVER message is received. To illustrate, the following code snippet writes "Mouse enter," "Mouse leave," or "Mouse hover" to the debug output window anytime the mouse enters, leaves, or pauses over a window. m_bMouseOver is a BOOL CMainWindow member variable. It should be set to FALSE in the class constructor:

// In the message map ON_WM_MOUSEMOVE () ON_MESSAGE (WM_MOUSELEAVE, OnMouseLeave) ON_MESSAGE (WM_MOUSEHOVER, OnMouseHover) void CMainWindow::OnMouseMove (UINT nFlags, CPoint point) { if (!m_bMouseOver) { TRACE (_T ("Mouse enter\n")); m_bMouseOver = TRUE; TRACKMOUSEEVENT tme; tme.cbSize = sizeof (tme); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.hwndTrack = m_hWnd; tme.dwHoverTime = HOVER_DEFAULT; ::TrackMouseEvent (&tme); } } LRESULT CMainWindow::OnMouseLeave (WPARAM wParam, LPARAM lParam) { TRACE (_T ("Mouse leave\n")); m_bMouseOver = FALSE; return 0; } LRESULT CMainWindow::OnMouseHover (WPARAM wParam, LPARAM lParam) { TRACE (_T ("Mouse hover (x=%d, y=%d)\n"), LOWORD (lParam), HIWORD (lParam)); TRACKMOUSEEVENT tme; tme.cbSize = sizeof (tme); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.hwndTrack = m_hWnd; tme.dwHoverTime = HOVER_DEFAULT; ::TrackMouseEvent (&tme); return 0; }

MFC doesn't provide type-specific message-mapping macros for WM_MOUSELEAVE and WM_MOUSEHOVER messages, so as this example demonstrates, you must use the ON_MESSAGE macro to link these messages to class member functions. The lParam value accompanying a WM_MOUSEHOVER message holds the cursor's x coordinate in its low word and the cursor's y coordinate in its high word. wParam is unused. Both wParam and lParam are unused in WM_MOUSELEAVE messages.

One final note regarding ::TrackMouseEvent: In order to use it, you must include the following #define in your source code:

#define _WIN32_WINNT 0x0400

Be sure to include this line before the line that #includes Afxwin.h. Otherwise, it will have no effect.

The Mouse Wheel

Many of the mice used with Windows today include a wheel that can be used to scroll a window without clicking the scroll bar. When the wheel is rolled, the window with the input focus receives WM_MOUSEWHEEL messages. MFC's CScrollView class provides a default handler for these messages that automatically scrolls the window, but if you want mouse wheel messages to scroll a non-CScrollView window, you must process WM_MOUSEWHEEL messages yourself.

MFC's ON_WM_MOUSEWHEEL macro maps WM_MOUSEWHEEL messages to the message handler OnMouseWheel. OnMouseWheel is prototyped like this:

BOOL OnMouseWheel (UINT nFlags, short zDelta, CPoint point)

The nFlags and point parameters are identical to those passed to OnLButtonDown. zDelta is the distance the wheel was rotated. A zDelta equal to WHEEL_DELTA (120) means the wheel was rotated forward one increment, or notch, and _WHEEL_DELTA means the wheel was rotated backward one notch. If the wheel is rotated forward five notches, the window will receive five WM_MOUSEWHEEL messages, each with a zDelta of WHEEL_DELTA. OnMouseWheel should return a nonzero value if it scrolled the window, or zero if it did not.

A simple way to respond to a WM_MOUSEWHEEL message is to scroll the window one line up (if zDelta is positive) or one line down (if zDelta is negative) for every WHEEL_DELTA unit. The recommended approach, however, is slightly more involved. First you ask the system for the number of lines that corresponds to WHEEL_DELTA units. In Windows NT 4.0 and higher and in Windows 98, you can get this value by calling ::SystemParametersInfo with a first parameter equal to SPI_GETWHEELSCROLLLINES. Then you multiply the result by zDelta and divide by WHEEL_DELTA to determine how many lines to scroll. You can modify the Accel program presented in Chapter 2 to respond to WM_MOUSEWHEEL messages in this manner by adding the following message-map entry and message handler to CMainWindow:

// In the message map ON_WM_MOUSEWHEEL () BOOL CMainWindow::OnMouseWheel (UINT nFlags, short zDelta, CPoint point) { BOOL bUp = TRUE; int nDelta = zDelta; if (zDelta < 0) { bUp = FALSE; nDelta = -nDelta; } UINT nWheelScrollLines; ::SystemParametersInfo (SPI_GETWHEELSCROLLLINES, 0, &nWheelScrollLines, 0); if (nWheelScrollLines == WHEEL_PAGESCROLL) { SendMessage (WM_VSCROLL, MAKEWPARAM (bUp ? SB_PAGEUP : SB_PAGEDOWN, 0), 0); } else { int nLines = (nDelta * nWheelScrollLines) / WHEEL_DELTA; while (nLines--) SendMessage (WM_VSCROLL, MAKEWPARAM (bUp ? SB_LINEUP : SB_LINEDOWN, 0), 0); } return TRUE; }

Dividing zDelta by WHEEL_DELTA ensures that the application won't scroll too quickly if, in the future, it's used with a mouse that has a wheel granularity less than 120 units. WHEEL_PAGESCROLL is a special value that indicates the application should simulate a click of the scroll bar shaft—in other words, perform a page-up or page-down. Both WHEEL_DELTA and WHEEL_PAGESCROLL are defined in Winuser.h.

One issue to be aware of regarding this code sample is that it's not compatible with Windows 95. Why? Because calling ::SystemParametersInfo with an SPI_GETWHEELSCROLLLINES value does nothing in Windows 95. If you want to support Windows 95, you can either assume that ::SystemParametersInfo would return 3 (the default) or resort to more elaborate means to obtain the user's preference. MFC uses an internal function named _AfxGetMouseScrollLines to get this value. _AfxGetMouseScrollLines is platform-neutral; it uses various methods to attempt to obtain a scroll line count and defaults to 3 if none of those methods work. See the MFC source code file Viewscrl.cpp if you'd like to mimic that behavior in your code.

If the mouse wheel is clicked rather than rotated, the window under the cursor generally receives middle-button mouse messages—WM_MBUTTONDOWN messages when the wheel is pressed, WM_MBUTTONUP messages when the wheel is released. (I say "generally" because this is the default behavior; it can be changed through the Control Panel.) Some applications respond to wheel clicks in a special way. Microsoft Word 97, for example, scrolls the currently displayed document when it receives WM_MOUSEMOVE messages with the wheel held down. Knowing that the mouse wheel produces middle-button messages, you can customize your applications to respond to mouse wheel events any way you see fit.

Capturing the Mouse

One problem that frequently crops up in programs that process mouse messages is that the receipt of a button-down message doesn't necessarily mean that a button-up message will follow. Suppose you've written a drawing program that saves the point parameter passed to OnLButtonDown and uses it as an anchor point to draw a line whose other endpoint follows the cursor—an action known as "rubber-banding" a line. When a WM_LBUTTONUP message arrives, the application erases the rubber-band line and draws a real line in its place. But what happens if the user moves the mouse outside the window's client area before releasing the mouse button? The application never gets that WM_LBUTTONUP message, so the rubber-band line is left hanging in limbo and the real line isn't drawn.

Windows provides an elegant solution to this problem by allowing an application to "capture" the mouse upon receiving a button-down message and to continue receiving mouse messages no matter where the cursor goes on the screen until the button is released or the capture is canceled. (In the Win32 environment, to prevent applications from monopolizing the mouse, the system stops sending mouse messages to a window that owns the capture if the button is released.) The mouse is captured with CWnd::SetCapture and released with ::ReleaseCapture. Calls to these functions are normally paired in button-down and button-up handlers, as shown here:

// In CMainWindow's message map ON_WM_LBUTTONDOWN () ON_WM_LBUTTONUP () void CMainWindow::OnLButtonDown (UINT nFlags, CPoint point) { SetCapture (); } void CMainWindow::OnLButtonUp (UINT nFlags, CPoint point) { ::ReleaseCapture (); }

In between, CMainWindow receives WM_MOUSEMOVE messages that report the cursor position even if the cursor leaves it. Client-area mouse messages continue to report cursor positions in client coordinates, but coordinates can now go negative and can also exceed the dimensions of the window's client area.

A related function, CWnd::GetCapture, returns a CWnd pointer to the window that owns the capture. In the Win32 environment, GetCapture returns NULL if the mouse is not captured or if it's captured by a window belonging to another thread. The most common use of GetCapture is for determining whether your own window has captured the mouse. The statement

if (GetCapture () == this)

is true if and only if the window identified by this currently has the mouse captured.

How does capturing the mouse solve the problem with the rubber-banded line? By capturing the mouse in response to a WM_LBUTTONDOWN message and releasing it when a WM_LBUTTONUP message arrives, you're guaranteed to get the WM_LBUTTONUP message when the mouse button is released. The sample program in the next section illustrates the practical effect of this technique.

Mouse Capturing in Action

The MouseCap application shown in Figure 3-4 is a rudimentary paint program that lets the user draw lines with the mouse. To draw a line, press the left mouse button anywhere in the window's client area and drag the cursor with the button held down. As the mouse is moved, a thin line is rubber-banded between the anchor point and the cursor. When the mouse button is released, the rubber-band line is erased and a red line 16 pixels wide is drawn in its place. Because the mouse is captured while the button is depressed, rubber-banding works even if the mouse is moved outside the window. And no matter where the cursor is when the mouse button is released, a red line is drawn between the anchor point and the endpoint. MouseCap's source code appears in Figure 3-5.

Figure 3-4. The MouseCap window.

Figure 3-5. The MouseCap application.

MouseCap.h

class CMyApp : public CWinApp { public: virtual BOOL InitInstance (); }; class CMainWindow : public CFrameWnd { protected: BOOL m_bTracking; // TRUE if rubber banding BOOL m_bCaptureEnabled; // TRUE if capture enabled CPoint m_ptFrom; // "From" point for rubber banding CPoint m_ptTo; // "To" point for rubber banding void InvertLine (CDC* pDC, CPoint ptFrom, CPoint ptTo); public: CMainWindow (); protected: afx_msg void OnLButtonDown (UINT nFlags, CPoint point); afx_msg void OnLButtonUp (UINT nFlags, CPoint point); afx_msg void OnMouseMove (UINT nFlags, CPoint point); afx_msg void OnNcLButtonDown (UINT nHitTest, CPoint point); DECLARE_MESSAGE_MAP () };

 

MouseCap.cpp

#include <afxwin.h> #include "MouseCap.h" CMyApp myApp; ///////////////////////////////////////////////////////////////////////// // CMyApp member functions BOOL CMyApp::InitInstance () { m_pMainWnd = new CMainWindow; m_pMainWnd->ShowWindow (m_nCmdShow); m_pMainWnd->UpdateWindow (); return TRUE; } ///////////////////////////////////////////////////////////////////////// // CMainWindow message map and member functions BEGIN_MESSAGE_MAP (CMainWindow, CFrameWnd) ON_WM_LBUTTONDOWN () ON_WM_LBUTTONUP () ON_WM_MOUSEMOVE () ON_WM_NCLBUTTONDOWN () END_MESSAGE_MAP () CMainWindow::CMainWindow () { m_bTracking = FALSE; m_bCaptureEnabled = TRUE; // // Register a WNDCLASS. // CString strWndClass = AfxRegisterWndClass ( 0, AfxGetApp ()->LoadStandardCursor (IDC_CROSS), (HBRUSH) (COLOR_WINDOW + 1), AfxGetApp ()->LoadStandardIcon (IDI_WINLOGO) ); // // Create a window. // Create (strWndClass, _T ("Mouse Capture Demo (Capture Enabled)")); } void CMainWindow::OnLButtonDown (UINT nFlags, CPoint point) { // // Record the anchor point and set the tracking flag. // m_ptFrom = point; m_ptTo = point; m_bTracking = TRUE; // // If capture is enabled, capture the mouse. // if (m_bCaptureEnabled) SetCapture (); } void CMainWindow::OnMouseMove (UINT nFlags, CPoint point) { // // If the mouse is moved while we're "tracking" (that is, while a // line is being rubber-banded), erase the old rubber-band line and // draw a new one. // if (m_bTracking) { CClientDC dc (this); InvertLine (&dc, m_ptFrom, m_ptTo); InvertLine (&dc, m_ptFrom, point); m_ptTo = point; } } void CMainWindow::OnLButtonUp (UINT nFlags, CPoint point) { // // If the left mouse button is released while we're tracking, release // the mouse if it's currently captured, erase the last rubber-band // line, and draw a thick red line in its place. // if (m_bTracking) { m_bTracking = FALSE; if (GetCapture () == this) ::ReleaseCapture (); CClientDC dc (this); InvertLine (&dc, m_ptFrom, m_ptTo); CPen pen (PS_SOLID, 16, RGB (255, 0, 0)); dc.SelectObject (&pen); dc.MoveTo (m_ptFrom); dc.LineTo (point); } } void CMainWindow::OnNcLButtonDown (UINT nHitTest, CPoint point) { // // When the window's title bar is clicked with the left mouse button, // toggle the capture flag on or off and update the window title. // if (nHitTest == HTCAPTION) { m_bCaptureEnabled = m_bCaptureEnabled ? FALSE : TRUE; SetWindowText (m_bCaptureEnabled ? _T ("Mouse Capture Demo (Capture Enabled)") : _T ("Mouse Capture Demo (Capture Disabled)")); } CFrameWnd::OnNcLButtonDown (nHitTest, point); } void CMainWindow::InvertLine (CDC* pDC, CPoint ptFrom, CPoint ptTo) { // //Invert a line of pixels by drawing a line in the R2_NOT drawing mode. // int nOldMode = pDC->SetROP2 (R2_NOT); pDC->MoveTo (ptFrom); pDC->LineTo (ptTo); pDC->SetROP2 (nOldMode); }

Most of the action takes place in the program's OnLButtonDown, OnMouseMove, and OnLButtonUp handlers. OnLButtonDown starts the drawing process by initializing a trio of variables that are members of the CMainWindow class:

m_ptFrom = point; m_ptTo = point; m_bTracking = TRUE;

m_ptFrom and m_ptTo are the starting and ending points for the rubber-band line. m_ptTo is continually updated by the OnMouseMove handler as the mouse is moved. m_bTracking, which is TRUE when the left button is down and FALSE when it is not, is a flag that tells OnMouseMove and OnLButtonUp whether a line is being rubber-banded. OnLButtonDown's only other action is to capture the mouse if m_bCaptureEnabled is TRUE:

if (m_bCaptureEnabled) SetCapture ();

m_bCaptureEnabled is initialized to TRUE by CMainWindow's constructor. It is toggled by the window's OnNcLButtonDown handler so that you can turn mouse capturing on and off and see the effect that mouse capturing has on the program's operation. (More on this in a moment.)

OnMouseMove's job is to move the rubber-band line and update m_ptTo with the new cursor position whenever the mouse is moved. The statement

InvertLine (&dc, m_ptFrom, m_ptTo);

erases the previously drawn rubber-band line, and

InvertLine (&dc, m_ptFrom, point);

draws a new one. InvertLine is a member of CMainWindow. It draws a line not by setting each pixel to a certain color, but by inverting the existing pixel colors. This ensures that the line can be seen no matter what background it is drawn against and that drawing the line again in the same location will erase it by restoring the original screen colors. The inversion is accomplished by setting the device context's drawing mode to R2_NOT with the statement

int nOldMode = pDC->SetROP2 (R2_NOT);

See Chapter 2 for a discussion of R2_NOT and other drawing modes.

When the left mouse button is released, CMainWindow::OnLButtonUp is called. After setting m_bTracking to FALSE and releasing the mouse, it erases the rubber-band line drawn by the last call to OnMouseMove:

CClientDC dc (this); InvertLine (&dc, m_ptFrom, m_ptTo);

OnLButtonUp then creates a solid red pen 16 pixels wide, selects it into the device context, and draws a thick red line:

CPen pen (PS_SOLID, 16, RGB (255, 0, 0)); dc.SelectObject (&pen); dc.MoveTo (m_ptFrom); dc.LineTo (point);

Its work done, OnLButtonUp returns, and the drawing operation is complete. Figure 3-4 above shows what the MouseCap window looks like after a few lines have been drawn and as a new line is rubber-banded.

After you've played around with the program a bit, click the title bar to activate the OnNcLButtonDown handler and toggle the m_bCaptureEnabled flag from TRUE to FALSE. The window title should change from "Mouse Capture Demo (Capture Enabled)" to "Mouse Capture Demo (Capture Disabled)." OnNcLButtonDown processes left button clicks in the nonclient area and uses CWnd::SetWindowText to change the window title if the hit-test code in nHitTest is equal to HTCAPTION, indicating that the click occurred in the title bar.

Now draw a few lines with mouse capturing disabled. Observe that if you move the mouse outside the window while rubber-banding, the line freezes until the mouse reenters the client area, and that if you release the mouse button outside the window, the program gets out of sync. The rubber-band line follows the mouse when you move it back to the interior of the window (even though the mouse button is no longer pressed), and it never gets erased. Click the title bar once again to reenable mouse capturing, and the program will revert to its normal self.

The Cursor

Rather than use the arrow-shaped cursor you see in most Windows applications, MouseCap uses a crosshair cursor. Arrows and crosshairs are just two of several predefined cursor types that Windows places at your disposal, and if none of the predefined cursors fits the bill, you can always create your own. As usual, Windows gives programmers a great deal of latitude in this area.

First, a bit of background on how cursors work. As you know, every window has a corresponding WNDCLASS whose characteristics are defined in a WNDCLASS structure. One of the fields of the WNDCLASS structure is hCursor, which holds the handle of the class cursor—the image displayed when the cursor is over a window's client area. When the mouse is moved, Windows erases the cursor from its old location by redrawing the background behind it. Then it sends the window under the cursor a WM_SETCURSOR message containing a hit-test code. The system's default response to this message is to call ::SetCursor to display the class cursor if the hit-test code is HTCLIENT or to display an arrow if the hit-test code indicates that the cursor is outside the client area. As a result, the cursor is automatically updated as it is moved about the screen. When you move the cursor into an edit control, for example, it changes into a vertical bar or "I-beam" cursor. This happens because Windows registers a special WNDCLASS for edit controls and specifies the I-beam cursor as the class cursor.

It follows that one way to change the cursor's appearance is to register a WNDCLASS and specify the desired cursor type as the class cursor. In MouseCap, CMainWindow's constructor registers a WNDCLASS whose class cursor is IDC_CROSS and passes the WNDCLASS name to CFrameWnd::Create:

CString strWndClass = AfxRegisterWndClass ( 0, AfxGetApp ()->LoadStandardCursor (IDC_CROSS), (HBRUSH) (COLOR_WINDOW + 1), AfxGetApp ()->LoadStandardIcon (IDI_WINLOGO) ); Create (strWndClass, _T ("Mouse Capture Demo (Capture Enabled)"));

Windows then displays a crosshair cursor anytime the mouse pointer is positioned in CMainWindow's client area.

A second way to customize the cursor is to call the API function ::SetCursor in response to WM_SETCURSOR messages. The following OnSetCursor function displays the cursor whose handle is stored in CMainWindow::m_hCursor when the cursor is over CMainWindow's client area:

// In CMainWindow's message map ON_WM_SETCURSOR () BOOL CMainWindow::OnSetCursor (CWnd* pWnd, UINT nHitTest, UINT message) { if (nHitTest == HTCLIENT) { ::SetCursor (m_hCursor); return TRUE; } return CFrameWnd::OnSetCursor (pWnd, nHitTest, message); }

Returning TRUE after calling ::SetCursor tells Windows that the cursor has been set. WM_SETCURSOR messages generated outside the window's client area are passed to the base class so that the default cursor is displayed. The class cursor is ignored because OnSetCursor never gives Windows the opportunity to display it.

Why would you want to use OnSetCursor rather than just registering m_hCursor as the class cursor? Suppose you want to display an arrow cursor when the cursor is in the top half of the window and an I-beam cursor when the cursor is in the bottom half. A class cursor won't suffice in this case, but OnSetCursor will do the job quite nicely. The following OnSetCursor handler sets the cursor to either m_hCursorArrow or m_hCursorIBeam when the cursor is in CMainWindow's client area:

BOOL CMainWindow::OnSetCursor (CWnd* pWnd, UINT nHitTest, UINT message) { if (nHitTest == HTCLIENT) { DWORD dwPos = ::GetMessagePos (); CPoint point (LOWORD (dwPos), HIWORD (dwPos)); ScreenToClient (&point); CRect rect; GetClientRect (&rect); ::SetCursor ((point.y < rect.Height () / 2) ? m_hCursorArrow : m_hCursorIBeam); return TRUE; } return CFrameWnd::OnSetCursor (pWnd, nHitTest, message); }

::GetMessagePos returns a DWORD value containing the cursor's x and y screen coordinates at the moment the WM_SETCURSOR message was retrieved from the message queue. CWnd::ScreenToClient converts screen coordinates to client coordinates. If the converted point's y coordinate is less than half the height of the window's client area, the cursor is set to m_hCursorArrow. But if y is greater than or equal to half the client area height, the cursor is set to m_hCursorIBeam instead. The VisualKB application presented later in this chapter uses a similar technique to change the cursor to an I-beam when it enters a rectangle surrounding a text-entry field.

Should the need ever arise, you can hide the cursor with the statement

::ShowCursor (FALSE);

and display it again with

::ShowCursor (TRUE);

Internally, Windows maintains a display count that's incremented each time ::ShowCursor (TRUE) is called and decremented by each call to ::ShowCursor (FALSE). The count is initially set to 0 if a mouse is installed and to -1 if no mouse is present, and the cursor is displayed whenever the count is greater than or equal to 0. Thus, if you call ::ShowCursor (FALSE) twice to hide the cursor, you must call ::ShowCursor (TRUE) twice to display it again.

The Hourglass Cursor

When an application responds to a message by undertaking a lengthy processing task, it's customary to change the cursor to an hourglass to remind the user that the application is "busy." (While a message handler executes, no further messages are retrieved from the message queue and the program is frozen to input. In Chapter 17, you'll learn about ways to perform background processing tasks while continuing to retrieve and dispatch messages.)

Windows provides the hourglass cursor for you; its identifier is IDC_WAIT. An easy way to display an hourglass cursor is to declare a CWaitCursor variable on the stack, like this:

CWaitCursor wait;

CWaitCursor's constructor displays an hourglass cursor, and its destructor restores the original cursor. If you'd like to restore the cursor before the variable goes out of scope, simply call CWaitCursor::Restore:

wait.Restore ();

You should call Restore before taking any action that would allow a WM_SETCURSOR message to seep through and destroy the hourglass—for example, before displaying a message box or a dialog box.

You can change the cursor displayed by CWaitCursor::CWaitCursor and BeginWaitCursor by overriding CWinApp's virtual DoWaitCursor function. Use the default implementation of CWinApp::DoWaitCursor found in the MFC source code file Appui.cpp as a model for your own implementations.

Mouse Miscellanea

As mentioned earlier, calling the ::GetSystemMetrics API function with an SM_CMOUSEBUTTONS argument queries the system for the number of mouse buttons. (There is no MFC equivalent to ::GetSystemMetrics, so you must call it directly.) The usual return value is 1, 2, or 3, but a 0 return means no mouse is attached. You can also find out whether a mouse is present by calling ::GetSystemMetrics this way:

::GetSystemMetrics (SM_MOUSEPRESENT)

The return value is nonzero if there is a mouse attached, 0 if there is not. In the early days of Windows, programmers had to consider the possibility that someone might be using Windows without a mouse. Today that's rarely a concern, and a program that queries the system to determine whether a mouse is present is a rare program indeed.

Other mouse-related ::GetSystemMetrics parameters include SM_CXDOUBLECLK and SM_CYDOUBLECLK, which specify the maximum horizontal and vertical distances (in pixels) that can separate the two halves of a double click, and SM_SWAPBUTTON, which returns a nonzero value if the user has swapped the left and right mouse buttons using the Control Panel. When the mouse buttons are swapped, the left mouse button generates WM_RBUTTON messages and the right mouse button generates WM_LBUTTON messages. Generally you don't need to be concerned about this, but if for some reason your application wants to be sure that the left mouse button really means the left mouse button, it can use ::GetSystemMetrics to determine whether the buttons have been swapped.

The API functions ::SetDoubleClickTime and ::GetDoubleClickTime enable an application to set and retrieve the mouse double-click time—the maximum amount of time permitted between clicks when a mouse button is double-clicked. The expression

::GetDoubleClickTime ()

returns the double-click time in milliseconds, while the statement

::SetDoubleClickTime (250);

sets the double-click time to 250 milliseconds, or one quarter of a second. When the same mouse button is clicked twice in succession, Windows uses both the double-click time and the SM_CXDOUBLECLK and SM_CYDOUBLECLK values returned by ::GetSystemMetrics to determine whether to report the second of the two clicks as a double click.

A function that processes mouse messages can determine which, if any, mouse buttons are pressed by checking the nFlags parameter passed to the message handler. It's also possible to query the state of a mouse button outside a mouse message handler by calling ::GetKeyState or ::GetAsyncKeyState with a VK_LBUTTON, VK_MBUTTON, or VK_RBUTTON parameter. ::GetKeyState should be called only from a keyboard message handler because it returns the state of the specified mouse button at the time the keyboard message was generated. ::GetAsyncKeyState can be called anywhere, anytime. It works in real time, returning the state of the button at the moment the function is called. A negative return value from

::GetKeyState (VK_LBUTTON)

or

::GetAsyncKeyState (VK_LBUTTON)

indicates that the left mouse button is pressed. Swapping the mouse buttons does not affect ::GetAsyncKeyState, so if you use this function, you should also use ::GetSystemMetrics to determine whether the buttons have been swapped. The expression

::GetAsyncKeyState (::GetSystemMetrics (SM_SWAPBUTTON) ? VK_RBUTTON : VK_LBUTTON)

checks the state of the left mouse button asynchronously and automatically queries the right mouse button instead if the buttons have been swapped.

Windows provides a pair of API functions named ::GetCursorPos and ::SetCursorPos for getting and setting the cursor position manually. ::GetCursorPos copies the cursor coordinates to a POINT structure. A related function named ::GetMessagePos returns a DWORD value containing a pair of 16-bit coordinates specifying where the cursor was when the last message was retrieved from the message queue. You can extract those coordinates using the Windows LOWORD and HIWORD macros:

DWORD dwPos = ::GetMessagePos (); int x = LOWORD (dwPos); int y = HIWORD (dwPos);

::GetCursorPos and ::GetMessagePos both report the cursor position in screen coordinates. Screen coordinates can be converted to client coordinates by calling a window's ClientToScreen function.

Windows also provides a function named ::ClipCursor that restricts the cursor to a particular area of the screen. ::ClipCursor accepts a pointer to a RECT structure that describes, in screen coordinates, the clipping rectangle. Since the cursor is a global resource shared by all applications, an application that uses ::ClipCursor must free the cursor by calling

::ClipCursor (NULL);

before terminating, or else the cursor will remain locked into the clipping rectangle indefinitely.

本文地址:http://com.8s8s.com/it/it3570.htm