天天看點

Windows桌面應用程式(1-2-5-8th) 使用者輸入:擴充示例

讓我們結合我們學習的有關使用者輸入的所有内容來建立一個簡單的繪圖程式。 這是該程式的螢幕截圖:

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

傳回目錄

繼續閱讀