Benutzerdefinierte (vorlagenbasierte) XAML-Steuerelemente mit C++/WinRT

Wichtig

Wichtige Konzepte und Begriffe im Zusammenhang mit der Nutzung und Erstellung von Laufzeitklassen mit C++/WinRT findest du unter Verwenden von APIs mit C++/WinRT sowie unter Erstellen von APIs mit C++/WinRT.

Eines der leistungsstärksten Features der universellen Windows-Plattform (UWP) ist die Flexibilität des Benutzeroberflächenstapels hinsichtlich der Erstellung benutzerdefinierter Steuerelemente auf der Grundlage des XAML-Typs Control. Das XAML-Benutzeroberflächenframework bietet Features wie benutzerdefinierte Abhängigkeitseigenschaften und angefügte Eigenschaften sowie Steuerelementvorlagen zur mühelosen Erstellung vielseitiger und anpassbarer Steuerelemente. In diesem Thema werden die Schritte zum Erstellen eines benutzerdefinierten (vorlagenbasierten) Steuerelements mit C++/WinRT beschrieben.

Erstellen einer leeren App (BgLabelControlApp)

Erstelle zunächst ein neues Projekt in Microsoft Visual Studio. Erstelle ein Leere App (C++/WinRT) -Projekt, lege dessen Name auf BgLabelControlApp fest, und stelle sicher, dass Legen Sie die Projektmappe und das Projekt im selben Verzeichnis ab deaktiviert ist (damit deine Ordnerstruktur mit der exemplarischen Vorgehensweise übereinstimmt). Die neueste allgemein verfügbare Version von Windows SDK (d. h. keine Vorschauversion).

Führe den Buildvorgang für dein Projekt erst aus, wenn du weiter unten in diesem Thema dazu aufgefordert wirst.

Hinweis

Informationen zum Einrichten von Visual Studio für die C++/WinRT-Entwicklung – einschließlich Installieren und Verwenden der C++/WinRT Visual Studio-Erweiterung (VSIX) und des NuGet-Pakets (die zusammen die Projektvorlage und Buildunterstützung bereitstellen) – finden Sie unter Visual Studio-Unterstützung für C++/WinRT.

Wir erstellen eine neue Klasse, um ein benutzerdefiniertes (vorlagenbasiertes) Steuerelement darzustellen. Die Klasse wird innerhalb der gleichen Kompilierungseinheit erstellt und genutzt. Da diese Klasse jedoch per XAML-Markup instanziierbar sein soll, verwenden wir eine Laufzeitklasse. Und wir verwenden C++/WinRT, um sie zu schreiben und zu nutzen.

Um eine neue Laufzeitklasse zu erstellen, müssen wir dem Projekt zunächst ein neues Element vom Typ Midl-Datei (.idl) hinzufügen. Nenne es BgLabelControl.idl. Lösche den Standardinhalt von BgLabelControl.idl, und füge die folgende Laufzeitklassendeklaration ein:

