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

Important

Wichtige Konzepte und Begriffe, die Ihr Verständnis für die Nutzung und Erstellung von Laufzeitklassen mit C++/WinRT unterstützen, finden Sie unter Verwenden von APIs mit C++/WinRT und Erstellen von APIs mit C++/WinRT.

Eines der leistungsstärksten Features des Windows App SDK ist die Flexibilität, die der Benutzeroberflächenstapel bereitstellt, um benutzerdefinierte Steuerelemente basierend auf dem XAML-Steuerelementtyp zu erstellen. Das XAML-Benutzeroberflächenframework bietet Features wie benutzerdefinierte Abhängigkeitseigenschaften und angefügte Eigenschaften sowie Steuerelementvorlagen, die das Erstellen von funktionsreichen und anpassbaren Steuerelementen vereinfachen. In diesem Thema werden Sie durch die Schritte zum Erstellen eines benutzerdefinierten (vorlagenbasierten) Steuerelements mit C++/WinRT geführt.

Eine leere App erstellen (BgLabelControlApp)

Beginnen Sie damit, ein neues Projekt in Microsoft Visual Studio zu erstellen. Erstellen Sie für ein C++-Projekt eine leere App, verpackt (WinUI 3 in Desktop), legen Sie als Namen BgLabelControlApp fest, und stellen Sie (damit Ihre Ordnerstruktur mit der Anleitung übereinstimmt) sicher, dass Projektmappe und Projekt im selben Verzeichnis speichern nicht aktiviert ist. Richten Sie sich auf die neueste allgemein verfügbare (d. h. keine Vorschau)-Version des Windows SDK.

In einem späteren Abschnitt dieses Themas werden Sie aufgefordert, Ihr Projekt zu kompilieren (kompilieren Sie es jedoch erst dann).

Note

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

Wir werden eine neue Klasse erstellen, um ein benutzerdefiniertes (vorlagenbasiertes) Steuerelement darzustellen. Wir erstellen und verwenden die Klasse innerhalb derselben Kompilierungseinheit. Aber wir möchten diese Klasse aus XAML-Markup instanziieren können, und aus diesem Grund wird es eine Laufzeitklasse sein. Und wir werden C++/WinRT verwenden, um sie zu erstellen und zu nutzen.

Der erste Schritt beim Erstellen einer neuen Laufzeitklasse besteht darin, dem Projekt ein neues Midl File -Element (IDL) hinzuzufügen. Nennen Sie es BgLabelControl.idl. Löschen Sie den Standardinhalt von BgLabelControl.idl, und fügen Sie diese Laufzeitklassendeklaration ein.

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

Die obige Auflistung zeigt das Muster, das Sie beim Deklarieren einer Abhängigkeitseigenschaft (DP) befolgen. Es gibt zwei Teile zu jedem DP. Zunächst deklarieren Sie eine schreibgeschützte statische Eigenschaft vom Typ DependencyProperty. Es hat den Namen Ihrer DP plus Eigenschaft. Sie verwenden diese statische Eigenschaft in Ihrer Implementierung. Zweitens deklarieren Sie eine Instanzeigenschaft mit Lese-/Schreibzugriff mit dem Typ und Namen Ihrer DP. Wenn Sie eine angefügte Eigenschaft (anstelle eines DP) erstellen möchten, sehen Sie sich die Codebeispiele in benutzerdefinierten angefügten Eigenschaften an.

Note

Wenn Sie einen DP mit einem Gleitkommatyp verwenden möchten, machen Sie ihn double (Double in MIDL 3.0). Das Deklarieren und Implementieren einer DP des Typs float (Single in MIDL) und das anschließende Festlegen eines Werts für diese DP im XAML-Markup führen zu dem Fehler „Aus dem Text „<ZAHL>“ konnte kein Wert vom Typ ‚Windows.Foundation.Single‘ erstellt werden.“.

