Freigeben über


Erstellen von XAML-Steuerelementen mit C++/WinRT

Dieser Artikel führt Sie durch das Erstellen eines vorlagenbasierten XAML-Steuerelements für WinUI 3 mit C++/WinRT. Vorlagensteuerelemente erben von Microsoft.UI.Xaml.Controls.Control und verfügen über visuelle Struktur und visuelles Verhalten, das mithilfe von XAML-Steuerelementvorlagen angepasst werden kann. In diesem Artikel wird dasselbe Szenario wie im Artikel benutzerdefinierte XAML-Steuerelemente (mit Vorlagen) mit C++/WinRT beschrieben, und es wurde an die Nutzung von WinUI 3 angepasst.

Voraussetzungen

  1. Starten der Entwicklung von Windows-Apps
  2. Laden Sie die neueste Version der C++/WinRT Visual Studio-Erweiterung (VSIX) herunter, und installieren Sie sie.

Eine leere App erstellen (BgLabelControlApp)

Beginnen Sie mit dem Erstellen eines neuen Projekts in Microsoft Visual Studio. Wählen Sie im Dialogfeld Create a new project die Leere App (WinUI in UWP) Projektvorlage aus, und stellen Sie sicher, dass Sie die C++-Sprachversion auswählen. Legen Sie den Projektnamen auf "BgLabelControlApp" fest, damit die Dateinamen mit dem Code in den folgenden Beispielen übereinstimmen. Legen Sie die Zielversion auf Windows 10, Version 1903 (Build 18362) und Mindestversion auf Windows 10, Version 1803 (Build 17134) fest. Diese Schritt-für-Schritt-Anleitung funktioniert auch für Desktop-Apps, die mit der Blank App, verpackt (WinUI in Desktop) Projektvorlage erstellt wurden. Stellen Sie sicher, dass Sie alle Schritte im BgLabelControlApp (Desktop) Projekt ausführen.

Leere App-Projektvorlage

Hinzufügen eines vorlagenbasierten Steuerelements zu Ihrer App

Um ein vorlagenbasiertes Steuerelement hinzuzufügen, klicken Sie in der Symbolleiste auf das Menü Project, oder klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie Neues Element hinzufügen aus. Wählen Sie unter Visual C++->WinUI- die Vorlage für benutzerdefiniertes Steuerelement (WinUI) aus. Benennen Sie das neue Steuerelement "BgLabelControl", und klicken Sie auf Hinzufügen. Dadurch werden Ihrem Projekt drei neue Dateien hinzugefügt. BgLabelControl.h ist der Header mit den Steuerelementdeklarationen und BgLabelControl.cpp enthält die C++/WinRT-Implementierung des Steuerelements. BgLabelControl.idl ist die Schnittstellendefinitionsdatei, mit der das Steuerelement als Laufzeitklasse instanziiert werden kann.

Implementieren Sie die benutzerdefinierte BgLabelControl-Steuerelementklasse

In den folgenden Schritten aktualisieren Sie den Code in den Dateien BgLabelControl.idl, BgLabelControl.hund BgLabelControl.cpp im Projektverzeichnis, um die Laufzeitklasse zu implementieren.

Die vorlagenbasierte Steuerelementklasse wird aus XAML-Markup instanziiert, und aus diesem Grund wird sie eine Laufzeitklasse sein. Wenn Sie das fertige Projekt erstellen, verwendet der MIDL-Compiler (midl.exe) die BgLabelControl.idl Datei, um die Windows-Runtime-Metadatendatei (WINMD) für Ihr Steuerelement zu generieren, auf das Verbraucher Ihrer Komponente verweisen. Weitere Informationen zum Erstellen von Laufzeitklassen finden Sie unter Author APIs with C++/WinRT.

Das von uns erstellten Vorlagensteuerelement macht eine einzelne Eigenschaft verfügbar, bei der es sich um eine Zeichenfolge handelt, die als Bezeichnung für das Steuerelement verwendet wird. Ersetzen Sie den Inhalt von BgLabelControl.idl durch den folgenden Code.

