分类: C/C++
2008-09-01 17:47:13
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.
// In the mouseaction header you create define that look like this // // These base classes can easily be changed to any CWnd derived class #if defined(MYBTN) #define BASECLASS CButton #elif defined(MYSLID) #define BASECLASS CSliderCtrl #elif defined(MYTEXT) #define BASECLASS CStatic #else #define BASECLASS CWnd #endif ///////////////////////////////////////////////////////////////////////////// // CMouseAction window class CMouseAction : public BASECLASS // BASECLASS will then be replaced with the proper base class // when included in each file { }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; //Tell Windows that we want to process all mouse Hover and Leave Messages 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.
//In the cpp message map call ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave) ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover) //In the header AFX_MSG section 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);//Set transparent background LOGBRUSH brush; brush.lbColor = HOLLOW_BRUSH; brush.lbHatch = 0; brush.lbStyle = 0; hbr = CreateBrushIndirect(&brush);//Use a transparent brush for painting } else hbr = BASECLASS::OnCtlColor(pDC, pWnd, nCtlColor);//Else use default 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();//Remove old tooltip // Create a new Tooltip with new Button Size and Location SetToolTipText(m_tooltext); if (m_ToolTip != NULL) if (::IsWindow(m_ToolTip->m_hWnd)) //Display ToolTip 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 ) { // Are we allowed to resize the window? if ((IsMoveable() == TRUE) && (m_bResizing == FALSE)) { m_bResizing = TRUE; m_point = point; CRect rect; BOOL bSuccess = FALSE; GetClientRect(rect);//Tell the tracker where we are m_track = new CRectTracker(&rect, CRectTracker::dottedLine | CRectTracker::resizeInside | CRectTracker::hatchedBorder); if (nFlags & MK_CONTROL) // If Ctrl + Right-Click then Resize object { GetWindowRect(rect); GetParent()->ScreenToClient(&rect); //Let user resize window bSuccess = m_track->TrackRubberBand(GetParent(),rect.TopLeft()); m_track->m_rect.NormalizeRect(); } else // If not Ctrl + Right-Click, then Move Object { bSuccess = m_track->Track(GetParent(), point);//Let user move window } if (bSuccess) { rect = LPRECT(m_track->m_rect); //ClientToScreen(&rect); //GetParent()->ScreenToClient(&rect); //SetWindowPos(&wndTopMost,rect.left,rect.top, rect.Width(),rect.Height(),SWP_SHOWWINDOW); //Move Window to our new position 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); // This code is so that when a transparent control is moved // and the dialog or app window behind the transparent control // is showing a bitmap, this forces the parent to redraw // before we redraw so that the bitmap is shown properly // and also eliminates any window overlapping that may occur with // using a Transparent Window on top of a Bitmap... // If you are not using a transparent window, you shouldn't need this... ShowWindow(SW_HIDE);// Hide Window CRect rect; GetWindowRect(&rect); GetParent()->ScreenToClient(&rect); GetParent()->InvalidateRect(&rect);//Tell Parent to redraw the rect ShowWindow(SW_SHOW);//Now redraw us so that Control displays correctly }
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 .... //This is very important or else the mouse actions will not work properly BEGIN_MESSAGE_MAP(CText, CMouseAction) //{{AFX_MSG_MAP(CText) // NOTE - the ClassWizard will add and remove mapping macros here. //}}AFX_MSG_MAP END_MESSAGE_MAP() void CText::OnLButtonDown(...) { CMouseAction::OnLButtonDown(...); //Don't forget this either }