// BgLabelControl.idl
namespace BgLabelControlApp
{
    runtimeclass BgLabelControl : Windows.UI.Xaml.Controls.Control
    {
        BgLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

Das obige Listing veranschaulicht das Muster zum Deklarieren einer Abhängigkeitseigenschaft (Dependency Property, DP). Eine Abhängigkeitseigenschaft besteht aus zwei Teilen: Zuerst wird eine schreibgeschützte statische Eigenschaft vom Typ DependencyProperty deklariert. Sie hat den Namen deiner Abhängigkeitseigenschaft plus Property. Diese statische Eigenschaft wird in deiner Implementierung verwendet. Danach wird eine Instanzeigenschaft mit Lese-/Schreibzugriff sowie mit dem Typ und dem Namen deiner Abhängigkeitseigenschaft deklariert. Wenn du anstelle einer Abhängigkeitseigenschaft eine angefügte Eigenschaft erstellen möchtest, sieh dir die Codebeispiele unter Benutzerdefinierte angefügte Eigenschaften an.

Hinweis

Wenn du eine Abhängigkeitseigenschaft mit einem Gleitkommatyp erstellen möchtest, lege sie auf double (Double in MIDL 3.0) fest. Wenn du eine Abhängigkeitseigenschaft vom Typ float (Single in MIDL) deklarierst und implementierst und anschließend im XAML-Markup einen Wert für diese Abhängigkeitseigenschaft festlegst, tritt der folgende Fehler auf: Fehler beim Erstellen von "Windows.Foundation.Single" aus dem Text "<NUMBER>".

Speichern Sie die Datei. Im aktuellen Zustand wird das Projekt zwar nicht vollständig erstellt, die Erstellung ist jedoch hilfreich, da dadurch die Quellcodedateien generiert werden, in denen die Laufzeitklasse BgLabelControl implementiert wird. Erstellen Sie daher als Nächstes das Projekt. (Die in dieser Phase zu erwartenden Buildfehler sind auf ein „nicht behobenes externes Problem“ zurückzuführen.)

Während des Buildprozesses wird das Tool midl.exe ausgeführt, um eine Windows-Runtime-Metadatendatei (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) zu erstellen, die die Laufzeitklasse beschreibt. Danach wird das Tool cppwinrt.exe ausgeführt, um Quellcodedateien zu generieren, die dich bei der Erstellung und Nutzung deiner Laufzeitklasse unterstützen. Diese Dateien enthalten Stubs zur Implementierung der Laufzeitklasse BgLabelControl, die du in deiner IDL deklariert hast. Diese Stubs sind \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h und BgLabelControl.cpp.

Kopiere die Stub-Dateien BgLabelControl.h und BgLabelControl.cpp aus \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ in den Projektordner \BgLabelControlApp\BgLabelControlApp\. Vergewissern Sie sich im Projektmappen-Explorer, dass Alle Dateien anzeigen aktiviert ist. Klicke mit der rechten Maustaste auf die kopierten Stub-Dateien, und klicke auf Zu Projekt hinzufügen.

Du siehst eine static_assert-Deklaration am Anfang von BgLabelControl.h und BgLabelControl.cpp, die du entfernen musst. Jetzt wird das Projekt erstellt.

Implementieren der benutzerdefinierten Steuerelementklasse BgLabelControl

Als Nächstes öffnen wir \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h und BgLabelControl.cpp und implementieren unsere Laufzeitklasse. Ändere in BgLabelControl.h den Konstruktor, um den Standarddesignschlüssel festzulegen, implementiere Label und LabelProperty, füge einen statischen Ereignishandler namens OnLabelChanged für die Verarbeitung von Wertänderungen der Abhängigkeitseigenschaft hinzu, und füge einen privaten Member zum Speichern des Unterstützungsfelds für LabelProperty hinzu.

Danach sieht BgLabelControl.h wie folgt aus: Du kannst diese Codezeile kopieren und einfügen, um den Inhalt von BgLabelControl.h zu ersetzen.

// BgLabelControl.h
#pragma once
#include "BgLabelControl.g.h"

namespace winrt::BgLabelControlApp::implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl>
    {
        BgLabelControl() { DefaultStyleKey(winrt::box_value(L"BgLabelControlApp.BgLabelControl")); }

        winrt::hstring Label()
        {
            return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
        }

        void Label(winrt::hstring const& value)
        {
            SetValue(m_labelProperty, winrt::box_value(value));
        }

        static Windows::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

        static void OnLabelChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);

    private:
        static Windows::UI::Xaml::DependencyProperty m_labelProperty;
    };
}
namespace winrt::BgLabelControlApp::factory_implementation
{
    struct BgLabelControl : BgLabelControlT<BgLabelControl, implementation::BgLabelControl>
    {
    };
}

Definiere in BgLabelControl.cpp die statischen Member: Du kannst diese Codezeile kopieren und einfügen, um den Inhalt von BgLabelControl.cpp zu ersetzen.

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#include "BgLabelControl.g.cpp"

namespace winrt::BgLabelControlApp::implementation
{
    Windows::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
        Windows::UI::Xaml::DependencyProperty::Register(
            L"Label",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
            Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
    );

    void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
    {
        if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
        {
            // Call members of the projected type via theControl.

            BgLabelControlApp::implementation::BgLabelControl* ptr{ winrt::get_self<BgLabelControlApp::implementation::BgLabelControl>(theControl) };
            // Call members of the implementation type via ptr.
        }
    }
}

In dieser exemplarischen Vorgehensweise wird OnLabelChanged nicht verwendet. Es ist jedoch vorhanden, um zu zeigen, wie du eine Abhängigkeitseigenschaft mit einem PropertyChanged-Rückruf registrierst. Die Implementierung von OnLabelChanged zeigt auch, wie du einen abgeleiteten projizierten Typ eines projizierten Basistyps (in diesem Fall: DependencyObject) erhältst. Darüber hinaus wird gezeigt, wie du anschließend einen Zeiger auf den Typ erhältst, der den projizierten Typ implementiert. Dieser zweite Vorgang ist natürlich nur in dem Projekt möglich, das den projizierten Typ implementiert (also in dem Projekt, das die Laufzeitklasse implementiert).