Speichern Sie die Datei. Das Projekt lässt sich derzeit nicht vollständig erstellen, aber es jetzt zu erstellen ist dennoch sinnvoll, weil dadurch die Quelldateien generiert werden, in denen Sie die BgLabelControl-Laufzeitklasse implementieren. Fahren Sie also fort und führen Sie jetzt den Build aus (die Buildfehler, die Sie in dieser Phase voraussichtlich sehen werden, hängen mit einem „nicht aufgelösten externen Symbol“ zusammen).

Während des Buildvorgangs wird das midl.exe Tool ausgeführt, um eine Windows-Runtime Metadatendatei (\BgLabelControlApp\Debug\BgLabelControlApp\Unmerged\BgLabelControl.winmd) zu erstellen, die die Laufzeitklasse beschreibt. Anschließend wird das cppwinrt.exe Tool ausgeführt, um Quellcodedateien zu generieren, die Sie beim Erstellen und Verwenden ihrer Laufzeitklasse unterstützen. Diese Dateien enthalten Gerüstcode, damit Sie mit der Implementierung der in Ihrer IDL deklarierten BgLabelControl-Laufzeitklasse beginnen können. Diese Stubs sind \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\BgLabelControl.h und BgLabelControl.cpp.

Kopieren Sie die Stubdateien BgLabelControl.h und BgLabelControl.cpp aus \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ in den Projektordner, also \BgLabelControlApp\BgLabelControlApp\. Stellen Sie in Projektmappen-Explorer sicher, dass "Alle Dateien anzeigen" aktiviert ist. Klicken Sie mit der rechten Maustaste auf die kopierten Stubdateien, und klicken Sie auf Include In Project.

Sie sehen oben in BgLabelControl.h und BgLabelControl.cpp ein static_assert, das Sie entfernen müssen. Jetzt wird das Projekt kompiliert.

Implementieren der benutzerdefinierten BgLabelControl-Steuerelementklasse

Öffnen wir nun \BgLabelControlApp\BgLabelControlApp\BgLabelControl.h und BgLabelControl.cpp und implementieren wir unsere Laufzeitklasse. Ändern Sie in BgLabelControl.h den Konstruktor so, dass der Standard-Styleschlüssel festgelegt wird, implementieren Sie Label und LabelProperty, fügen Sie einen statischen Ereignishandler namens OnLabelChanged hinzu, um Änderungen am Wert der Abhängigkeitseigenschaft zu verarbeiten, und fügen Sie ein privates Mitglied hinzu, um das unterstützende Feld für LabelProperty zu speichern.

Nachdem Sie diese hinzugefügt haben, sieht Ihr BgLabelControl.h wie folgt aus. Sie können dieses Codelisting 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 Microsoft::UI::Xaml::DependencyProperty LabelProperty() { return m_labelProperty; }

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

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

BgLabelControl.cppDefinieren Sie in diesem Fall die statischen Elemente wie folgt. Sie können dieses Codelisting 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
{
    Microsoft::UI::Xaml::DependencyProperty BgLabelControl::m_labelProperty =
        Microsoft::UI::Xaml::DependencyProperty::Register(
            L"Label",
            winrt::xaml_typename<winrt::hstring>(),
            winrt::xaml_typename<BgLabelControlApp::BgLabelControl>(),
            Microsoft::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Microsoft::UI::Xaml::PropertyChangedCallback{ &BgLabelControl::OnLabelChanged } }
    );

    void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::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 Schritt-für-Schritt-Anleitung werden wir OnLabelChanged nicht verwenden. Es ist jedoch nur dazu da, damit Sie sehen können, wie Sie eine Abhängigkeitseigenschaft mit einem Rückruf bei Eigenschaftenänderung registrieren. Die Implementierung von OnLabelChanged zeigt auch, wie ein abgeleiteter projizierter Typ aus einem basisprojektierten Typ abgerufen wird (in diesem Fall ist der projizierte Basistyp DependencyObject). Außerdem wird gezeigt, wie Sie dann einen Zeiger auf den Typ erhalten, der den projizierten Typ implementiert. Dieser zweite Vorgang ist natürlich nur in dem Projekt möglich, das den projizierten Typ implementiert (d. h. das Projekt, das die Laufzeitklasse implementiert).

