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:
Collapse
Copy Code
#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
Copy Code
#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.