65.9K
CodeProject 正在变化。 阅读更多。
Home

为任何控件添加鼠标功能

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (12投票s)

2002 年 1 月 20 日

5分钟阅读

viewsIcon

85917

downloadIcon

3638

一篇关于为任何CWnd派生控件添加基于鼠标的功能的文章。

引言

什么是 CMouseActionCMouseAction 是一个可序列化的基类,它允许任何 CWnd 派生类实现各种鼠标移动操作。CMouseAction 提供了鼠标通知、运行时调整大小和移动、控件透明度、控件工具提示以及自动窗口放置的实现。自动窗口放置是通过在窗口创建或销毁时对其位置进行序列化来实现的,然后使用 MoveWindow 将控件窗口正确放置在其父窗口上。SetWindowName 设置序列化期间使用的窗口名称,以便在序列化文件中轻松识别每个控件窗口。序列化要求您使用正确设置的 CArchive 调用 SerializeCMouseAction 通过使用 #define 来控制其基类,从而适用于多个类。

// 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
{
}
在您派生的控件类头文件中,创建一个宏来映射到 Mouseaction 头文件中的相应类。
#define MYBTN
或者
#define MYSLID

当代码编译时,对于此实例,CMyBtn 将派生自 CMouseAction,而 CMouseAction 将派生自 CButton。对于此实例,CMySlider 将派生自 CMouseAction,而 CMouseAction 将派生自 CSlider。对于此实例,CMyStatic 将派生自 CMouseAction,而 CMouseAction 将派生自 CStatic。尽管它们都派生自同一个类,但通过使用 #define,我们可以使每个类派生自其正确的类。处理通知后,将通过使用我们的宏并调用,例如:

BASECLASS::OnMouseMove(nFlags,point);
将调用 CButtonCStaticCSliderCtrlCWnd 类中的 OnMouseMove 函数,具体取决于子类化头文件中的 #define。

当鼠标移动到控件上时,我们通过简单地调用来告诉 Windows 我们想收到任何鼠标移动的通知。

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;
    }

OnMouseMove 函数内部。这将跟踪鼠标,并为我们提供两个需要实现的新函数。

//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);

OnMouse 函数中,我们将 BOOL m_bHover 设置为 true(如果鼠标悬停在我们的控件上),否则设置为 false。设置 m_bHover 后,我们调用 Invalidate() 以便我们的控件更新其界面。在控件的绘制代码中,我们然后调用 IsHovering() 来确定是否需要绘制带有鼠标悬停的控件。我们在绘制代码中使用 IsHovering(),以便如果悬停代码有任何更改,或者 m_bHover 发生更改,使用 IsHovering() 仍然可以确定鼠标是否悬停,从而防止未来可能出现的问题。如果您想添加第三个 MouseDown 状态,您可以轻松地向您的控件添加 OnLButtonDown(),在 OnLButtonDown 函数中设置一个布尔变量,并调用 Invalidate 您的控件,从而为控件提供三种绘制状态:MouseHoverMouseOut\LeaveMouseDown

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;
}

如果使用 SetTransparent(TRUE),则控件设置为以透明背景绘制。通过在调用 OnCtlColor 时为我们的控件创建一个 NULL 画笔来实现透明背景。如果我们的控件不透明,则调用基类的 OnCtlColor 并返回适当的背景绘制画笔。这允许我们的控件以透明方式绘制,或使用之前使用 BASECLASS 宏设置的基类的默认背景进行绘制。

我们还允许为每个控件窗口自定义工具提示。SetToolTipText 用于设置将在工具提示中显示的文本。当鼠标悬停在窗口上时,将显示工具提示。工具提示是通过使用 OnMouseHover 函数显示的,在调用 RedrawWindow 更新我们的控件后,我们立即更新工具提示以显示,因为鼠标现在正在悬停。我们使用以下代码在鼠标悬停时更新工具提示:

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();

通过实现 CRectTracker 和四个函数,我们为控件添加了运行时调整大小和移动功能。首先,我们将 SetMoveable() 设置为 TRUE,以允许移动和调整控件的大小。我们在函数和绘制代码中使用 IsMoveable 来确定用户是否可以在运行时移动控件。如果我们的控件允许在运行时移动和调整大小,那么我们使用 OnRButtonDown 来允许用户调整控件的大小和移动。由于大多数控件在左键按下时执行特定任务是很常见的,因此我决定使用 OnLButtonDown 来允许子类化控件执行其特定功能,同时保留 OnRButtonDown 来执行控件的调整大小和移动。除了使用 OnRButtonDown 之外,还实现了 OnMove 来纠正透明窗口在移动或调整大小时覆盖使用位图或图像作为背景的窗口时可能出现的任何问题。当按下鼠标右键时,用户可以移动控件。当按下 CTRL 键并在我们的控件上单击鼠标右键时,用户可以调整窗口大小。如果我们的控件已处于调整大小或移动模式,并且再次单击鼠标右键,则操作将被取消。当我们在调整控件大小或移动控件时单击鼠标左键,控件窗口将更新到新的大小和位置。请参见下面的 OnRButtonDown

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);
}

为避免透明控件覆盖位图窗口时出现问题,我们使用以下方法隐藏窗口、移动它,然后显示并重绘它。

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
}

此类有助于为任何控件窗口添加大量鼠标功能,同时仍允许我们使用设置的宏从正确的类中子类化我们的控件窗口。希望这对某人有所帮助……请记住,当从该类派生控件时,您必须在所有区域更改其派生类,包括以下内容:

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
}
© . All rights reserved.