Note

Wenn Sie nicht die Windows SDK-Version 10.0.17763.0 (Windows 10, Version 1809) oder eine höhere Version installiert haben, müssen Sie im obigen Ereignishandler für Änderungen der Abhängigkeitseigenschaft winrt::from_abi anstelle von winrt::get_self aufrufen.

Entwerfen des Standardstils für BgLabelControl

Im Konstruktor legt BgLabelControl einen Standardstilschlüssel für sich selbst fest. Aber was ist ein Standardstil? Ein benutzerdefiniertes (vorlagenbasiertes) Steuerelement muss über einen Standardstil verfügen, der eine Standardsteuerelementvorlage enthält und den es zur Darstellung verwenden kann, falls der Verwender des Steuerelements keinen Stil und/oder keine Vorlage festlegt. In diesem Abschnitt fügen wir dem Projekt eine Markup-Datei hinzu, die unsere Standardformatvorlage enthält.

Stellen Sie sicher, dass Alle Dateien anzeigen weiterhin aktiviert ist (in Projektmappen-Explorer). Erstellen Sie unter dem Projektknoten einen neuen Ordner (nicht einen Filter, sondern einen Ordner) und nennen Sie ihn "Themen". Fügen Sie unter Themes"Generic.xaml" ein neues Element vom Typ Visual C++>XAML-XAML-Ansicht> hinzu, und nennen Sie es "Generic.xaml". Die Ordner- und Dateinamen müssen wie folgt aussehen, damit das XAML-Framework den Standardstil für ein benutzerdefiniertes Steuerelement finden kann. Löschen Sie den Standardinhalt von Generic.xaml, und fügen Sie es in 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 ist die einzige Eigenschaft, die vom Standardstil festgelegt wird, die Steuerelementvorlage. Die Vorlage besteht aus einem Quadrat (dessen Hintergrund an die Background-Eigenschaft gebunden ist, die alle Instanzen des XAML-Steuerelementtyps aufweisen) und ein Textelement (dessen Text an die Abhängigkeitseigenschaft BgLabelControl::Label gebunden ist).

Hinzufügen einer Instanz von BgLabelControl zur Haupt-UI-Seite

Ö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ügen Sie außerdem die folgende Include-Direktive zu MainPage.h hinzu, damit der Typ MainPage (eine Kombination aus kompiliertem XAML-Markup und imperativem Code) den benutzerdefinierten Steuerelementtyp BgLabelControl erkennen kann. Wenn Sie BgLabelControl von einer anderen XAML-Seite verwenden möchten, fügen Sie dieselbe Include-Anweisung auch der Header-Datei dieser Seite hinzu. Alternativ können Sie auch einfach eine einzelne Include-Direktive in die vorkompilierte Headerdatei einfügen.

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

Erstellen Sie nun das Projekt und führen Sie es aus. Sie werden feststellen, dass die Standardvorlage für Steuerelemente an den Hintergrundpinsel und das Etikett der Instanz BgLabelControl im Markup gebunden ist.

Diese exemplarische Vorgehensweise zeigte ein einfaches Beispiel für ein benutzerdefiniertes (vorlagenbasiertes) Steuerelement in C++/WinRT. Sie können Ihre eigenen benutzerdefinierten Steuerelemente beliebig leistungsfähig und funktionsreich gestalten. Zum Beispiel kann ein benutzerdefiniertes Steuerelement die Form von etwas so Komplexem wie einem bearbeitbaren Datenraster, einem Videoplayer oder einem Visualisierer für 3D-Geometrie annehmen.

Implementieren überschreibbarer Methoden wie MeasureOverride und OnApplyTemplate

Weitere Informationen finden Sie im Abschnitt " Aufrufen und Überschreiben Ihres Basistyps mit C++/WinRT".

Wichtige APIs