Introduction
The implementation of the UI of an application sometimes requires the capture of the mouse.
The following situations come to mind:
- You need a reliable mouse over detection.
- You are implementing some sort of drag and drop interface.
- You want to know what window the mouse is over.
Notes on the SetCapture API
The action of the
SetCapture()
API is somewhat complex, and not well documented in the
Platform SDK. You can unserstand how best to use
SetCapture()
in you application if
you understand the following limitations of using mouse capture:
Only one window can have the mouse capture at a time. A window can request mouse capture by
calling the SetCapture()
API, and that window has the mouse capture untill either
the ReleaseCapture()
API or SetCapture()
is called directing the
mouse capture to a diffrent window.
In addition, there are two types of capture, that I call foreground and background
capture. Foreground capture is obtained when the follwoing two conditions are met:
- The current thread is the foreground thread (ie. it owns the foreground window).
- At least one mouse button is being held down.
Otherwise (if the current thread is not the foreground thread, or no mouse buttons are held down)
the window merely gets background capture.
If, at any time, all the mouse buttons are released, the mouse capture will automatically revert
to the bacground.
Here are the diffrences between foreground and background capture:
- Foreground Capture
- A window with foreground capture receives all mouse messages for all windows in the system.
- Background Capture
- Once the capture has reverted to background capture, the window only receives mouse messages
for:
- all windows owned by the same thread
- all windows on all threads if those windows and the window with capture share the
same top-level window.
Implementing a Drag operation using SetCapture
To implement a drag operation in your application you would implement the following message
handlers:
WM_LBUTTONDOWN WM_RBUTTONDOWN |
A drag operation generally starts when the user clicks on something,
and begins to move the mouse. If the user is clicking on something draggable use the
DragDetect() API to dectect if a drag operation is beginning. Once the
beginning of a drag operation is confirmed call SetCapture() . Note that
various drag enabled controls detect when a drag begins and send their parent a message
such as LVN_BEGINDRAG .
|
WM_LBUTTONUP WM_RBUTTONUP |
If the mouse up finishes the drag (see the below
Remarks for more info on why a mouse up might not finish a drag operation)
ReleaseCapture() must be called to allow other windows access to mouse
messages.
|
WM_CHAR |
A drag operation can ususally be aborted by pressing ESC.
If required abort the drag operation and call ReleaseCapture() .
|
WM_CANCELMODE |
The window maanger sends this message when it detects a
change that requires that an application cancel any modal state it has entered. Abort
the drag operation and call ReleaseCapture() .
|
WM_CAPTURECHANGED |
The capture has been cleared, or some other window has
obtained it. It probably makes no sense to continue your drag operation, so it should
be aborted. As you have explicitly lost capture you don't need to call
ReleaseCapture() .
|
WM_SETCURSOR |
Mouse capture interrupts the normal flow of mouse
processing. WM_SETCURSOR messages are not dispatched to a window that
has mouse capture - if the cursor should be set to indicate the drag via
SetCursor when the drag operation begins - if the cursor needs to
change to provide feedback to the user it should be set in resoponse to
WM_MOUSEMOVE .
|
Remarks
With the exception of the mouse down or initial drag operation begin detection most applications
implement their drag code in a modal loop to prevent cluttering up the main applications window
procedure.
Also note that most system drag operations allows the user to "swap" buttons during a drag by
pressing the other button, and then releasing the initial button. If the drag operation is not
implemented in a modal loop this situation would ahve to be specially catered for to prevent
another drag operation being launched.
Using TrackMouseEvent
There are two variations of this API available:
TrackMouseEvent()
- Available as a standard window manager function on Windows 98 and above and Windows NT
4 and above.
_TrackMouseEvent()
- Available in the common control library on all systems with Internet Explorer 3 and
higher.
Use TrackMouseEvent()
if you can ignore windows 95 as a target. Use
TrackMouseEvent()
if you need to target windows 95, and can assume the machine
has at least IE3 installed. If you need TrackMouseEvent()
functionality on Windows
95 and cannot assume at least IE3 then the following quick hack demonstrates the basic
functionality.
Rolling your own TrackMouseEvent
A full custom implementation of
TrackMouseEvent()
would have to implement a message
hook so it could hook messages intended for any window. Any window that needs to detect mouse
enter, leave or hover events can use code like this:
#define TID_POLLMOUSE 100
#define MOUSE_POLL_DELAY 500
case WM_MOUSEMOVE:
SetTimer(hwnd,TID_POLLMOUSE,MOUSE_POLL_DELAY,NULL);
break;
case WM_TIMER:
RECT rc;
POINT pt;
GetWindowRect(hwnd,&rc);
GetCursorPos(&pt);
if(PtInRect(&rc,pt))
{
PostMessage(hwnd,WM_MOUSEHOVER,0,0L);
break;
}
PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
KillTimer(hwnd,TID_POLLMOUSE);
break;
A more responsive version could be made by using SetCapture()
to detect more
quickly when the mouse leaves.
#define TID_POLLMOUSE 100
#define MOUSE_POLL_DELAY 500
case WM_MOUSEMOVE:
RECT rc;
POINT pt;
GetWindowRect(hwnd,&rc);
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
if(PtInRect(&rc,pt))
{
SetTimer(hwnd,TID_POLLMOUSE,MOUSE_POLL_DELAY,NULL);
if(hwnd != GetCapture())
{
SetCapture(hwnd);
PostMessage(hwnd,WM_MOUSEENTER,0,0L);
}
break;
}
ReleaseCapture();
KillTimer(hwnd,TID_POLLMOUSE);
PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
break;
case WM_TIMER:
GetWindowRect(hwnd,&rc);
GetCursorPos(&pt);
if(PtInRect(&rc,pt))
{
PostMessage(hwnd,WM_MOUSEHOVER,0,0L);
break;
}
ReleaseCapture();
KillTimer(hwnd,TID_POLLMOUSE);
PostMessage(hwnd,WM_MOUSELEAVE,0,0L);
break;
The code samples provided differ in a number of ways from the TrackMouseEvent()
APIs:
- In a real application you need to perfrom hover detection for child windows. The sample
assumes you are perfroming hover detection over the main window only.
TrackMouseEvent()
does not send a WM_MOUSEENTER
message. Nor is
there such a message defined. The code simply demonstrates how such a message could be
implemented.
TrackMouseEvent
is a once off API. Once a notification has been received you
must call it again to receive subsequent notifications. The sample code given will
repeatedly send WM_MOUSEHOVER
events - no mechanism is provided whereby
notifications can be stopped, or only arrive when requested.
Please feel free to expand the code to fit your own requirements.
Please send any comments or bug reports to me via
email. For any updates to this article, check my site
here.
Knowledge Base References
More information on this topic can be found in the following KB articles:
Q135865
HOWTO: Use Win32 API to Draw a Dragging Rectangle on Screen DC
Q183107
HOWTO: Detect When the Cursor Leaves the Window