鼠标移动

鼠标移动时,Windows 会发布 WM_MOUSEMOVE 消息。 默认情况下, WM_MOUSEMOVE 转到包含光标的窗口。 可以通过 捕获 鼠标来替代此行为,下一部分将对此进行介绍。

WM_MOUSEMOVE消息包含与鼠标单击消息相同的参数。 lParam 的最低 16 位包含 x 坐标,接下来的 16 位包含 y 坐标。 使用 GET_X_LPARAMGET_Y_LPARAM 宏从 lParam 中解包坐标。 wParam 参数包含按位 OR 标志,指示其他鼠标按钮的状态以及 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 函数。

通常按以下方式使用 SetCaptureReleaseCapture

  1. 当用户按下鼠标左键时,调用 SetCapture 开始捕获鼠标。
  2. 响应鼠标移动消息。
  3. 当用户松开鼠标左键时,请调用 ReleaseCapture

示例:绘制圆圈

让我们通过允许用户使用鼠标绘制圆圈,从 模块 3 扩展 Circle 程序。 从 Direct2D 圆形示例 程序开始。 我们将修改此示例中的代码以添加简单绘图。 首先,向 类添加新成员变量 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) 。 若要正确处理高 DPI 设置,必须将像素坐标转换为 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;

创建 Direct2D 工厂对象后,在 WM_CREATE 处理程序中调用 DPIScale::Initialize

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_LPARAMGET_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-radii 定义。 我们希望绘制一个椭圆,该椭圆适合由鼠标向下点 (ptMouse) 定义的边界框,当前光标位置 (xy) ,因此需要一些算术来查找椭圆的宽度、高度和位置。

以下代码重新计算椭圆,然后调用 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(); 
}

下一步