Analyse der Fensterklasse und Implementierung einer Button-Klick-Aktion

Leser können Visual Studio 2022 auf Version 17.9.1 oder höher aktualisieren. Das mit der Standardvorlage erstellte WinUI3-C++-Projekt enthält bereits einen Button; beim Klick auf diesen lassen sich beliebige Hintergrundaktionen ausführen.

Wir erstellen ein neues Projekt

In der vorherigen Lektion haben wir alle Dateien des Projekts und deren jeweilige Aufgaben erläutert. In diesem Teil erstellen wir ein neues leeres Fenster, fügen einen Button auf das Fenster hinzu und behandeln die Klickaktion des Buttons.

Wie bereits gesagt, befindet sich unsere Einstiegsklasse bei App.

    void App::OnLaunched([[maybe_unused]] LaunchActivatedEventArgs const& e)
    {
        window = make<MainWindow>();
        window.Activate();
    }Code-Sprache: C++ (cpp)

An dieser Stelle wird das Hauptfenster instanziiert – es ist das Fenster, das beim Start des Programms angezeigt wird. Im Folgenden analysieren wir unser Hauptfenster: Ein Fenster besteht aus insgesamt vier Dateien.

Fenster

MainWindow.xaml (Schnittstellenbeschreibungsdatei)

Dies ist eine reine XML-Markup-Sprache, die ausschließlich zum Schreiben von UI-Layouts dient. Sie ist nur für die visuelle Darstellung der Oberfläche zuständig und enthält keine Geschäftslogik.

Sie können darin Layout-Steuerelemente sowie alle Arten von Fensterkomponenten wie Button, TextBox und weitere Elemente definieren. Diese Schnittstellendatei wird seit WPF verwendet und ist im Grunde eine XML-Datei. Sie muss mit gleichnamigen .h- und .cpp-Dateien kombiniert werden, um Hintergrundereignisse zu verarbeiten.

In dem offiziellen Standardbeispiel ist die XAML leer und enthält keine Steuerelemente.

<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="WinuiCppDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WinuiCppDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="WinuiCppDemo">

    <Grid>

    </Grid>
</Window>Code-Sprache: YAML (yaml)

MainWindow.idl

Schnittstellendefinitionsdatei, zentraler einzigartiger Bestandteil von C++/WinRT

IDL = Interface Definition Language (Schnittstellendefinitionssprache). Sie definiert die nach außen exponierten APIs, Eigenschaften und Ereignisse des Fensters und bildet die Brücke für die sprachübergreifende Interoperabilität von C++/WinRT.

  1. Legt die öffentlichen Member der Fensterklasse fest: benutzerdefinierte Eigenschaften, benutzerdefinierte Ereignisse und aufrufbare Methoden
  2. Der Compiler generiert automatisch WinRT-Standard-ABI-Schnittstellencode aus der IDL-Datei, sodass C++, C#, JS das Fenster aufrufen können
  3. Datenbindungen und benutzerdefinierte Steuerelemente in der XAML-Oberfläche funktionieren nur, wenn die benötigten Eigenschaften in der IDL deklariert werden

Warum ist eine .idl-Datei bei WinUI3 C++/WinRT zwingend erforderlich?

Denn WinRT ist ein sprachübergreifendes binäres ABI-Standardsystem

Windows Runtime (WinRT) ist keine bloße C++-Bibliothek, sondern eine universelle binäre Schnittstellenspezifikation für mehrere Programmiersprachen. C++, C#, Python, JS können dieselbe WinRT-Komponente aufrufen – dies funktioniert über eine einheitliche Schnittstellenbeschreibung, eine gegenseitige Kommunikation allein über C++-Headerdateien ist nicht möglich.

C++-Header werden nur vom C++-Compiler verstanden und sind für C#/JS unleserlich. IDL hingegen ist eine neutrale Schnittstellensprache, die der Compiler in für alle Sprachen verständlichen ABI-Bindungscode umwandelt.

Hier ist der Inhalt der IDL-Datei unseres Beispielprojekts

namespace WinuiCppDemo
{
    [default_interface]
    runtimeclass MainWindow : Microsoft.UI.Xaml.Window
    {
        MainWindow();
        Int32 MyProperty;
    }
}

Wie bereits erläutert ist IDL eine Beschreibungssprache. In dieser IDL-Datei steht zuerst der Namensraum namespace WinuiCppDemo, gefolgt vom Attribut [default_interface]