Hinweis

Falls nicht mindestens die Windows SDK-Version 10.0.17763.0 (Windows 10, Version 1809) installiert ist, muss im obigen Ereignishandler für die Änderung der Abhängigkeitseigenschaft winrt::from_abi (anstelle von winrt::get_self) aufgerufen werden.

Entwerfen des Standardstils für BgLabelControl

BgLabelControl legt in seinem Konstruktor einen Standardstilschlüssel für sich selbst fest. Aber was ist ein Standardstil? Ein benutzerdefiniertes Steuerelement (mit Vorlagen) muss über einen Standardstil (mit einer Standardvorlage für das Steuerelement) verfügen, mit dem es sich selbst rendern kann, falls der Consumer des Steuerelements keinen Stil und/oder keine Vorlage festlegt. In diesem Abschnitt fügen wir dem Projekt eine Markupdatei mit unserem Standardstil hinzu.

Vergewissere dich, dass Alle Dateien anzeigen weiterhin aktiviert ist (im Projektmappen-Explorer). Erstelle unter deinem Projektknoten einen neuen Ordner (keinen Filter, sondern einen Ordner) mit dem Namen „Themes”. Füge unter Themes ein neues Element vom Typ Visual C++>XAML>XAML-Ansicht hinzu, und nenne es „Generic.xaml“. Ordner- und Dateiname müssen wie angegeben festgelegt werden, damit das XAML-Framework den Standardstil für ein benutzerdefiniertes Steuerelement findet. Lösche den Standardinhalt von Generic.xaml, und füge das folgende Markup ein:

<!-- \Themes\Generic.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BgLabelControlApp">

    <Style TargetType="local:BgLabelControl" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:BgLabelControl">
                    <Grid Width="100" Height="100" Background="{TemplateBinding Background}">
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Label}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

In diesem Fall legt der Standardstil als einzige Eigenschaft die Steuerelementvorlage fest. Die Vorlage besteht aus einem Quadrat (dessen Hintergrund an die Eigenschaft Background gebunden ist, über die alle Instanzen des XAML-Typs Control verfügen) sowie aus einem Textelement (dessen Text an die Abhängigkeitseigenschaft BgLabelControl::Label gebunden ist).

Hinzufügen einer Instanz von BgLabelControl zur UI-Hauptseite

Öffne MainPage.xaml. Darin befindet sich das XAML-Markup für unsere UI-Hauptseite. Füge direkt nach dem Element Button (innerhalb von StackPanel) das folgende Markup hinzu:

<local:BgLabelControl Background="Red" Label="Hello, World!"/>

Füge außerdem die folgende include-Anweisung zu MainPage.h hinzu, um den Typ MainPage (eine Kombination aus XAML-Kompilierungsmarkup und imperativem Code) auf den benutzerdefinierten Steuerelementtyp BgLabelControl aufmerksam zu machen. Wenn du BgLabelControl von einer anderen XAML-Seite aus verwenden möchtest, musst du der Headerdatei für diese Seite die gleiche include-Anweisung hinzufügen. Alternativ kannst du auch einfach deiner vorkompilierten Headerdatei eine einzelne include-Anweisung hinzufügen.

// MainPage.h
...
#include "BgLabelControl.h"
...

Erstelle nun das Projekt, und führe es aus. Wie du siehst, wird die Standardsteuerelementvorlage an den Hintergrundpinsel (und an die Bezeichnung) der Instanz BgLabelControl im Markup gebunden.

In dieser exemplarischen Vorgehensweise wurde ein einfaches Beispiel für ein benutzerdefiniertes (vorlagenbasiertes) Steuerelement in C++/WinRT gezeigt. Du kannst deine eigenen Steuerelemente nach Belieben mit verschiedensten Features ausstatten. Ein benutzerdefiniertes Steuerelement kann beispielsweise ein komplexes bearbeitbares Datenraster, ein Videoplayer oder eine 3D-Geometrievisualisierung sein.

Implementieren überschreibbarer Methoden wie MeasureOverride und OnApplyTemplate

Siehe Abschnitt unter Aufrufen und Überschreiben Ihres Basistyps mit C++/WinRT.

Wichtige APIs