Перемещение мыши

При перемещении мыши Windows публикует WM_MOUSEMOVE сообщение. По умолчанию WM_MOUSEMOVE переходит в окно, содержащее курсор. Это поведение можно переопределить, захватив мышь, как описано в следующем разделе.

Сообщение WM_MOUSEMOVE содержит те же параметры, что и сообщения для щелчков мышью. Самые низкие 16 битов lParam содержат координату X, а следующие 16 битов — координату Y. Используйте макросы GET_X_LPARAM и GET_Y_LPARAM для распаковки координат из lParam. Параметр wParam содержит побитовое ИЛИ флагов, указывающее состояние других кнопок мыши, а также клавиш SHIFT и CTRL. Следующий код получает координаты мыши из lParam.

int xPos = GET_X_LPARAM(lParam); 
int yPos = GET_Y_LPARAM(lParam);

Помните, что эти координаты находятся в пикселях, а не в аппаратно-независимых пикселях (DIP). Далее в этом разделе мы рассмотрим код, который преобразуется между двумя единицами.

Окно также может получать WM_MOUSEMOVE сообщение, если положение курсора изменяется относительно окна. Например, если курсор установлен над окном и пользователь скрывает окно, окно получает WM_MOUSEMOVE сообщения, даже если мышь не двигается. Одним из следствий этого поведения является то, что координаты мыши могут не меняться между WM_MOUSEMOVE сообщениями.

Захват перемещения мыши за пределами окна

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

иллюстрация захвата мыши.

Чтобы получать сообщения о перемещении мыши за край окна, вызовите функцию SetCapture . После вызова этой функции окно будет продолжать получать WM_MOUSEMOVE сообщения до тех пор, пока пользователь удерживает хотя бы одну кнопку мыши, даже если мышь перемещается за пределы окна. Окно захвата должно быть окном переднего плана, и только одно окно может быть окном захвата одновременно. Чтобы освободить захват мыши, вызовите функцию ReleaseCapture .

Обычно setCapture и ReleaseCapture используются следующим образом.

  1. Когда пользователь нажимает левую кнопку мыши, вызовите SetCapture , чтобы начать захват мыши.
  2. Реагирование на сообщения о перемещении мыши.
  3. Когда пользователь отпускает левую кнопку мыши, вызовите ReleaseCapture.

Пример: рисование кругов

Давайте расширим программу Circle из модуля 3 , разрешив пользователю рисовать круг с помощью мыши. Начните с примера программы Direct2D Circle . Мы изменим код в этом примере, чтобы добавить простой рисунок. Сначала добавьте новую переменную-член в MainWindow класс .

D2D1_POINT_2F ptMouse;

Эта переменная сохраняет положение вниз, пока пользователь перетаскивает мышь. В конструкторе инициализируйте MainWindow переменные ptMouse и эллипса.

    MainWindow() : pFactory(NULL), pRenderTarget(NULL), pBrush(NULL),
        ellipse(D2D1::Ellipse(D2D1::Point2F(), 0, 0)),
        ptMouse(D2D1::Point2F())
    {
    }

Удалите тело MainWindow::CalculateLayout метода. Для этого примера это не требуется.

void CalculateLayout() { }

Затем объявите обработчики сообщений для левой кнопки вниз, левой кнопки вверх и перемещения мыши.

void OnLButtonDown(int pixelX, int pixelY, DWORD flags);
void OnLButtonUp();
void OnMouseMove(int pixelX, int pixelY, DWORD flags);

Координаты мыши задаются в физических пикселях, но Direct2D ожидает аппаратно-независимые пиксели (DIP). Для правильной обработки параметров высокого разрешения необходимо преобразовать координаты пикселей в dip. Дополнительные сведения о DPI см. в разделе DPI и Device-Independent пикселей. В следующем коде показан вспомогательный класс, который преобразует пиксели в DIP.

class DPIScale
{
    static float scale;

public:
    static void Initialize(HWND hwnd)
    {
        float dpi = GetDpiForWindow(hwnd);
        scale = dpi/96.0f;
    }

    template <typename T>
    static D2D1_POINT_2F PixelsToDips(T x, T y)
    {
        return D2D1::Point2F(static_cast<float>(x) / scale, static_cast<float>(y) / scale);
    }
};

float DPIScale::scale = 1.0f;

Вызовите DPIScale::Initialize в обработчике WM_CREATE после создания объекта фабрики Direct2D.

case WM_CREATE:
    if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory)))
    {
        return -1;  // Fail CreateWindowEx.
    }
    DPIScale::Initialize(hwnd);
    return 0;

Чтобы получить координаты мыши в DIP из сообщений мыши, сделайте следующее:

  1. Используйте макросы GET_X_LPARAM и GET_Y_LPARAM для получения координат пикселей. Эти макросы определены в WindowsX.h, поэтому не забудьте включить этот заголовок в проект.
  2. Вызовите DPIScale::PixelsToDips для преобразования пикселей в DIP.

Теперь добавьте обработчики сообщений в процедуру окна.

case WM_LBUTTONDOWN: 
    OnLButtonDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (DWORD)wParam);
    return 0;

case WM_LBUTTONUP: 
    OnLButtonUp();
    return 0;

case WM_MOUSEMOVE: 
    OnMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (DWORD)wParam);
    return 0;

Наконец, реализуйте сами обработчики сообщений.

Левая кнопка вниз

Для сообщения вниз слева выполните указанные ниже действия.

  1. Вызовите SetCapture , чтобы начать захват мыши.
  2. Сохраните положение щелчка мыши в переменной ptMouse . Эта позиция определяет верхний левый угол ограничивающего прямоугольника для эллипса.
  3. Сброс структуры эллипса.
  4. Вызовите InvalidateRect. Эта функция принудительно перерисовывал окно.
void MainWindow::OnLButtonDown(int pixelX, int pixelY, DWORD flags)
{
    SetCapture(m_hwnd);
    ellipse.point = ptMouse = DPIScale::PixelsToDips(pixelX, pixelY);
    ellipse.radiusX = ellipse.radiusY = 1.0f; 
    InvalidateRect(m_hwnd, NULL, FALSE);
}

Перемещение мыши

Для сообщения о перемещении мыши проверка, находится ли левая кнопка мыши. Если это так, пересчитать многоточие и перекрасить окно. В Direct2D эллипс определяется центральной точкой и радиусами x и y. Нам нужно нарисовать эллипс, который соответствует ограничивающему прямоугольнику, определенному точкой мыши вниз (ptMouse) и текущим положением курсора (x, y), поэтому для поиска ширины, высоты и положения эллипса требуется немного арифметики.

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

Схема: эллипс с радиусами x и y.

void MainWindow::OnMouseMove(int pixelX, int pixelY, DWORD flags)
{
    if (flags & MK_LBUTTON) 
    { 
        const D2D1_POINT_2F dips = DPIScale::PixelsToDips(pixelX, pixelY);

        const float width = (dips.x - ptMouse.x) / 2;
        const float height = (dips.y - ptMouse.y) / 2;
        const float x1 = ptMouse.x + width;
        const float y1 = ptMouse.y + height;

        ellipse = D2D1::Ellipse(D2D1::Point2F(x1, y1), width, height);

        InvalidateRect(m_hwnd, NULL, FALSE);
    }
}

Левая кнопка вверх

Для сообщения с левой кнопкой вверх просто вызовите ReleaseCapture , чтобы освободить захват мыши.

void MainWindow::OnLButtonUp()
{
    ReleaseCapture(); 
}

Следующая