共用方式為


使用者輸入:擴充範例

讓我們結合我們所瞭解的使用者輸入,以建立簡單的繪圖程式。 以下是程式的螢幕快照:

繪圖程式的螢幕快照

用戶可以以數種不同的色彩繪製省略號,然後選取、移動或刪除省略號。 為了讓UI保持簡單,程式不會讓用戶選取省略號色彩。 相反地,程式會自動迴圈查看預先定義的色彩清單。 程式不支援省略號以外的任何圖形。 顯然,此計劃不會贏得任何圖形軟體獎。 不過,從中學習仍然是一個有用的範例。 您可以從簡單繪圖範例下載完整的原始程式碼。 本節只會說明一些重點。

省略號是由包含橢圓形數據 (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<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。 一般規則是與視窗或滑鼠輸入相關的函式使用圖元,而 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(); 
}

如您所見,滑鼠輸入的訊息處理程式全都有分支程式代碼,視目前模式而定。 這是這個相當簡單的程式可接受的設計。 不過,如果新增了新的模式,它可能會變得太複雜。 對於較大的程式,model-view-controller (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;

摘要

在本課程模組中,您已瞭解如何處理滑鼠和鍵盤輸入;如何定義鍵盤快捷方式;以及如何更新數據指標影像,以反映程式的目前狀態。