Windows桌面应用程序(1-2-5-3rd) 鼠标移动

当鼠标移动时,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 Circle Sample程序开始。我们将修改此示例中的代码以添加简单的绘图。首先,向MainWindow类添加一个新的成员变量。

D2D1_POINT_2F ptMouse;

当用户拖动鼠标时,该变量存储鼠标向下的位置。在MainWindow构造函数中,初始化ellipseptMouse变量。

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和设备无关的像素。以下代码显示了将像素转换为DIP的辅助类。

class DPIScale{
    static float scaleX;
    static float scaleY;
    public:
    static void Initialize(ID2D1Factory *pFactory){
        FLOAT dpiX,dpiY;
        pFactory->GetDesktopDpi(&dpiX,&dpiY);
        scaleX=dpiX/96.0f;
        scaleY=dpiY/96.0f;
    }
    template<typename T>
    static D2D1_POINT_2F PixelsToDips(T x,T y){
        return D2D1::Point2F(static_cast<float>(x)/scaleX,static_cast<float>(y)/scaleY);
    }
};
float DPIScale::scaleX=1.0f;
float DPIScale::scaleY=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(pFactory);
    return 0;

要从鼠标消息中获取DIP中的鼠标坐标,请执行以下操作:

  1. 使用GET_X_LPARAMGET_Y_LPARAM宏来获取像素坐标。这些宏是在WindowsX.h中定义的,所以记得在你的项目中包含这个头文件。
  2. 调用DPIScale::PixelsToDipsX和DPIScale::PixelsToDipsY将像素转换为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重新绘制窗口。
这里写图片描述

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();
}

下一个
其他鼠标操作

猜你喜欢

转载自blog.csdn.net/qq_37422196/article/details/79242970
今日推荐