Introduction
What is CMouseAction
? CMouseAction
is a serializable baseclass that allows any
CWnd
derived class to implement
all manner of mouse movement actions. CMouseAction
provides an implementation of mouse notifications,
run-time resizing and moving, control transparency, control toolips, and automatic window placement.
Automatic window placement is done through serialization of the window position
when created or destroyed and then MoveWindow is used to properly place the
control window on its parent. SetWindowName
sets the window name used during
serialization, so that each control window can easily be identified in a
serialized file. The serialization requires you to call Serialize
with the properly set CArchive
. CMouseAction
works for multiple classes by using
#define
to control its base class.
#if defined(MYBTN)
#define BASECLASS CButton
#elif defined(MYSLID)
#define BASECLASS CSliderCtrl
#elif defined(MYTEXT)
#define BASECLASS CStatic
#else
#define BASECLASS CWnd
#endif
class CMouseAction : public BASECLASS
{
}
In your derived control class header file you create a define that maps to the proper class in the Mouseaction header:
#define MYBTN
or else
#define MYSLID
When the code is compiled, CMyBtn
will be derived from CMouseAction
, and
CMouseAction
will be derived
from CButton
for this instance. CMySlider
will be derived from CMouseAction
, and
CMouseAction
will be
derived from CSlider
for this instance. CMyStatic
will be derived from
CMouseAction
, and CMouseAction
will be derived from CStatic
for this instance. Even though they are all derived from the same class
each, by using #defines we are able to derive each from its proper class. After processing notifications
are then sent to the proper baseclass by using our define's and calling,
for ex.
BASECLASS::OnMouseMove(nFlags,point);
would call the OnMouseMove function from the
CButton
,
CStatic
,
CSliderCtrl
, or
CWnd
class depending on the #define in the subclassed header file.
When the mouse is moved over the control, we tell windows that we want to be notified of any mouse
movements by simply calling:
if (!m_bTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(tme);
tme.hwndTrack = m_hWnd;
tme.dwFlags = TME_LEAVE|TME_HOVER;
tme.dwHoverTime = 1;
m_bTracking = _TrackMouseEvent(&tme);
m_point = point;
}
from within the OnMouseMove
function. This will then track the mouse and present us with two new functions
that we need to implement.
ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover)
afx_msg LRESULT OnMouseLeave(WPARAM wparam, LPARAM lparam);
afx_msg LRESULT OnMouseHover(WPARAM wparam, LPARAM lparam);
Within the OnMouse
functions, we set BOOL m_bHover
to true if the mouse is hovering over our control,
and false otherwise. After setting m_bHover
, we call Invalidate() so that our control will update its
interface. From within the drawing code of our control, we then call IsHovering
() to determine if we need
to draw the control with the mouse hovering or not. We use IsHovering
() in our drawing code, so that if the
hover code is changed at all, or m_bHover
changed, using IsHovering
() will still determine if the mouse is
hovering, and thus prevent future potential problems. If you wanted to add a third
MouseDown
state, you could
easily add OnLButtonDown
() to your control, set a boolean variable in the
OnLButtonDown
function, and Invalidate
your control, thus providing three drawing states for the control, MouseHover
,
MouseOut\Leave
, and MouseDown
.
HBRUSH CMouseAction::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = NULL;
if (m_bTranparent)
{
pDC->SetBkMode(TRANSPARENT);
LOGBRUSH brush;
brush.lbColor = HOLLOW_BRUSH;
brush.lbHatch = 0;
brush.lbStyle = 0;
hbr = CreateBrushIndirect(&brush);
}
else
hbr = BASECLASS::OnCtlColor(pDC, pWnd, nCtlColor);
return hbr;
}
If SetTransparent(TRUE)
is used, then the control is set to draw with a transparent background. The transparent
background is achieved by creating a NULL brush for our control when OnCtlColor
is called. If our control is
not transparent then the base class OnCtlColor
is called and returns the proper background drawing brush. This
allows our control to be drawn transparently or with the default background from our base class that was previously
set using the BASECLASS
defines.
We also allow for custom tooltips for each control window. SetToolTipText
is used to set the text that will be
displayed in the ToolTip. When the mouse hovers over the window, the tooltip is then displayed. The tooltip is
displayed by using the OnMouseHover
function, and immediately after calling
RedrawWindow
to update our control,
we the update the tooltip to display since the mouse is now hovering. We update the tooltip while the mouse hovers
by using the following code:
DeleteToolTip();
SetToolTipText(m_tooltext);
if (m_ToolTip != NULL)
if (::IsWindow(m_ToolTip->m_hWnd))
m_ToolTip->Update();
We add runtime resizing and moving to control by implementing a CRectTracker
along with four functions. First we use SetMoveable
() to TRUE, to allow our
control to be moved and resized. We use IsMoveable
in our functions and drawing
code to determine if the control is allowed to be moved or not by the user at
runtime. If our control is allowed to be moved and resized at runtime, then we
use the OnRButtonDown
to allow the user to resize and move the control. Since it
is common place for most controls to perform specific tasks when the left button
is down, I have decided to use the OnLButtonDown
to allow subclassed controls to
perform their specific functions, while reserving the OnRButtonDown
to perform
the resizing and moving of the control. In addition to using OnRButtonDown
,
OnMove
is also implemented to correct any potential issues when a transparent
window is moved or resized over a window that uses a bitmap or image as its
background. When the right mouse button is down, the user is allowed to move the
control. When the CTRL key is down and the Right Button is clicked on our
control, then the user is allowed to resize the window instead. If our control
is already in resize or move mode, and the right button is clicked again, then
the action is cancelled. If the left button is clicked while we are resizing or
moving the control, then the control window is updated to the new size and
location. See OnRButtonDown
below:
void CMouseAction::OnRButtonDown( UINT nFlags, CPoint point )
{
if ((IsMoveable() == TRUE) && (m_bResizing == FALSE))
{
m_bResizing = TRUE;
m_point = point;
CRect rect;
BOOL bSuccess = FALSE;
GetClientRect(rect);
m_track = new CRectTracker(&rect, CRectTracker::dottedLine |
CRectTracker::resizeInside |
CRectTracker::hatchedBorder);
if (nFlags & MK_CONTROL)
{
GetWindowRect(rect);
GetParent()->ScreenToClient(&rect);
bSuccess = m_track->TrackRubberBand(GetParent(),rect.TopLeft());
m_track->m_rect.NormalizeRect();
}
else
{
bSuccess = m_track->Track(GetParent(), point);
}
if (bSuccess)
{
rect = LPRECT(m_track->m_rect);
rect.Width(),rect.Height(),SWP_SHOWWINDOW);
MoveWindow(rect.left,rect.top,
rect.Width(),rect.Height(),TRUE);
}
delete m_track;
m_track = NULL;
rect = NULL;
m_bResizing = FALSE;
}
BASECLASS::OnRButtonDown(nFlags,point);
}
To avoid issues with transparent controls over bitmapped windows, we use the following to hide the window, move it,
and then show it and redraw it.
void CMouseAction::OnMove(int x, int y)
{
BASECLASS::OnMove(x, y);
ShowWindow(SW_HIDE);
CRect rect;
GetWindowRect(&rect);
GetParent()->ScreenToClient(&rect);
GetParent()->InvalidateRect(&rect);
ShowWindow(SW_SHOW);
}
This class helps add a lot of mouse functionality to any control window, and still allows us to subclass our control window
from the proper class using the set defines. Hopefully this is useful to someone...
Please remember that when deriving a control from this class you must change the
class it is derived from in all areas, this includes the following:
class CText : public CMouseAction
....
BEGIN_MESSAGE_MAP(CText, CMouseAction)
END_MESSAGE_MAP()
void CText::OnLButtonDown(...)
{
CMouseAction::OnLButtonDown(...);
}