Hosten eines WinRT-XAML-Standardsteuerelements in einer C++-Desktop (Win32)-App

Wichtig

In diesem Thema werden Typen aus dem GitHub-Repository CommunityToolkit/Microsoft.Toolkit.Win32 verwendet oder erwähnt. Wichtige Informationen zur Unterstützung von XAML Islands finden Sie in diesem Repository im Hinweis zu XAML Islands.

Dieser Artikel veranschaulicht, wie mithilfe der WinRT-XAML-Hosting-API ein WinRT-XAML-Standardsteuerelement (d. h., ein vom Windows SDK bereitgestelltes Steuerelement) in einer neuen C++-Desktop-App verwendet wird. Der Code basiert auf dem einfachen XAML Islands-Beispiel, und in diesem Abschnitt werden einige der wichtigen Teile des Codes besprochen. Wenn Sie über ein vorhandenes C++-Desktop-App-Projekt verfügen, können Sie diese Schritte und Codebeispiele für Ihr Projekt anpassen.

Hinweis

Das in diesem Artikel veranschaulichte Szenario bietet keine direkte Unterstützung für die Bearbeitung von XAML-Markup für WinRT-XAML-Steuerelemente, die in deiner App gehostet werden. In diesem Szenario bist du darauf beschränkt, das Aussehen und Verhalten der gehosteten Steuerelemente über den Code zu ändern. Informationen dazu, wie XAML-Markup beim Hosten von WinRT-XAML-Steuerelementen direkt bearbeitet werden kann, findest du unter Hosten eines benutzerdefinierten WinRT-XAML-Steuerelements in einer C++-Desktop-App.

Erstellen eines Desktopanwendungsprojekts

  1. Erstellen Sie in Visual Studio 2019 mit dem SDK für Windows 10, Version 1903 (Build 10.0.18362) oder höher ein neues Projekt vom Typ Windows-Desktopanwendung, und geben Sie ihm den Namen MyDesktopWin32App. Dieser Projekttyp steht unter den Projektfiltern C++ , Windows und Desktop zur Verfügung.

  2. Klicke im Projektmappen-Explorer mit der rechten Maustaste auf den Projektmappenknoten, klicke auf Projektmappe neu zuweisen, wähle 10.0.18362.0 oder eine höhere SDK-Version aus, und klicke dann auf OK.

  3. Installiere das NuGet-Paket Microsoft.Windows.CppWinRT, um die Unterstützung für C++/WinRT in deinem Projekt zu aktivieren:

    1. Klicke im Projektmappen-Explorer mit der rechten Maustaste auf dein Projekt, und wähle NuGet-Pakete verwalten aus.
    2. Wähle die Registerkarte Durchsuchen aus, suche nach dem Paket Microsoft.Windows.CppWinRT, und installiere die neueste Version dieses Pakets.

    Hinweis

    Für neue Projekte kannst du alternativ die C++/WinRT-Visual Studio-Erweiterung (VSIX) installieren und eine der in dieser Erweiterung enthaltenen C++/WinRT-Projektvorlagen verwenden. Weitere Informationen finden Sie unter Visual Studio-Support für C++/WinRT und VSIX.

  4. Wählen Sie die Registerkarte Durchsuchen im Fenster NuGet-Paket-Manager aus, suchen Sie nach dem NuGet-Paket Microsoft.Toolkit.Win32.UI.SDK, und installieren Sie die neueste stabile Version dieses Pakets. Dieses Paket stellt verschiedene Ressourcen für Kompilier- und Laufzeit bereit, um XAML Islands in deiner App zu aktivieren.

  5. Legen Sie den maxversiontested-Wert in Ihrem Anwendungsmanifest fest, um anzugeben, dass Ihre Anwendung mit Windows 10, Version 1903 kompatibel ist.

    1. Falls noch kein Anwendungsmanifest in deinem Projekt enthalten ist, füge deinem Projekt eine neue XML-Datei hinzu, und gibt ihr den Namen app.manifest.

    2. Füge das compatibility-Element und die untergeordneten Elemente wie im folgenden Beispiel gezeigt in dein Anwendungsmanifest ein. Ersetzen Sie das Id-Attribut des maxversiontested-Elements durch die Versionsnummer von Windows, die Sie als Zielplattform verwenden möchten (hierbei muss es sich um 10.0.18362.0 oder ein höheres Release handeln). Beachten Sie, dass das Festlegen eines höheren Werts bedeutet, dass die App unter älteren Versionen von Windows nicht ordnungsgemäß ausgeführt wird, da jedes Windows-Release nur die zurückliegenden Versionen kennt. Wenn die App unter Windows 10, Version 1903 (Build 10.0.18362), ausgeführt werden soll, sollten Sie entweder den Wert „10.0.18362.0“ unverändert lassen oder mehrere maxversiontested-Elemente für die verschiedenen Werte hinzufügen, die die App unterstützt.

      <?xml version="1.0" encoding="UTF-8"?>
      <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
          <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
              <application>
                  <!-- Windows 10 -->
                  <maxversiontested Id="10.0.18362.0"/>
                  <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
              </application>
          </compatibility>
      </assembly>
      
  6. Fügen Sie einen Verweis auf die Windows-Runtime-Metadaten hinzu:

    1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Projektknoten Verweise, und wählen Sie dann Verweis hinzufügen aus.
    2. Klicken Sie auf die Schaltfläche Durchsuchen unten auf der Seite, und navigieren Sie zum Ordner „UnionMetadata“ in Ihrem SDK-Installationspfad. Standardmäßig wird das SDK unter C:\Program Files (x86)\Windows Kits\10\UnionMetadata installiert.
    3. Wählen Sie dann den Ordner aus, der nach der Windows-Version benannt ist, auf die Sie abzielen (z. B. 10.0.18362.0), und wählen Sie in diesem Ordner die Datei Windows.winmd aus.
    4. Klicken Sie auf OK, um das Dialogfeld Verweis hinzufügen zu schließen.

