鼠标移动

鼠标移动时,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(); 
}

下一个