欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Windows桌面应用程序(1-2-5-8th) 用户输入:扩展示例

程序员文章站 2022-07-15 08:06:11
...

让我们结合我们学习的有关用户输入的所有内容来创建一个简单的绘图程序。 这是该程序的屏幕截图:
Windows桌面应用程序(1-2-5-8th) 用户输入:扩展示例
用户可以绘制几种不同颜色的椭圆,并选择,移动或删除椭圆。 为了保持UI简单,程序不允许用户选择椭圆颜色。 相反,程序会自动循环显示预定义的颜色列表。 该程序不支持椭圆以外的任何形状。 显然,这个程序不会赢得任何图形软件奖。 但是,它仍然是一个有用的例子。 您可以从Simple Drawing Sample下载完整的源代码。 本节仅介绍一些亮点。

椭圆在程序中由包含椭圆数据(D2D1_ELLIPSE)和颜色(D2D1_COLOR_F)的结构表示。 该结构还定义了两种方法:绘制椭圆的方法和执行命中测试的方法。

struct MyEllipse{
    D2D1_ELLIPSE ellipse;
    D2D1_COLOR_F color;
    void Draw(ID2D1RenderTarget *pRT,ID2D1SolidColorBrush *pBrush){
        pBrush->SetColor(color);
        pRT->FillEllipse(ellipse,pBrush);
        pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black));
        pRT->DrawEllipse(ellipse,pBrush,1.0f);
    }
    BOOL HitTest(float x,float y){
        const float a=ellipse.radiusX;
        const float b=ellipse.radiusY;
        const float x1=x-ellipse.point.x;
        const float y1=y-ellipse.point.y;
        const float d=((x1*x1)/(a*a))+((y1*y1)/(b*b));
        return d<=1.0f;
    }
};

该程序使用相同的纯色画笔绘制每个椭圆的填充和轮廓,根据需要更改颜色。 在Direct2D中,更改纯色画笔的颜色是一种有效的操作。 因此,纯色画笔对象支持SetColor方法。

椭圆存储在STL list容器中:

list<shared_ptr<MyEllipse>> ellipses;

注意
shared_ptr是一个智能指针类,它被添加到TR1中的C ++中并在C ++ 0x中形式化。 Visual Studio 2010添加了对shared_ptr和其他C ++ 0x功能的支持。有关详细信息,请参阅MSDN Magazine中的在Visual Studio 2010中探索新的C ++和MFC功能。(某些语言和国家/地区可能无法使用此资源。)

该计划有三种模式:

  • 绘制模式。 用户可以绘制新的省略号。
  • 选择模式。 用户可以选择椭圆。
  • 拖动模式。 用户可以拖动选定的椭圆。

用户可以使用加速器表中描述的相同键盘快捷键在绘图模式和选择模式之间切换。从选择模式,如果用户点击椭圆,程序将切换到拖动模式。 当用户释放鼠标按钮时,它会切换回选择模式。 当前选择作为迭代器存储到省略号列表中。 辅助方法MainWindow::Selection返回指向所选椭圆的指针,如果没有选择则返回值nullptr

list<shared_ptr<MyEllipse>>::iterator selection;
shared_ptr<MyEllipse> Selection(){
    if(selection==ellipses.end())
        return nullptr;
    else
        return *selection;
}
void ClearSelection(){
    selection=ellipses.end();
}

下表总结了三种模式中鼠标输入的效果。

鼠标输入 绘制模式 选择模式 拖动模式
左键按下 设置鼠标捕获并开始绘制新的椭圆。 释放当前选择并执行命中测试。 如果点击椭圆,则捕获光标,选择椭圆,然后切换到拖动模式。 没有动作。
鼠标移动 如果左键被按下,请调整椭圆的大小。 没有动作。 移动选定的椭圆。
左键松开 停止绘制椭圆。 没有动作。 切换到选择模式。

MainWindow类中的以下方法处理WM_LBUTTONDOWN消息。