Verwenden der XAML-Hosting-API zum Hosten eines WinRT-XAML-Steuerelements

Das grundlegende Verfahren bei Verwendung der XAML-Hosting-API zum Hosten eines WinRT-XAML-Steuerelements umfasst diese allgemeinen Schritte:

  1. Initialisieren des WinRT-XAML-Frameworks für den aktuellen Thread, bevor deine App eines der Windows.UI.Xaml.UIElement-Objekte erstellt, die gehostet werden sollen. Je nachdem, wann du das DesktopWindowXamlSource-Objekt zum Hosten der Steuerelemente erstellst, gibt es hierfür mehrere Möglichkeiten.

    • Wenn deine Anwendung das DesktopWindowXamlSource-Objekt erstellt, bevor eines der zu hostenden Windows.UI.Xaml.UIElement-Objekte erstellt wird, wird dieses Framework beim Instanziieren des DesktopWindowXamlSource-Objekts für dich initialisiert. In diesem Szenario ist es nicht erforderlich, eigenen Code zum Initialisieren des Frameworks hinzuzufügen.

    • Wenn deine Anwendung die Windows.UI.Xaml.UIElement-Objekte jedoch erstellt, bevor das DesktopWindowXamlSource-Objekt zu deren Hosting erstellt wird, muss deine Anwendung vor dem Instanziieren der Windows.UI.Xaml.UIElement-Objekte die statische WindowsXamlManager.InitializeForCurrentThread-Methode aufrufen, um das WinRT-XAML-Framework explizit zu initialisieren. Deine Anwendung sollte diese Methode in der Regel aufrufen, wenn das übergeordnete Benutzeroberflächenelement instanziiert wird, das DesktopWindowXamlSource hostet.

    Hinweis

    Diese Methode gibt ein WindowsXamlManager-Objekt zurück, das einen Verweise auf das WinRT-XAML-Framework enthält. Du kannst für einen bestimmten Thread beliebig viele WindowsXamlManager-Objekte erstellen. Da jedoch jedes Objekt einen Verweis auf das WinRT-XAML-Framework umfasst, solltest du diese Objekte verwerfen, um sicherzustellen, dass die XAML-Ressourcen schließlich freigegeben werden.

  2. Erstelle ein DesktopWindowXamlSource-Objekt, und füge es an ein übergeordnetes Benutzeroberflächenelement in deiner Anwendung an, das einem Fensterhandle zugeordnet ist.

    Führe hierzu die folgenden Schritte aus:

    1. Erstelle ein DesktopWindowXamlSource-Objekt, und wandle es in die IDesktopWindowXamlSourceNative- oder IDesktopWindowXamlSourceNative2-COM-Schnittstelle um.

    2. Rufe die AttachToWindow-Methode der Schnittstelle IDesktopWindowXamlSourceNative oder IDesktopWindowXamlSourceNative2 auf, und übergebe das Fensterhandle des übergeordneten Benutzeroberflächenelements in deiner Anwendung.

      Wichtig

      Stellen Sie sicher, dass ihr Code die AttachToWindow-Methode nur einmal pro DesktopWindowXamlSource-Objekt aufruft. Wenn diese Methode für ein DesktopWindowXamlSource-Objekt mehr als einmal aufgerufen wird, kann dies zu einem Speicherverlust führen.

    3. Lege die anfängliche Größe des internen untergeordneten Fensters fest, das in DesktopWindowXamlSource enthalten ist. Standardmäßig ist dieses interne untergeordnete Fenster auf eine Breite und Höhe von 0 festgelegt. Wenn du die Größe des Fensters nicht festlegst, ist keines der WinRT-XAML-Steuerelemente sichtbar, die du DesktopWindowXamlSource hinzufügst. Um in DesktopWindowXamlSource auf das interne untergeordnete Fenster zuzugreifen, verwendest du die WindowHandle-Eigenschaft der Schnittstelle IDesktopWindowXamlSourceNative oder IDesktopWindowXamlSourceNative2.

  3. Schließlich weist du das zu hostende Windows.UI.Xaml.UIElement der Content-Eigenschaft deines DesktopWindowXamlSource-Objekts zu.

