Benutzereingabe: Erweitertes Beispiel

Fassen wir nun alles Erlernte über die Benutzereingabe zusammen, um ein einfaches Zeichenprogramm zu erstellen. Screenshot des Programms:

Screenshot des Zeichenprogramms

Der Benutzer kann Ellipsen in mehreren unterschiedlichen Farben zeichnen und diese Ellipsen auswählen, verschieben oder löschen. Um die Benutzeroberfläche einfach zu halten, gestattet das Programm dem Benutzer nicht die Auswahl der Ellipsenfarben. Stattdessen beschränkt sich das Programm automatisch auf eine vorgegebene Liste von Farben. Das Programm unterstützt keine anderen Formen außer Ellipsen. Natürlich lassen sich mit diesem Programm keine Preise für die Grafiksoftware gewinnen. Es ist zum Lernen jedoch noch immer ein nützliches Beispiel. Sie können den vollständigen Quellcode aus dem Abschnitt über ein Einfaches Zeichenbeispiel herunterladen. In diesem Abschnitt werden lediglich einige wichtige Punkte behandelt.

Ellipsen werden im Programm durch eine Struktur dargestellt, die die Ellipsendaten und (D2D1_ELLIPSE) die Farbe (D2D1_COLOR_F) enthält. Die Struktur definiert auch zwei Methoden: Eine Methode, um die Ellipse zu zeichnen, und eine Methode, um Treffertests auszuführen.


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


Das Programm verwendet denselben Volltonpinsel, um den Füllbereich und die Konturen zu jeder Ellipse zu zeichnen. Dabei werden die Farben nach Bedarf geändert. In Direct2D ist die Änderung der Farbe eines Volltonpinsels ein effizienter Vorgang. Deshalb unterstützt das Volltonpinselobjekt eine SetColor-Methode.

Die Ellipsen werden in einem STL list-Container gespeichert:


    list<shared_ptr<MyEllipse>>             ellipses;


Hinweis  shared_ptr ist eine intelligente Zeigerklasse, die in TR1 zu C++ hinzugefügt wurde und in C++0x formalisiert wurde. Visual Studio 2010 bietet zusätzlich Unterstützung für shared_ptr und weitere C++0x-Funktionen. Weitere Informationen finden Sie im Abschnitt über Untersuchen der neuen C++- und MFC-Funktionen in Visual Studio 2010 im MSDN-Magazin. (Diese Ressource ist möglicherweise in einigen Sprachen nicht verfügbar. Auch ist sie möglicherweise in einigen Ländern nicht verfügbar.)

Das Programm weist drei Modi auf:

  • Zeichenmodus. Der Benutzer kann neue Ellipsen zeichnen.
  • Auswahlmodus. Der Benutzer kann eine Ellipse auswählen.
  • Dragmodus. Der Benutzer kann eine ausgewählte Ellipse ziehen.

Der Benutzer kann zwischen Dragmodus und Auswahlmodus umschalten, indem er dieselben Tastenkombinationen verwendet, die im Abschnitt über Zugriffstastentabellen beschrieben sind. Aus dem Auswahlmodus schaltet das Programm in den Dragmodus, wenn der Benutzer auf eine Ellipse klickt. Es wechselt zurück zum Auswahlmodus, wenn der Benutzer die Maustaste loslässt. Die aktuelle Auswahl wird als Iterator in der Liste der Ellipsen gespeichert. Die Hilfsmethode MainWindow::Selection gibt einen Zeiger an die ausgewählte Ellipse zurück, oder den Wert nullptr, wenn keine Auswahl vorliegt.


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

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


In der folgenden Tabelle werden die Effekte der Mauseingabe in jedem der drei Modi zusammengefasst.

MauseingabeZeichenmodus.Auswahlmodus.Dragmodus.
Gedrückte linke TasteLegen Sie die Mausauswahl fest. Beginnen Sie dann, eine neue Ellipse zu zeichnen.Geben Sie die aktuelle Auswahl frei und führen Sie einen Treffertest durch. Wenn eine Ellipse als Treffer angegeben wird, erfassen Sie den Cursor, wählen Sie die Ellipse aus und schalten Sie in den Dragmodus.Keine Aktion.
MausbewegungWenn die linke Taste gedrückt ist, ändern Sie die Größe der Ellipse.Keine Aktion.Verschieben Sie die ausgewählte Ellipse.
Nicht gedrückte linke TasteBeenden Sie das Zeichnen der Ellipse.Keine Aktion.Umschalten in den Auswahlmodus.

 

Die folgende Methode in der MainWindow-Klasse verarbeitet WM_LBUTTONDOWN-Meldungen.


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


Mauskoordinaten werden in Pixeln an diese Methode übergeben und anschließend in DIPs konvertiert. Es ist wichtig, diese zwei Einheiten nicht zu verwechseln. Die DragDetect-Funktion verwendet z. B. Pixel. Die Zeichen- und Treffertestvorgänge verwenden allerdings DIPs. Die allgemeine Regel lautet, dass Funktionen, die mit Fenstern oder Mauseingaben verknüpft sind, Pixel verwenden. Dahingegen verwenden Direct2D und DirectWrite die DIPs. Testen Sie das Programm stets mit hohen DPI-Werten. Beachten Sie, dass Sie das Programm als DPI-kompatibel kennzeichnen müssen. Weitere Informationen hierzu finden Sie unter DPI- und geräteunabhängige Pixel.

Mit diesem Code werden WM_MOUSEMOVE-Meldungen verarbeitet.


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


Die Logik, um die Größe einer Ellipse zu ändern, wurde zuvor beschrieben im Abschnitt Beispiel: Zeichnen von Kreisen. Beachten Sie auch den Aufruf von InvalidateRect. Dadurch wird sichergestellt, dass das Fenster neu gezeichnet wird. Im folgenden Code werden WM_LBUTTONUP-Meldungen verarbeitet.


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


Es ist ersichtlich, dass die Meldungshandler für die Mauseingabe je nach aktuellem Modus alle einen Verzweigungscode aufweisen. Das ist ein akzeptabler Entwurf für dieses recht einfache Programm. Es kann aber unter Umständen schnell zu komplex werden, wenn neue Modi hinzugefügt werden. Bei einem größeren Programm kann eine Modellansichtscontroller-Architektur (MVC-Architektur) ein besserer Entwurf sein. In dieser Art von Architektur wird der Controller, der die Benutzereingabe verarbeitet, vom Modell getrennt, das die Anwendungsdaten verwaltet.

Wenn das Programm zwischen den Modi umschaltet, ändert sich der Cursor, so dass der Benutzer Feedback erhält.


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


Beachten Sie abschließend auch, dass Sie den Cursor festlegen müssen, wenn das Fenster eine WM_SETCURSOR-Meldung erhält:


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


Zusammenfassung

In diesem Modul haben Sie erfahren, wie Maus- und Tastatureingabe gehandhabt werden. Sie haben gelernt, wie Tastenkombinationen definiert werden und wie das Cursorbild aktualisiert wird, um den aktuellen Status des Programms wiederzugeben.

 

 

Anzeigen:
© 2014 Microsoft