Aracılığıyla paylaş


Kullanıcı girişi: genişletilmiş örnek

Basit bir çizim programı oluşturmak için kullanıcı girişi hakkında öğrendiğimiz her şeyi birleştirelim. Programın ekran görüntüsü aşağıdadır:

Çizim programıekran görüntüsü

Kullanıcı birkaç farklı renkte üç nokta çizebilir ve üç noktayı seçebilir, taşıyabilir veya silebilir. Kullanıcı arabirimini basit tutmak için, program kullanıcının üç nokta renklerini seçmesine izin vermez. Bunun yerine, program otomatik olarak önceden tanımlanmış bir renk listesi arasında döngüler oluşturur. Program üç nokta dışında hiçbir şekli desteklemez. Açıkçası, bu program grafik yazılımı için herhangi bir ödül kazanmayacak. Ancak, yine de öğrenmek için yararlı bir örnektir. Kaynak kodun tamamını Basit Çizim Örneğiindirebilirsiniz. Bu bölümde yalnızca bazı önemli noktalar ele alınacaktır.

Üç nokta, programda üç nokta verilerini (D2D1_ELLIPSE) ve rengi (D2D1_COLOR_F) içeren bir yapıyla temsil edilir. Yapı ayrıca iki yöntem tanımlar: üç noktayı çizmek için bir yöntem ve isabet testi gerçekleştirmek için bir yöntem.

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

Program, her üç nokta için dolguyu ve ana hattı çizmek için aynı düz renkli fırçayı kullanır ve rengi gerektiği gibi değiştirir. Direct2D'de düz renkli fırçanın rengini değiştirmek verimli bir işlemdir. Bu nedenle, düz renkli fırça nesnesi SetColor yöntemini destekler.

Üç nokta bir STL listesinde kapsayıcıda depolanır:

    list<shared_ptr<MyEllipse>>             ellipses;

Not

shared_ptr, TR1'de C++'a eklenen ve C++0x ile resmileştirilen bir akıllı işaretçi sınıfıdır. Visual Studio 2010, shared_ptr ve diğer C++0x özellikleri için destek ekler. Daha fazla bilgi için Visual Studio 2010'da Yeni C++ ve MFC Özelliklerini Keşfetme MSDN Dergisi makalesine bakın.

 

Programın üç modu vardır:

  • Çizim modu. Kullanıcı yeni üç nokta çizebilir.
  • Seçim modu. Kullanıcı bir üç nokta seçebilir.
  • Sürükleme modu. Kullanıcı seçili bir üç noktayı sürükleyebilirsiniz.

Kullanıcı, Hızlandırıcı Tabloları'nde açıklanan klavye kısayollarını kullanarak çizim modu ile seçim modu arasında geçiş yapabilir. Kullanıcı bir üç noktaya tıklarsa, seçim modundan program sürükleme moduna geçer. Kullanıcı fare düğmesini serbest bıraktığında seçim moduna geri döner. Geçerli seçim, üç nokta listesinde yineleyici olarak depolanır. yardımcı yöntemi MainWindow::Selection seçili üç noktanın işaretçisini veya seçim yoksa nullptr değeri döndürür.

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

    void    ClearSelection() { selection = ellipses.end(); }

Aşağıdaki tabloda, üç modun her birinde fare girişinin etkileri özetlemektedir.

Fare Girişi Çizim Modu Seçim Modu Sürükleme Modu
Sol düğme aşağı Fare yakalamayı ayarlayın ve yeni bir üç nokta çizmeye başlayın. Geçerli seçimi serbest bırakın ve isabet testi yapın. Bir üç noktaya isabet edilirse, imleci yakalayın, üç noktayı seçin ve sürükleme moduna geçin. Eylem yok.
Fareyle hareket etme Sol düğme aşağıysa, üç noktayı yeniden boyutlandırın. Eylem yok. Seçili üç noktayı taşıma.
Sol düğme yukarı Üç nokta çizmeyi durdurun. Eylem yok. Seçim moduna geçin.

 

MainWindow sınıfında aşağıdaki yöntem WM_LBUTTONDOWN iletileri işler.

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

Fare koordinatları piksel cinsinden bu yönteme geçirilir ve ardından DIP'lere dönüştürülür. Bu iki ünitenin karıştırılmaması önemlidir. Örneğin, DragDetect işlevi piksel kullanır, ancak çizim ve isabet sınaması DIP'leri kullanır. Genel kural, windows veya fare girişiyle ilgili işlevlerin pikselleri, Direct2D ve DirectWrite ise IP'leri kullanmasıdır. Programınızı her zaman yüksek DPI ayarında test edin ve programınızı DPI kullanan olarak işaretlemeyi unutmayın. Daha fazla bilgi için bkz. DPI ve Device-Independent Piksel.

WM_MOUSEMOVE iletileri işleyen kod aşağıdadır.

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

Üç noktayı yeniden boyutlandırma mantığı daha önce Örnek: Çizim Daireleri bölümünde açıklanmıştır. Ayrıca InvalidateRectçağrısına da dikkat edin. Bu, pencerenin yeniden boyanmasını sağlar. Aşağıdaki kod WM_LBUTTONUP iletileri işler.

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

Gördüğünüz gibi, fare girişi için ileti işleyicilerinin tümü geçerli moda bağlı olarak dallanma koduna sahiptir. Bu oldukça basit bir program için kabul edilebilir bir tasarımdır. Ancak yeni modlar eklenirse çok karmaşık hale gelebilir. Daha büyük bir program için model-görünüm denetleyicisi (MVC) mimarisi daha iyi bir tasarım olabilir. Bu tür bir mimaride, kullanıcı girişini işleyen denetleyicisi, uygulama verilerini yöneten modelinden ayrılır.

Program modları değiştirdiğinde, imleç kullanıcıya geri bildirimde bulunmak üzere değişir.

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

Son olarak, pencere bir WM_SETCURSOR iletisi aldığında imleci ayarlamayı unutmayın:

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

Özet

Bu modülde fare ve klavye girişini işlemeyi öğrendiniz; klavye kısayollarını tanımlama; ve imleç görüntüsünü programın geçerli durumunu yansıtacak şekilde güncelleştirme.