Udostępnij przez


Dane wejściowe użytkownika: przykład rozszerzony

Połączmy wszystko, czego dowiedzieliśmy się o danych wejściowych użytkownika, aby utworzyć prosty program rysunkowy. Oto zrzut ekranu programu:

zrzut ekranu programu rysunkowego

Użytkownik może narysować wielokropek w kilku różnych kolorach i wybrać, przenieść lub usunąć wielokropek. Aby zachować prosty interfejs użytkownika, program nie pozwala użytkownikowi wybrać kolorów wielokropka. Zamiast tego program automatycznie przechodzi przez wstępnie zdefiniowaną listę kolorów. Program nie obsługuje żadnych kształtów innych niż wielokropek. Oczywiście ten program nie wygra żadnych nagród za oprogramowanie graficzne. Jednak nadal jest to przydatny przykład, z których można się uczyć. Pełny kod źródłowy można pobrać z prostego przykładu rysunku. Ta sekcja zawiera tylko niektóre najważniejsze informacje.

Wielokropek jest reprezentowany w programie przez strukturę zawierającą dane wielokropka (D2D1_ELLIPSE) i kolor (D2D1_COLOR_F). Struktura definiuje również dwie metody: metodę narysowania wielokropka i metodę przeprowadzania testów trafień.

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 używa tego samego pędzla w kolorze stałym, aby narysować wypełnienie i kontur dla każdego wielokropka, zmieniając kolor zgodnie z potrzebami. W trybie Direct2D zmiana koloru pędzla w kolorze stałym jest wydajną operacją. Dlatego obiekt pędzla w kolorze stałym obsługuje metodę SetColor.

Wielokropek jest przechowywany w kontenerze listy STL:

    list<shared_ptr<MyEllipse>>             ellipses;

Nuta

shared_ptr to klasa inteligentnego wskaźnika, która została dodana do języka C++ w tr1 i sformalizowane w języku C++0x. Program Visual Studio 2010 dodaje obsługę shared_ptr i innych funkcji języka C++0x. Aby uzyskać więcej informacji, zobacz artykuł MSDN Magazine Eksplorowanie nowych funkcji języka C++ i MFC w programie Visual Studio 2010.

 

Program ma trzy tryby:

  • Tryb rysowania. Użytkownik może rysować nowe wielokropki.
  • Tryb zaznaczenia. Użytkownik może wybrać wielokropek.
  • Tryb przeciągania. Użytkownik może przeciągnąć zaznaczony wielokropek.

Użytkownik może przełączać się między trybem rysowania i trybem zaznaczenia, używając tych samych skrótów klawiaturowych opisanych w Tabele akceleratora. W trybie wyboru program przełącza się w tryb przeciągania, jeśli użytkownik kliknie wielokropek. Przełącza się z powrotem do trybu zaznaczenia, gdy użytkownik zwalnia przycisk myszy. Bieżące zaznaczenie jest przechowywane jako iterator na liście wielokropka. Metoda pomocnika MainWindow::Selection zwraca wskaźnik do wybranego wielokropka lub wartość nullptr, jeśli nie ma zaznaczenia.

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

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

W poniższej tabeli przedstawiono podsumowanie efektów wprowadzania myszy w każdym z trzech trybów.

Wprowadzanie myszy Tryb rysowania Tryb zaznaczenia Tryb przeciągania
Lewy przycisk w dół Ustaw przechwytywanie myszy i zacznij rysować nowy wielokropek. Zwolnij bieżące zaznaczenie i wykonaj test trafienia. Jeśli wielokropek zostanie trafiony, przechwyć kursor, wybierz wielokropek i przełącz się w tryb przeciągania. Brak akcji.
Ruch myszą Jeśli lewy przycisk nie działa, zmień rozmiar wielokropka. Brak akcji. Przenieś zaznaczony wielokropek.
Lewy przycisk w górę Zatrzymaj rysowanie wielokropka. Brak akcji. Przełącz się do trybu zaznaczenia.

 

Poniższa metoda w klasie MainWindow obsługuje komunikaty 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);
}

Współrzędne myszy są przekazywane do tej metody w pikselach, a następnie konwertowane na dips. Ważne jest, aby nie mylić tych dwóch jednostek. Na przykład funkcja DragDetect używa pikseli, ale rysowanie i testowanie trafień używają adresów DIPs. Ogólną regułą jest to, że funkcje związane z oknami lub wprowadzaniem myszy używają pikseli, podczas gdy direct2D i DirectWrite używają adresów DIPs. Zawsze testuj program w ustawieniu o wysokiej rozdzielczości DPI i pamiętaj, aby oznaczyć program jako obsługujący dpi. Aby uzyskać więcej informacji, zobacz DPI i Device-Independent Pixel.

Oto kod obsługujący komunikaty 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);
    }
}

Logika zmiany rozmiaru wielokropka została opisana wcześniej w sekcji Przykład: Okręgi rysunkowe. Zwróć również uwagę na wywołanie metody InvalidateRect. Dzięki temu okno zostanie przemalowane. Poniższy kod obsługuje komunikaty WM_LBUTTONUP.

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

Jak widać, programy obsługi komunikatów dla danych wejściowych myszy mają kod rozgałęziania, w zależności od bieżącego trybu. Jest to akceptowalny projekt dla tego dość prostego programu. Jednak może to szybko stać się zbyt złożone, jeśli dodawane są nowe tryby. W przypadku większego programu architektura mvC (model-view-controller) może być lepszym projektem. W tej architekturze kontroler , który obsługuje dane wejściowe użytkownika, jest oddzielony od modelu , który zarządza danymi aplikacji.

Gdy program przełącza tryby, kursor zmienia się, aby przekazać opinię użytkownikowi.

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

Na koniec pamiętaj, aby ustawić kursor, gdy okno odbiera komunikat WM_SETCURSOR:

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

Streszczenie

W tym module przedstawiono sposób obsługi wprowadzania myszy i klawiatury; jak definiować skróty klawiaturowe; oraz sposób aktualizowania obrazu kursora w celu odzwierciedlenia bieżącego stanu programu.