Die folgenden Schritte und Codebeispiele veranschaulichen, wie der oben genannte Prozess implementiert wird:

  1. Öffne im Ordner Quelldateien des Projekts die Standarddatei MyDesktopWin32App.cpp. Lösche den gesamten Inhalt der Datei, und füge die folgenden include- und using-Anweisungen ein. Zusätzlich zu standardmäßigen C++- und UWP-Headern und Namespaces umfassen diese Anweisungen spezifische XAML Islands-Elemente.

    #include <windows.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include <winrt/Windows.Foundation.Collections.h>
    #include <winrt/Windows.system.h>
    #include <winrt/windows.ui.xaml.hosting.h>
    #include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
    #include <winrt/windows.ui.xaml.controls.h>
    #include <winrt/Windows.ui.xaml.media.h>
    
    using namespace winrt;
    using namespace Windows::UI;
    using namespace Windows::UI::Composition;
    using namespace Windows::UI::Xaml::Hosting;
    using namespace Windows::Foundation::Numerics;
    
  2. Kopiere den folgenden Code nach dem vorherigen Abschnitt. Dieser Code definiert die WinMain-Funktion für die App. Sie initialisiert ein Basisfenster und verwendet die XAML-Hosting-API, um ein einfaches TextBlock-UWP-Steuerelement im Fenster zu hosten.

    LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
    
    HWND _hWnd;
    HWND _childhWnd;
    HINSTANCE _hInstance;
    
    int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)
    {
        _hInstance = hInstance;
    
        // The main window class name.
        const wchar_t szWindowClass[] = L"Win32DesktopApp";
        WNDCLASSEX windowClass = { };
    
        windowClass.cbSize = sizeof(WNDCLASSEX);
        windowClass.lpfnWndProc = WindowProc;
        windowClass.hInstance = hInstance;
        windowClass.lpszClassName = szWindowClass;
        windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    
        windowClass.hIconSm = LoadIcon(windowClass.hInstance, IDI_APPLICATION);
    
        if (RegisterClassEx(&windowClass) == NULL)
        {
            MessageBox(NULL, L"Windows registration failed!", L"Error", NULL);
            return 0;
        }
    
        _hWnd = CreateWindow(
            szWindowClass,
            L"Windows c++ Win32 Desktop App",
            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            NULL,
            NULL,
            hInstance,
            NULL
        );
        if (_hWnd == NULL)
        {
            MessageBox(NULL, L"Call to CreateWindow failed!", L"Error", NULL);
            return 0;
        }
    
    
        // Begin XAML Island section.
    
        // The call to winrt::init_apartment initializes COM; by default, in a multithreaded apartment.
        winrt::init_apartment(apartment_type::single_threaded);
    
        // Initialize the XAML framework's core window for the current thread.
        WindowsXamlManager winxamlmanager = WindowsXamlManager::InitializeForCurrentThread();
    
        // This DesktopWindowXamlSource is the object that enables a non-UWP desktop application 
        // to host WinRT XAML controls in any UI element that is associated with a window handle (HWND).
        DesktopWindowXamlSource desktopSource;
    
        // Get handle to the core window.
        auto interop = desktopSource.as<IDesktopWindowXamlSourceNative>();
    
        // Parent the DesktopWindowXamlSource object to the current window.
        check_hresult(interop->AttachToWindow(_hWnd));
    
        // This HWND will be the window handler for the XAML Island: A child window that contains XAML.  
        HWND hWndXamlIsland = nullptr;
    
        // Get the new child window's HWND. 
        interop->get_WindowHandle(&hWndXamlIsland);
    
        // Update the XAML Island window size because initially it is 0,0.
        SetWindowPos(hWndXamlIsland, 0, 200, 100, 800, 200, SWP_SHOWWINDOW);
    
        // Create the XAML content.
        Windows::UI::Xaml::Controls::StackPanel xamlContainer;
        xamlContainer.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
    
        Windows::UI::Xaml::Controls::TextBlock tb;
        tb.Text(L"Hello World from Xaml Islands!");
        tb.VerticalAlignment(Windows::UI::Xaml::VerticalAlignment::Center);
        tb.HorizontalAlignment(Windows::UI::Xaml::HorizontalAlignment::Center);
        tb.FontSize(48);
    
        xamlContainer.Children().Append(tb);
        xamlContainer.UpdateLayout();
        desktopSource.Content(xamlContainer);
    
        // End XAML Island section.
    
        ShowWindow(_hWnd, nCmdShow);
        UpdateWindow(_hWnd);
    
        //Message loop:
        MSG msg = { };
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
        return 0;
    }
    
  3. Kopiere den folgenden Code nach dem vorherigen Abschnitt. Dieser Code definiert die Fensterprozedur für das Fenster.

    LRESULT CALLBACK WindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam)
    {
        PAINTSTRUCT ps;
        HDC hdc;
        wchar_t greeting[] = L"Hello World in Win32!";
        RECT rcClient;
    
        switch (messageCode)
        {
            case WM_PAINT:
                if (hWnd == _hWnd)
                {
                    hdc = BeginPaint(hWnd, &ps);
                    TextOut(hdc, 300, 5, greeting, wcslen(greeting));
                    EndPaint(hWnd, &ps);
                }
                break;
            case WM_DESTROY:
                PostQuitMessage(0);
                break;
    
            // Create main window
            case WM_CREATE:
                _childhWnd = CreateWindowEx(0, L"ChildWClass", NULL, WS_CHILD | WS_BORDER, 0, 0, 0, 0, hWnd, NULL, _hInstance, NULL);
                return 0;
    
            // Main window changed size
            case WM_SIZE:
                // Get the dimensions of the main window's client
                // area, and enumerate the child windows. Pass the
                // dimensions to the child windows during enumeration.
                GetClientRect(hWnd, &rcClient);
                MoveWindow(_childhWnd, 200, 200, 400, 500, TRUE);
                ShowWindow(_childhWnd, SW_SHOW);
    
                return 0;
    
                // Process other messages.
    
            default:
                return DefWindowProc(hWnd, messageCode, wParam, lParam);
                break;
        }
    
        return 0;
    }
    
  4. Speichere die Codedatei, kompiliere die App, und führe sie aus. Vergewissere dich, dass das TextBlock-UWP-Steuerelement im App-Fenster angezeigt wird.

    Hinweis

    Möglicherweise werden verschiedene Kompilierungswarnungen angezeigt, z. B. warning C4002: too many arguments for function-like macro invocation 'GetCurrentTime' und manifest authoring warning 81010002: Unrecognized Element "maxversiontested" in namespace "urn:schemas-microsoft-com:compatibility.v1". Diese Warnungen sind bekannte Probleme mit den aktuellen Tools und NuGet-Paketen und können ignoriert werden.

