2
votes

I have my dialog derived from CDialog and I want to close it once user moves mouse cursor away from it. To do so, I've added OnMouseLeave handler which calls OnCancel(). As I understand, for WM_MOUSELEAVE events to be sent in time, TrackMouseEvent must be called inside OnMouseMove routine. So the whole code is as following:

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

It works fine, but the dialog is closed when user hovers some of its child controls (like buttons he wants to click on :) ). It is because child controls do not send WM_MOUSEMOVE to the parent dialog.

The only function I found to "propagate" WM_MOUSEMOVE messages from child controls is SetCapture(). And it does the job, but 1) user cannot click any button after that and 2) mouse icon changes to hourglasses. So this is not an option.

Any suggestions?

Update I placed TrackMouseEvent call to the PreTranslateMessage routine which is called correctly on any mouse move events (even hovering the child controls). The strange thing is WM_MOUSELEAVE is still generated when user hovers child control! Seems like TrackMouseEvent knows what control is hovered now. Any ideas how to fix this?

4

4 Answers

1
votes

I would try CDialog::PreTranslateMessage() if this is a modal dialog. If you still cannot detect mouse movements inside the children, the only option left is SetWindowsHookEx + WH_MOUSE.

0
votes

When 2 dialog get event, then rise child dialog's WM_MOUSELEAVE event by force. see the code, below

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

How do you think?

0
votes

I think I understand the problem now. It's indeed a bit tricky. I think you need a timer to guarantee that the subsequent WM_MOUSEMOVE message is handled (you have to test this).

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

Handle WM_MOUSELEAVE, wait for WM_MOUSEMOVE. Did it arrive? No -> dismiss dialog. Yes -> restart.

0
votes

Thanks for all your help, guys. I wasn't able to make TrackMouseEvent properly, so I end up implementing solution with timer. On each tick I check position of mouse cursor to be inside my dialog area and ensure it is still foreground. That works perfect for me, though it is a little hack.

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