// 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 Property. 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 benutzerdefinierte angefügten Eigenschaftenan.

Beachten Sie, dass sich die XAML-Klassen, auf die im obigen Code verwiesen wird, in Microsoft.UI.Xaml-Namespaces befinden. Dies unterscheidet sie als WinUI-Steuerelemente im Gegensatz zu UWP-XAML-Steuerelementen, die in Windows.UI.XAML-Namespaces definiert sind.

Ersetzen Sie den Inhalt von BgLabelControl.h durch den folgenden Code.

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

Der oben gezeigte Code implementiert die Eigenschaften Label und LabelProperty, fügt einen statischen Ereignishandler mit dem Namen OnLabelChanged hinzu, um Änderungen am Wert der Abhängigkeitseigenschaft zu verarbeiten, und fügt ein privates Mitglied hinzu, um das Sicherungsfeld für LabelProperty zu speichern. Beachten Sie auch hier, dass sich die XAML-Klassen, auf die in der Headerdatei verwiesen wird, in den Microsoft.UI.Xaml-Namespaces befinden, die zum WinUI 3-Framework gehören, anstelle der vom UWP-Ui-Framework verwendeten Windows.UI.Xaml-Namespaces.

Ersetzen Sie als Nächstes den Inhalt von BgLabelControl.cpp durch den folgenden Code.