Dieses Attribut markiert die standardmäßige Hauptschnittstelle für die runtimeclass und ist ein unverzichtbarer Standard bei der Entwicklung von Fenstern oder benutzerdefinierten Steuerelementen mit C++/WinRT

Der MIDL-Compiler erstellt automatisch eine rein virtuelle Schnittstelle IMainWindow, die sämtliche Member der Klasse (Konstruktor, Eigenschaften, Methoden) enthält

[default_interface] legt fest, dass diese IMainWindow die nach außen sichtbare Haupt-ABI-Schnittstelle der Klasse darstellt

Die Zeile MainWindow(); innerhalb der geschweiften Klammern ist die Deklaration des parameterlosen Standardkonstruktors, der explizit angegeben werden muss. Die XAML-Engine ruft diesen Standardkonstruktor auf, wenn sie ein Fenster instanziiert

Dies entspricht der Implementierung des Konstruktors in der Datei .xaml.h.

Int32 MyProperty;

Deklariert eine WinRT-Abhängigkeitseigenschaft (bindbare Eigenschaft) – hier zeigt sich der zentrale Nutzen von IDL:

Int32: Standard-Basistyp von WinRT (entspricht dem C++-Typ int32_t) ;

MyProperty: Der Compiler generiert automatisch ein vollständiges Eigenschaftssystem:

  • Getter: int32_t MyProperty();
  • Setter: void MyProperty(int32_t value);
  • Eigenschaftsänderungsereignis: MyPropertyChanged (wird automatisch ausgelöst, wenn sich die Eigenschaft ändert; XAML x:Bind nutzt es zur automatischen Aktualisierung der Benutzeroberfläche)

Nur Member, die in der IDL definiert werden, können in XAML mit {x:Bind MyProperty} gebunden werden; wenn Sie nur eine normale Membervariable in der .h-Datei deklarieren, erkennt XAML diese nicht und die Bindung erzeugt einen Fehler.

Sie können die IDL-Datei per Rechtsklick separat kompilieren – das Tool midl.exe übernimmt die Verarbeitung. Es erzeugt sowohl Metadatendateien für C# als auch Schnittstellen-Headerdateien für C++.

Einfach ausgedrückt dient IDL der Generierung von Schnittstellen für sprachübergreifende Aufrufe und liefert gleichzeitig die Metadaten für XAML-Datenbindungen. Warum benötigt WinUI 3 in C# keine IDL-Datei? Weil C# über eingebaute Reflexion und Metadaten verfügt und eine verwaltete Sprache ist. Die C++-Version von WinRT hat keine nativen Mechanismen für Reflexion und Metadaten.

Wenn Sie eine Membervariable nur in der .h-Datei deklarieren, sieht die XAML-Engine diese überhaupt nicht;

Nur wenn Sie in der IDL Int32 MyProperty; deklarieren, generiert der Compiler folgende Inhalte:

  1. Eigenschafts-Get/Set-Schnittstellen
  2. Ereignis zur Benachrichtigung bei Eigenschaftsänderungen MyPropertyChanged
  3. Metadaten, die die statische Bindungsprüfung in XAML ermöglichen

Auch wenn Ihr Projekt ausschließlich mit C++ arbeitet und keine Interaktion mit C#/JS oder anderen Sprachen stattfindet, ist IDL zwingend erforderlich, sobald Sie XAML verwenden

WinRT wurde mit dem Ziel einer einheitlichen ABI entwickelt, damit C++ / C# / JS / Python gegenseitig Komponenten aufrufen können:

IDL ist eine neutrale Beschreibungssprache, die nicht an eine bestimmte Programmiersprache gebunden ist. Die Compiler jeder Sprache lesen die aus IDL generierten .winmd-Metadatendateien, um Ihre Klassen, Eigenschaften und Methoden zu erkennen;

Klicken Sie mit der rechten Maustaste auf die IDL-Datei, um sie zu kompilieren

Die Kernaufgabe von IDL ist die Bereitstellung von Metadaten für XAML zur Erkennung von UI-Bindungen, ergänzt um die sprachübergreifende Interoperabilität.

Bei der Kompilierung der IDL werden Dateien vom Typ xxxx.g.h und xxxx.g.cpp erstellt, die im Ordner Generated Files im Projektverzeichnis abgelegt werden

