bui3
bui3

Reputation: 152

Visual Studio 2008 MFC drag dialog without title and detect all mouse events

I'm asked to add new feature to an existing program. The program consists of a dialog without title/border. I need couple of things:

  1. When the user simply clicks inside the dialog area, just close it;
  2. Move the dialog when the user mouse-down inside its area and drag

Here's what I found so far:

void MyDialog::onMessageReceived(UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
    case WM_LBUTTONDOWN:
        lastX=LOWORD(lParam);
        lastY=HIWORD(lParam);
        SendMessage(DlgHandle, WM_NCLBUTTONDOWN, HTCAPTION, NULL);
        break;
    case WM_LBUTTONUP:
        if (LOWORD(lParam)==lastX && HIWORD(lParam)==lastY)
            onKillButtonClick();
        break;
}}

EDIT: This function is called in this way:

INT_PTR CALLBACK MyDialog::dialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    _this->onMessageReceived(uMsg, wParam, lParam);
} 

Moving the window works very well, but looks like the WM_LBUTTONUP event is lost. I had to click twice to get it fired. Hope someone can help me...

EDIT: Using Spy++ I saw that WM_LBTTONUP is fired, but immediately after a new WM_NCLBUTTONDOWN is emitted.

Upvotes: 1

Views: 431

Answers (1)

Vlad Feinstein
Vlad Feinstein

Reputation: 11321

First, I agree with Michael Walz - this is a very confusing behavior: the processing of the mouse up event depends on whether or not it has moved... What if it moved just a little bit? I would much rather dismiss this dialog with a different action - click on the icon, right-click, etc.

However, the correct way to let the user move your captionless window is to process WM_NCHITTEST message and return HTCAPTION:

case WM_NCHITTEST:
    SetWindowLong(hDlg, DWL_MSGRESULT, HTCAPTION);
    return HTCAPTION;

Unfortunately, Windows then will take over all mouse events, so, as you observed, you would never get WM_LBUTTONUP. You have an option to set a short timer and see if the user started to move your window; cancel it when you get WM_ENTERSIZEMOVE message. If that timer fires - close your window. Yes, it's also awkward, but no more thatn your proposal.

Another way is to handle the move yourself:

static bool bDragging(false), bMoved(false);
static POINT pt1 = {}, pt2 = {};
static RECT r;
switch (message)
{
case WM_LBUTTONDOWN:
    GetWindowRect(hDlg, &r);
    pt1 = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
    ClientToScreen(hDlg, &pt1);
    bDragging = true;
    bMoved = false;
    break;
case WM_MOUSEMOVE:
    if (bDragging)
    {
        pt2 = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
        ClientToScreen(hDlg, &pt2);
        if (pt2.x != pt1.x || pt2.y != pt1.y)
        {
            OffsetRect(&r, pt2.x - pt1.x, pt2.y - pt1.y);
            SetWindowPos(hDlg, 0, r.left, r.top, 0, 0, SWP_NOSIZE);
            pt1 = pt2;
            bMoved = true;
        }
    }
    break;
case WM_LBUTTONUP:
    bDragging = false;
    if (!bMoved)
        PostMessage(hDlg, WM_COMMAND, IDCANCEL, 0);
    bMoved = false;
    break;
}
return (INT_PTR)FALSE;

Upvotes: 1

Related Questions