void MainWindow::OnLButtonDown(int pixelX,int pixelY,DWORD flags){
    const float dipX=DPIScale::PixelsToDipsX(pixelX);
    const float dipY=DPIScale::PixelsToDipsY(pixelY);
    if(mode==DrawMode){
        POINT pt={pixelX,pixelY};
        if(DragDetect(m_hwnd,pt)){
            SetCapture(m_hwnd);
            // Start a new ellipse.
            InsertEllipse(dipX,dipY);
        }
    }
    else{
        ClearSelection();
        if(HitTest(dipX,dipY)){
            SetCapture(m_hwnd);
            ptMouse=Selection()->ellipse.point;
            ptMouse.x-=dipX;
            ptMouse.y-=dipY;
            SetMode(DragMode);
        }
    }
    InvalidateRect(m_hwnd,NULL,FALSE);
}

鼠标坐标以像素为单位传递给此方法,然后转换为DIP。 重要的是不要混淆这两个单位。 例如,DragDetect函数使用像素,但绘图和命中测试使用DIP。 一般规则是与Windows或鼠标输入相关的功能使用像素,而Direct2D和DirectWrite使用DIP。 始终以高DPI设置测试程序,并记住将程序标记为支持DPI。 有关更多信息,请参阅DPI和设备无关的像素

这是处理WM_MOUSEMOVE消息的代码。

void MainWindow::OnMouseMove(int pixelX,int pixelY,DWORD flags){
    const float dipX=DPIScale::PixelsToDipsX(pixelX);
    const float dipY=DPIScale::PixelsToDipsY(pixelY);
    if((flags&MK_LBUTTON) && Selection()){
        if(mode==DrawMode){
            // Resize the ellipse.
            const float width=(dipX-ptMouse.x)/2;
            const float height=(dipY-ptMouse.y)/2;
            const float x1=ptMouse.x+width;
            const float y1=ptMouse.y+height;
            Selection()->ellipse=D2D1::Ellipse(D2D1::Point2F(x1,y1),width,height);
        }
        else if(mode==DragMode){
            // Move the ellipse.
            Selection()->ellipse.point.x=dipX+ptMouse.x;
            Selection()->ellipse.point.y=dipY+ptMouse.y;
        }
        InvalidateRect(m_hwnd,NULL,FALSE);
    }
}

之前在“示例:绘制圆”一节中描述了调整椭圆大小的逻辑。 另请注意对InvalidateRect的调用。 这样可以确保重新绘制窗口。 以下代码处理WM_LBUTTONUP消息。

void MainWindow::OnLButtonUp(){
    if((mode==DrawMode) && Selection()){
        ClearSelection();
        InvalidateRect(m_hwnd,NULL,FALSE);
    }
    else if(mode==DragMode)
        SetMode(SelectMode);
    ReleaseCapture();
}

如您所见,鼠标输入的消息处理程序都具有分支代码,具体取决于当前模式。 这个相当简单的程序是可接受的设计。 但是,如果添加新模式,它可能很快变得太复杂。 对于更大的程序,模型 - 视图 - 控制器(MVC)架构可能是更好的设计。 在这种体系结构中,处理用户输入的控制器与管理应用程序数据的模型分离。

当程序切换模式时,光标会改变以向用户提供反馈。

void MainWindow::SetMode(Mode m){
    mode=m;
    // Update the cursor
    LPWSTR cursor;
    switch(mode){
        case DrawMode:
            cursor=IDC_CROSS;
            break;
        case SelectMode:
            cursor=IDC_HAND;
            break;
        case DragMode:
            cursor=IDC_SIZEALL;
            break;
    }
    hCursor=LoadCursor(NULL,cursor);
    SetCursor(hCursor);
}

最后,记得在窗口收到WM_SETCURSOR消息时设置光标:

case WM_SETCURSOR:
    if(LOWORD(lParam)==HTCLIENT){
        SetCursor(hCursor);
        return TRUE;
    }
    break;

概要

在本单元中,您学习了如何处理鼠标和键盘输入; 如何定义键盘快捷键; 以及如何更新光标图像以反映程序的当前状态。


原文链接:User Input: Extended Example

返回目录