Generated Files ist das Verzeichnis für automatisch generierten Quellcode

  1. Alle Dateien, die midl.exe, cppwinrt.exe und der XAML-Compiler beim Projektbuild automatisch erzeugen.
  2. Ändern Sie keine Dateien in diesem Ordner manuell – sie werden bei jedem neuen Build vollständig überschrieben
  3. Die VS-Projektkonfiguration fügt dieses Verzeichnis standardmäßig zum Header-Include-Pfad hinzu, damit #include „MainWindow.g.h“ die Datei finden kann

MainWindow.xaml.h

Standard-C++-Headerdatei, die die Deklarationen der Klasse MainWindow enthält.

Sie bindet automatisch die aus IDL generierten Basisschnittstellen ein;

Klassenfelder, private Variablen und Vorwärtsdeklarationen werden hier geschrieben – genau wie bei normalen .h-Dateien der C++-Syntax.

Sie listet alle vorhandenen Variablen und Funktionen auf, ohne deren Implementierung zu schreiben; andere Klassen können sie per include-Anweisung einbinden.

// Header-Schutz: Verhindert mehrfaches Einbinden der Datei und vermeidet Kompilierfehler durch mehrfache Definitionen
#pragma once

// Einbindung der automatisch durch die Kompilierung von MainWindow.idl generierten Bindungs-Headerdatei
// .g.h enthält die aus IDL erzeugte IMainWindow-Schnittstelle, die CRTP-Template-Basisklasse MainWindowT<> sowie Definitionen zu WinRT-Metadaten
// Alle in der IDL deklarierten MyProperty-Eigenschaften und Konstruktorschnittstellen sind hier definiert
#include "MainWindow.g.h"

// 【Implementierungs-Namensraum】Enthält die Implementierungsklasse für die Fenster-Geschäftslogik, hier werden eigene Eigenschaften und Funktionen umgesetzt
namespace winrt::WinuiCppDemo::implementation
{
    // MainWindowT<MainWindow> ist die vom .g.h bereitgestellte CRTP-Template-Basisklasse
    // 1. Erbt automatisch die durch IDL definierte IMainWindow-Schnittstelle und erzwingt die Implementierung aller in der IDL deklarierten Member
    // 2. Kapselt die unterlagerte WinUI-Fensterlogik, Initialisierung von XAML-Komponenten und die Weiterleitung von ABI-Schnittstellen
    struct MainWindow : MainWindowT<MainWindow>
    {
        // Standardkonstruktor des Fensters, entspricht der Deklaration MainWindow(); in der IDL
        MainWindow()
        {
            // Offizielle Regel: Innerhalb des Konstruktors darf InitializeComponent() nicht aufgerufen werden
            // Die Initialisierung von XAML-Steuerelementen wird vom Framework verzögert ausgeführt; ein Aufruf im Konstruktor führt zu Lebenszyklus-Abstürzen
            // Siehe offizielle cppwinrt-Dokumentation von Microsoft
            // See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent
        }

        // Automatisch generierte Leseschnittstelle für die in IDL deklarierte Eigenschaft Int32 MyProperty;
        // Gibt den int32_t-Wert der Eigenschaft zurück
        int32_t MyProperty();
        // Automatisch generierte Schreibschnittstelle für die in IDL deklarierte Eigenschaft Int32 MyProperty;
        // Ändert den Eigenschaftswert und löst intern automatisch das MyPropertyChanged-Änderungsereignis aus, damit x:Bind die UI aktualisiert
        void MyProperty(int32_t value);
    };
}

// 【Factory-Namensraum】WinRT-Objektfabrikklasse, die das MainWindow-Fensterobjekt nach außen exponiert und instanziiert
namespace winrt::WinuiCppDemo::factory_implementation
{
    // Der zweite Parameter von MainWindowT bindet die obige Implementierungsklasse implementation::MainWindow
    // Aufgabe: Bereitstellen eines einheitlichen ABI-Erstellungseinstiegs, damit die XAML-Engine und externer Code (C# / andere Komponenten) Fensterinstanzen erstellen können
    struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
    {
    };
}Code-Sprache: C++ (cpp)

MainWindow.xaml.cpp

