【问题标题】:Receiving WM_MOUSEMOVE from child controls on MFC CDialog从 MFC CDialog 上的子控件接收 WM_MOUSEMOVE
【发布时间】:2012-02-02 15:53:37
【问题描述】:

我的对话框是从 CDialog 派生的,一旦用户将鼠标光标移开,我想关闭它。为此,我添加了调用 OnCancel() 的 OnMouseLeave 处理程序。据我了解,要及时发送 WM_MOUSELEAVE 事件,必须在 OnMouseMove 例程中调用 TrackMouseEvent。所以整个代码如下:

void CDlgMain::OnMouseLeave()
{
 CDialog::OnMouseLeave();

 // Close dialog when cursor is going out of it
 OnCancel();
}

void CDlgMain::OnMouseMove(UINT nFlags, CPoint point)
{
 TRACKMOUSEEVENT tme;
 tme.cbSize = sizeof(tme);
 tme.hwndTrack = m_hWnd;
 tme.dwFlags = TME_LEAVE;
 tme.dwHoverTime = HOVER_DEFAULT;
 TrackMouseEvent(&tme);

 CDialog::OnMouseMove(nFlags, point);
}

它工作正常,但是当用户悬停它的一些子控件时对话框关闭(比如他想点击的按钮:))。这是因为子控件不会向父对话框发送 WM_MOUSEMOVE。

我发现从子控件“传播”WM_MOUSEMOVE 消息的唯一函数是 SetCapture()。它可以完成这项工作,但是 1)用户在此之后无法单击任何按钮,并且 2)鼠标图标变为沙漏。所以这不是一个选择。

有什么建议吗?

更新我将 TrackMouseEvent 调用置于 PreTranslateMessage 例程中,该例程在任何鼠标移动事件(甚至悬停子控件)上都会正确调用。奇怪的是,当用户悬停子控件时,仍然会生成 WM_MOUSELEAVE!好像 TrackMouseEvent 知道现在悬停的是什么控件。任何想法如何解决这个问题?

【问题讨论】:

    标签: c++ visual-studio-2010 mfc mouseevent


    【解决方案1】:

    如果这是一个模态对话框,我会尝试CDialog::PreTranslateMessage()。如果您仍然无法检测到子项内部的鼠标移动,则剩下的唯一选择是SetWindowsHookEx + WH_MOUSE

    【讨论】:

    • 好主意,但我仍然有问题。请参阅我帖子中的更新部分。
    • 我的意思是;根本不要使用 TrackMouseEvent。相反,请尝试在 PreTranslateMessage 中获取 WM_MOUSEMOVE。如果消息没有到达那里,那么您需要使用挂钩。我过去使用过 TrackMouseEvent。它确实适用于某些场景,但通常还不够。
    【解决方案2】:

    当 2 个对话框获取事件时,强制触发子对话框的 WM_MOUSELEAVE 事件。 见下面的代码

        void CDlgParent::OnMouseMove(UINT nFlags, CPoint point)
        {
            CWnd* cwnd = this->GetDlgItem(IDC_CHILDRENNAME);
            ::SendMessage(cwnd->m_hWnd, WM_MouseLeave());
    
            CDialog::OnMouseMove(nFlags, point);
        }
    
        void CDlgMain::OnMouseMove(UINT nFlags, CPoint point)
        {
            TRACKMOUSEEVENT tme;
    
            tme.cbSize = sizeof(tme);
            tme.hwndTrack = m_hWnd;
            tme.dwFlags = TME_LEAVE;
            tme.dwHoverTime = HOVER_DEFAULT;
            TrackMouseEvent(&tme);
            ::SetFocus(this->mhWnd);
    
            CDialog::OnMouseMove(nFlags, point);
        }
    

    你觉得怎么样?

    【讨论】:

    • 这就是我所说的“紧急黑客”。通常,发送这样的内部 Windows 消息是个坏主意。但是,它应该可以工作。
    • 问题是我需要为所有子控件添加这样的处理程序。 IE。如果我添加一个新按钮,我应该在它上面处理 MouseMove。它可以完成,但作为最后的手段:)
    • @NOPslider 好的。我得到了它。所以,我有一个问题。制作自定义消息而不是发送消息怎么样?
    【解决方案3】:

    我想我现在明白了这个问题。这确实有点棘手。我认为您需要一个计时器来保证处理后续的WM_MOUSEMOVE 消息(您必须对此进行测试)。

    BOOL CTestDgDlg::PreTranslateMessage(MSG* pMsg)
    {
        if (pMsg->message == WM_MOUSEMOVE)
        {
            TCHAR buffer[255];
            ::GetWindowText(pMsg->hwnd, buffer, 255);
            TRACE(_T("WM_MOUSEMOVE: %s\n"), buffer);
        }
    
        return CDialogEx::PreTranslateInput(pMsg);
    }
    

    处理WM_MOUSELEAVE,等待WM_MOUSEMOVE。到了吗?否 -> 关闭对话框。是 -> 重新启动。

    【讨论】:

    • 如果用户将光标移动到按钮(生成 WM_MOUSELEAVE)然后保持不动(不生成 WM_MOUSEMOVE)怎么办?在这种情况下,似乎对话框将被关闭。
    【解决方案4】:

    谢谢你们的帮助,伙计们。我无法正确制作 TrackMouseEvent,所以我最终使用计时器实现了解决方案。在每个刻度上,我检查鼠标光标的位置是否在我的对话区域内,并确保它仍然是前景。这对我来说很完美,虽然有点小技巧。

    void CALLBACK EXPORT TimerProc(HWND hWnd, UINT nMsg, UINT nIDEvent, DWORD dwTime)
    {
      // This is a little hack, but suggested solution with TrackMouseEvent is quite 
      // unreliable to generate WM_MOUSELEAVE events
      POINT pt;
      RECT rect;
      GetCursorPos(&pt);
      GetWindowRect(hWnd, &rect);
    
      HWND hFGW = GetForegroundWindow();
    
      // Send leave message if cursor moves out of window rect or window 
      // stops being foreground
      if (!PtInRect(&rect, pt) || hFGW != hWnd)
      {
        PostMessage(hWnd, WM_MOUSELEAVE, 0, 0);
      }
    }
    

    【讨论】:

    • 好。不过,我会尽量避免发送这样的不可发送的系统消息。如果你使用定时器,为什么不简单地把它放在对话框类本身中,形式为GetCursorPosGetWindowRectPtInRectOnCancel()
    • 什么是“不可发送”消息?我看不出有什么区别,只是从 MSDN 复制粘贴了这个计时器处理程序代码。
    猜你喜欢
    • 2020-11-20
    • 2010-10-17
    • 2014-03-14
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 2012-06-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多