Vollständige Beispiele, die die Verwendung der XAML-Hosting-API zum Hosten eines WinRT-XAML-Steuerelements demonstrieren, finden Sie in den folgenden Codedateien:

Packen der App

Du kannst die App optional in einem MSIX-Paket für die Bereitstellung packen. MSIX ist eine moderne App-Pakettechnologie für Windows, die auf einer Kombination aus MSI-, APPX-, App-V- und ClickOnce-Installationstechnologien basiert.

Die folgenden Anweisungen veranschaulichen, wie du alle Komponenten in der Projektmappe in ein MSIX-Paket einschließen kannst, indem du die Option Paketerstellungsprojekt für Windows-Anwendungen in Visual Studio 2019 verwendest. Diese Schritte sind nur erforderlich, wenn du die App in einem MSIX-Paket packen möchtest.

Hinweis

Wenn Sie sich entscheiden, Ihre Anwendung nicht für die Bereitstellung in einem MSIX-Paket zu verpacken, muss auf Computern zur Ausführung Ihrer App die Visual C++-Runtime installiert sein.

  1. Fügen Sie Ihrer Projektmappe ein Paketerstellungsprojekt für Windows-Anwendungen hinzu. Wählen Sie beim Erstellen des Projekts Windows 10, Version 1903 (10.0; Build 18362) sowohl für Zielversion als auch für Mindestversion aus.

  2. Klicken Sie im Paketprojekt mit der rechten Maustaste auf den Knoten Anwendungen, und wählen Sie Verweis hinzufügen aus. Wähle in der Liste der Projekte das C++-Desktopanwendungsprojekt in deiner Projektmappe aus, und klicke auf OK.

  3. Kompiliere das Paketerstellungsprojekt, und führe es aus. Vergewissere dich, dass die App ausgeführt und die WinRT-XAML-Steuerelemente wie erwartet anzeigt.

  4. Informationen zum Verteilen/Bereitstellen des Pakets finden Sie unter Verwalten Ihrer MSIX-Bereitstellung.

