Как разрешить перетаскивание окна за любую точку?

Автор: Алексей Кирюшкин

Версия текста: 1.1

Демонстрационное приложение DragWin

Пример - приложение DragWin (диалоговое окошко, MFC) иллюстрирует два способа осуществить перемещение окна с захватом его не только за заголовок, но и за любую точку на клиентской области. Идея первого способа проста - при получении сообщения о перемещении мыши передвигаем наше окно в соответствии с новыми координатами. Второй способ поизящнее, и заключается в некотором "обмане" Windows, после которого она считает, что мышь находится над заголовоком окна, даже если реально это уже клиентсткая часть.

Способ 1

Реализован для главного окна приложения. Заключается в написании собственных обработчиков нажатия (WM_LBUTTONDOWN), перемещения (WM_MOUSEMOVE) и отпускания (WM_LBUTTONUP) левой кнопки мыши. Обработчики на данные события устанавливаются стандартным образом - через MFC ClassWizard.

void CDragWinDlg::OnLButtonDown(UINT nFlags, CPoint point) 
{
    // выставим флажок - пошло перетаскивание
    m_bMoveWindow = TRUE;
    // все сообщения от мыши - к нашему окну, независимо от координат
    // чтобы мышь не улетала с окна при быстром движении
    SetCapture();
    // сохраняем координаты окна
    GetWindowRect(m_RectDlg);
    // сохраняем положение мышки внутри окна программы
    ClientToScreen(&point);
    m_MouseInDlg = point - m_RectDlg.TopLeft();
    // меняем курсор, чтоб веселее было тащить
    m_hCursor = m_hCursorDown;
    ::SetCursor(m_hCursor);
    
    // вызываем обработчик по умолчанию
    CDialog::OnLButtonDown(nFlags, point);
}

void CDragWinDlg::OnMouseMove(UINT nFlags, CPoint point) 
{
    if (m_bMoveWindow) // надо тащить
    {
        // преобразуем координаты мыши в экранные
        // именно они нужны будут для SetWindowPos()
        ClientToScreen(&point);
        // двигаем окно в соответствии с новыми координатами мыши
        SetWindowPos(&wndTop,
            point.x - m_MouseInDlg.x, point.y - m_MouseInDlg.y,
            m_RectDlg.right - m_RectDlg.left,
            m_RectDlg.bottom - m_RectDlg.top,
            SWP_SHOWWINDOW);
        // поскольку обработчик по умолчанию все равно будет использовать
        // первоначальные параметры сообщения
        // обратное преобразование ScreenToClient(&point);
        // можно не вызывать
    }
    
    // вызываем обработчик по умолчанию
    CDialog::OnMouseMove(nFlags, point);
}

void CDragWinDlg::OnLButtonUp(UINT nFlags, CPoint point) 
{
    // перетаскивание закончилось
    m_bMoveWindow = FALSE;

    // "отпускаем" мышку
    ReleaseCapture();

    // меняем курсор на исходный
    m_hCursor = m_hCursorUp;
    
    // вызываем обработчик по умолчанию
    CDialog::OnLButtonUp(nFlags, point);
}

BOOL CDragWinDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
    // заменяем курсор на свой
    ::SetCursor(m_hCursor);
    
    return TRUE;
    // !!! было return CDialog::OnSetCursor(pWnd, nHitTest, message);
}

Замена курсора естесственно не является критичной для собственно перетаскивания, а добавлена исключительно для визуализации процесса захвата окошка.

Способ 2

Реализован для окна About этого же приложения. Заключается в замене обработчика события WM_NCHITTEST, которое информирует об области, над которой в данный момент находится мышка. Обработчик этого сообщения также можно добавить через MFC ClassWizard. Предварительно на закладке ClassInfo для класса CAboutDlg нужно установить для Message Filter значение Window.

Переписываем функцию - обработчик следующим образом:

UINT CAboutDlg::OnNcHitTest(CPoint point) 
{
    UINT ret = CDialog::OnNcHitTest(point);
    
    // если обработчик по умолчанию говорит нам что мышка
    // над клиентской областью окна,  заменяем возвращаемое
    // значение на HTCAPTION - мышка над заголовком окна, 
    // а за заголовок перемещать окно можно!
    if (ret ==  HTCLIENT)
        return HTCAPTION;
    
    return ret;
}

Второй способ проще, первый потенциально гибче, используйте тот, что лучше подходит к вашему конкретному случаю.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.