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 have 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.
Collapse
#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.
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