Nächste Schritte

In den Codebeispielen in diesem Artikel wurde ein Basisszenario zum Hosten eines WinRT-XAML-Standardsteuerelements in einer C++--Desktop-App vorgestellt. In den folgenden Abschnitten werden weitere Szenarien vorgestellt, die deine Anwendung möglicherweise unterstützen muss.

Hosten eines benutzerdefinierten WinRT-XAML-Steuerelements

Für viele Szenarien musst du möglicherweise ein benutzerdefiniertes WinRT-XAML-Steuerelement hosten, das mehrere einzelne Steuerelemente enthält, die zusammenarbeiten. Das Verfahren zum Hosten eines benutzerdefinierten Steuerelements (entweder ein selbst definiertes Steuerelement oder ein von einem Drittanbieter bereitgestelltes Steuerelement) in einer C++-Desktop-App ist komplexer als das Hosten eines Standardsteuerelements und erfordert zusätzlichen Code.

Eine vollständige exemplarische Vorgehensweise findest du unter Hosten eines benutzerdefinierten WinRT-XAML-Steuerelements in einer C++-Desktop-App unter Verwendung der XAML-Hosting-API.

Erweiterte Szenarios

Viele Desktopanwendungen, die XAML Islands hosten, müssen zusätzliche Szenarien verarbeiten, um ein reibungsloses Benutzererlebnis zu gewährleisten. Beispielsweise müssen Desktopanwendungen möglicherweise Tastatureingaben in XAML Islands verarbeiten, den Navigationsfokus zwischen XAML Islands und anderen Benutzeroberflächenelementen verlagern und Layoutänderungen unterstützen.

Weitere Informationen zum Handhaben dieser Szenarien und Verweise auf entsprechende Codebeispiele finden Sie unter Erweiterte Szenarien für XAML Islands in C++-Desktop-Apps.