// BgLabelControl.cpp
#include "pch.h"
#include "BgLabelControl.h"
#if __has_include("BgLabelControl.g.cpp")
#include "BgLabelControl.g.cpp"
#endif

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 exemplarischen Vorgehensweise wird der OnLabelChanged Rückruf nicht verwendet, er ist jedoch enthalten, damit Sie nachvollziehen können, wie eine Abhängigkeitseigenschaft mit einem Rückruf bei geänderten Eigenschaften registriert wird. Die Implementierung von OnLabelChanged zeigt auch, wie ein abgeleiteter projizierter Typ aus einem basisprojektierten Typ abgerufen wird (in diesem Fall ist der basisprojektierte Typ 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 im Projekt möglich, das den projizierten Typ implementiert (d. a. das Projekt, das die Laufzeitklasse implementiert).

Die xaml_typename-Funktion wird vom Windows.UI.Xaml.Interop-Namespace bereitgestellt, der nicht standardmäßig in der WinUI 3-Projektvorlage enthalten ist. Fügen Sie der vorkompilierten Headerdatei für Ihr Projekt eine Zeile hinzu, pch.hum die diesem Namespace zugeordnete Headerdatei einzuschließen.

// pch.h
...
#include <winrt/Windows.UI.Xaml.Interop.h>
...

Standardstil für BgLabelControl definieren

Im Konstruktor legt BgLabelControl einen Standardstilschlüssel für sich selbst fest. Ein vorlagenbasiertes Steuerelement muss über einen Standardstil verfügen, der eine Standardsteuerelementvorlage enthält, mit der es sich selbst rendern kann, falls der Benutzer 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 (im Projektmappen-Explorer). Erstellen Sie unter Ihrem Projektknoten einen neuen Ordner (nicht einen Filter, sondern einen Ordner), und nennen Sie ihn "Themen". Fügen Sie unter Themes" ein neues Element vom Typ Visual C++ > WinUI > Resource Dictionary (WinUI) 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 vorlagenbasiertes Steuerelement finden kann. Löschen Sie den Standardinhalt von Generic.xaml, und fügen Sie 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 Eigenschaft Background gebunden ist, die alle Instanzen des XAML-Control Typs aufweisen) und einem Textelement (dessen Text an die Abhängigkeitseigenschaft BgLabelControl::Label gebunden ist).

Fügen Sie eine Instanz von BgLabelControl zur Hauptbenutzeroberfläche hinzu.

Öffnen Sie MainWindow.xaml, die das XAML-Markup für unsere Haupt-UI-Seite enthält. Fügen Sie unmittelbar nach dem -Button--Element (innerhalb des -StackPanel-) das folgende Markup hinzu.

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

Fügen Sie außerdem die folgende Include-Direktive zu MainWindow.h hinzu, damit der Typ MainWindow (eine Kombination aus kompilierendem XAML-Markup und imperativem Code) den BgLabelControl-Steuerelementtyp kennt. 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.

//MainWindow.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.

Ergebnis des vorlagenbasierten Steuerelements

Implementierung von überschreibbaren Funktionen wie MeasureOverride und OnApplyTemplate

Sie leiten ein vorlagenbasiertes Steuerelement von der Control Laufzeitklasse ab, die wiederum von Basislaufzeitklassen abgeleitet wird. Außerdem gibt es überschreibbare Methoden von Control, FrameworkElement-und UIElement-, die Sie in der abgeleiteten Klasse überschreiben können. Hier ist ein Codebeispiel, das zeigt, wie man das tun kann.

// Control overrides.
void OnPointerPressed(Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /* e */) const { ... };

// FrameworkElement overrides.
Windows::Foundation::Size MeasureOverride(Windows::Foundation::Size const& /* availableSize */) const { ... };
void OnApplyTemplate() const { ... };

// UIElement overrides.
Microsoft::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer() const { ... };

Überschreibbare Funktionen stellen sich in verschiedenen Sprachprojektionen anders dar. In C# werden überschreibbare Funktionen in der Regel als geschützte virtuelle Funktionen deklariert. In C++/WinRT sind sie weder virtuell noch geschützt, aber Sie können sie weiterhin überschreiben und Ihre eigene Implementierung bereitstellen, wie oben gezeigt.

Generieren der Steuerungsquellendateien ohne Verwendung einer Vorlage.

In diesem Abschnitt wird gezeigt, wie Sie die erforderlichen Quelldateien zum Erstellen Ihres benutzerdefinierten Steuerelements generieren können, ohne die Vorlage für benutzerdefinierte Steuerelemente zu verwenden.

Fügen Sie zuerst dem Projekt ein neues MIDL-Datei-Element (.idl) hinzu. Wählen Sie im Menü ProjectNeues Element hinzufügen... aus, und geben Sie "MIDL" in das Suchfeld ein, um das .idl-Dateielement zu finden. Benennen Sie die neue Datei BgLabelControl.idl so, dass der Name mit den Schritten in diesem Artikel konsistent ist. Löschen Sie den Standardinhalt von BgLabelControl.idl, und fügen Sie die in den obigen Schritten gezeigte Laufzeitklassendeklaration ein.

Nach dem Speichern der neuen IDL-Datei besteht der nächste Schritt darin, die Windows-Runtime-Metadatendatei (.winmd) und Stubs für die Implementierungsdateien .cpp und .h zu generieren, die Sie zum Implementieren des vorlagenbasierten Steuerelements verwenden. Generieren Sie diese Dateien, indem Sie die Lösung erstellen, sodass der MIDL-Compiler (midl.exe) dazu veranlasst wird, die von Ihnen erstellte IDL-Datei zu kompilieren. Beachten Sie, dass die Projektmappe nicht erfolgreich erstellt wird und Visual Studio Buildfehler im Ausgabefenster anzeigt, aber die erforderlichen Dateien werden generiert.

Kopieren Sie die Stubdateien BgLabelControl.h und BgLabelControl.cpp aus \BgLabelControlApp\BgLabelControlApp\Generated Files\sources\ in den Projektordner. Stellen Sie im Solution Explorersicher, dass "Alle Dateien anzeigen" aktiviert ist. Klicken Sie mit der rechten Maustaste auf die kopierten Stubdateien, und klicken Sie auf In Projekt einfügen.

Der Compiler platziert eine static_assert Zeile oben in BgLabelControl.h und BgLabelControl.cpp, um zu verhindern, dass die generierten Dateien kompiliert werden. Wenn Sie Ihr Steuerelement implementieren, sollten Sie diese Zeilen aus den Dateien entfernen, die Sie in Ihrem Projektverzeichnis abgelegt haben. Für diese exemplarische Vorgehensweise können Sie einfach den gesamten Inhalt der Dateien mit dem oben angegebenen Code überschreiben.

Siehe auch