C++-Implementierungsdatei, in der der gesamte Geschäftslogik-Code geschrieben wird.

  • Implementierung aller in MainWindow.xaml.h deklarierten Funktionen
  • Verarbeitung von Steuerelementereignissen (Buttonklick, Fensterladung, Eingabeverarbeitung)
  • Datenverarbeitung, Netzwerkzugriff, Dateiverwaltung, Geschäftsrechnungen
  • Interaktionslogik: Bedienen von UI-Steuerelementen, Ändern von bindbaren Eigenschaften, Anzeigen von Dialogfenstern, Seitenwechsel
// Einbindung der vorkompilierten Headerdatei, bündelt gemeinsame Projektheader und WinRT-Basis-Projektionen zur Beschleunigung der Kompilierung
#include "pch.h"
// Einbindung der Headerdatei der aktuellen Fensterklasse, enthält die Deklaration der MainWindow-Implementierungsklasse
#include "MainWindow.xaml.h"

// Bedingte Kompilierung: Prüft, ob die durch IDL automatisch generierte unterlagerte Bindungsdatei MainWindow.g.cpp existiert
// .g.cpp wird von den Tools midl+cppwinrt anhand der .idl erstellt und kapselt Eigenschaftsänderungsereignisse sowie unterlagerte ABI-Weiterleitungslogik
// Bei Vorhandensein wird sie eingebunden, damit Eigenschaftsänderungsbenachrichtigungen (MyPropertyChanged) und XAML-Laufzeitbindungen korrekt funktionieren
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif

// Vereinfachung globaler Namensräume, der Präfix winrt:: muss nicht wiederholt im gesamten Code geschrieben werden
using namespace winrt;
// Namensraum der zentralen WinUI-Xaml-APIs für Steuerelemente, Fenster und Routing-Ereignisse
using namespace Microsoft::UI::Xaml;

// Offizielle Anmerkung: Dokumentationslink zur WinUI-Projektstruktur und Vorlagen
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

// implementation-Namensraum: Enthält die Umsetzung der Geschäftslogik des MainWindow-Fensters, passt zu den Klassendeklarationen im Header
namespace winrt::WinuiCppDemo::implementation
{
    // Implementierung der Lesemethode für die in IDL deklarierte Eigenschaft MyProperty
    // Gibt den Int32-Eigenschaftswert zurück; XAML x:Bind ruft diese Funktion zum Auslesen von Daten auf
    int32_t MainWindow::MyProperty()
    {
        // Das Standardtemplate wirft eine Ausnahme für nicht implementierte Funktionen, ersetzen Sie diesen Code durch die Logik zur Rückgabe Ihrer echten Speichervariable
        throw hresult_not_implemented();
    }

    // Implementierung der Schreibmethode für die in IDL deklarierte Eigenschaft MyProperty
    // value: Der neue Wert, der der Eigenschaft zugewiesen wird; nach der Änderung löst die Unterlage automatisch das MyPropertyChanged-Ereignis aus, um gebundene UI-Elemente zu aktualisieren
    void MainWindow::MyProperty(int32_t /* value */)
    {
        // Das Standardtemplate wirft eine Ausnahme für nicht implementierte Funktionen, ergänzen Sie selbst die Logik zur Variablenzuweisung
        throw hresult_not_implemented();
    }
}Code-Sprache: PHP (php)

Im Folgenden fügen wir einen Button in dieses Hauptfenster ein. Beim Klick auf den Button soll die Eigenschaft MyProperty automatisch um 1 erhöht werden, anschließend öffnet sich ein Dialogfenster mit dem aktuellen Wert von MyProperty.

Zuerst fügen wir einen Button in die XAML ein

    <Grid>
        <Button Content="Click Me" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"/>
    </Grid>Code-Sprache: C++ (cpp)

Der auf dem Button angezeigte Text lautet „Click Me“, zudem ist das Klickereignis OnButtonClick gebunden.

Platzieren Sie den Mauszeiger auf OnButtonClick und drücken Sie F12: Visual Studio erstellt automatisch die Ausführungsfunktion für dieses Ereignis.

Daraufhin sehen Sie Ergänzungen in MainWindow.xaml.h und MainWindow.xaml.cpp

void OnButtonClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);Code-Sprache: C++ (cpp)

Eine neue Funktionsdeklaration wurde hinzugefügt.

Die Implementierung dieser Funktion befindet sich in MainWindow.xaml.cpp

Anschließend bearbeiten wir den Code in der cpp-Datei

Das war es für diese Lektion. In der nächsten Lektion implementieren wir die Funktionalität des Buttonklicks und lernen die Funktionsweise von Eigenschaftsbindungen kennen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert