Hosten eines standardmäßigen UWP-XAML-Steuerelements in einer C++-Desktop-App (Win32)-App

Von Bedeutung

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

In diesem Artikel wird die Verwendung der UWP-XAML-Hosting-API zum Hosten eines standardmäßigen UWP-XAML-Steuerelements (d. h. eines Steuerelements vom Windows SDK) in einer neuen C++-Desktop-App veranschaulicht. Der Code basiert auf dem Beispiel simple XAML Island und in diesem Abschnitt werden einige der wichtigsten Teile des Codes erläutert. Wenn Sie über eine vorhandene C++-Desktop-App project verfügen, können Sie diese Schritte und Codebeispiele für Ihre project anpassen.

Hinweis

Das in diesem Artikel gezeigte Szenario unterstützt die direkte Bearbeitung von XAML-Markup für in Ihrer App gehostete UWP-XAML-Steuerelemente nicht. In diesem Szenario bist du darauf beschränkt, das Aussehen und Verhalten der gehosteten Steuerelemente über den Code zu ändern. Anweisungen zum direkten Bearbeiten von XAML-Markup beim Hosten von UWP-XAML-Steuerelementen finden Sie unter Hosten eines benutzerdefinierten UWP-XAML-Steuerelements in einer C++-Desktop-App.

Erstellen Sie ein Desktopanwendungsprojekt

  1. Erstellen Sie in Visual Studio 2019 mit dem Windows 10, Version 1903 SDK (Build 10.0.18362) oder einer höheren Version, eine neue Windows Desktop Application project und nennen Sie sie MyDesktopWin32App. Dieser project Typ ist unter dem Filter C++, Windows und Desktop project verfügbar.

  2. In Projektmappen-Explorer, Klicken Sie mit der rechten Maustaste auf den Lösungsknoten, klicken Sie auf Retarget-Lösung, wählen Sie die 10.0.18362.0 oder eine spätere SDK-Version aus, und klicken Sie dann auf OK.

  3. Installieren Sie das Microsoft.Windows.CppWinRT NuGet-Paket, um die Unterstützung für C++/WinRT in Ihrem project zu aktivieren:

    1. Klicken Sie mit der rechten Maustaste auf Ihre project in Projektmappen-Explorer und wählen Sie Manage NuGet Packages aus.
    2. Wähle die Registerkarte Durchsuchen aus, suche nach dem Paket Microsoft.Windows.CppWinRT, und installiere die neueste Version dieses Pakets.

    Hinweis

    Bei neuen Projekten können Sie alternativ die C++/WinRT Visual Studio Extension (VSIX) installieren und eine der in dieser Erweiterung enthaltenen C++/WinRT-project Vorlagen verwenden. Weitere Informationen finden Sie unter Visual Studio-Unterstützung für C++/WinRT und vsIX.

  4. Suchen Sie auf der Registerkarte Browse des NuGet-Paket-Managerfensters nach dem Microsoft.Toolkit.Win32.UI.SDK NuGet-Paket, und installieren Sie die neueste stabile Version dieses Pakets. Dieses Paket bietet mehrere Build- und Laufzeitressourcen, mit denen UWP-XAML-Inseln in Ihrer App funktionieren können.

  5. Legen Sie den wert maxversiontested in Ihrem application manifest fest, um anzugeben, dass Ihre Anwendung mit Windows 10, Version 1903, kompatibel ist.

    1. Wenn Sie noch kein Anwendungsmanifest in Ihrem project haben, fügen Sie ihrer project eine neue XML-Datei hinzu, und nennen Sie sie 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 auf Windows 10, Version 1903 (Build 10.0.18362), ausgeführt werden soll, sollten Sie entweder den Wert 10.0.18362.0 beibehalten oder mehrere maxversiontestedElemente für die verschiedenen Von der App unterstützten Werte hinzufügen.

      <?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 in Projektmappen-Explorer mit der rechten Maustaste auf den Referenzen-Knoten Ihres Projekts, und wählen Sie die Option 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 Windows.winmd Datei aus.
    4. Klicken Sie auf OK, um das Dialogfeld Verweis hinzufügen zu schließen.

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

Der grundlegende Prozess der Verwendung der XAML-Hosting-API zum Hosten eines UWP-XAML-Steuerelements folgt den folgenden allgemeinen Schritten:

  1. Initialisieren Sie das UWP-XAML-Framework für den aktuellen Thread, bevor Ihre App eines der Windows.UI.Xaml.UIElement-Objekte erstellt, die sie hosten wird. Es gibt mehrere Möglichkeiten, je nachdem, wann Sie das DesktopWindowXamlSource-Objekt erstellen möchten, das die Steuerelemente hosten soll.

    • 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 Ihre Anwendung jedoch die Windows.UI.Xaml.UIElement-Objekte erstellt, bevor das DesktopWindowXamlSource-Objekt erstellt wird, das sie hosten soll, muss Ihre Anwendung die statische WindowsXamlManager.InitializeForCurrentThread-Methode aufrufen, um das UWP-XAML-Framework explizit zu initialisieren, bevor die Windows.UI.Xaml.UIElement-Objekte instanziiert werden. 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 Verweis auf das UWP-XAML-Framework enthält. Du kannst für einen bestimmten Thread beliebig viele WindowsXamlManager-Objekte erstellen. Da jedes Objekt jedoch einen Verweis auf das UWP-XAML-Framework enthält, sollten Sie die Objekte verwerfen, um sicherzustellen, dass 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.

    Um dies zu tun, müssen Sie die folgenden Schritte ausführen:

    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.

      Von Bedeutung

      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 Sie die Größe des Fensters nicht festlegen, werden alle UWP-XAML-Steuerelemente, die Sie der DesktopWindowXamlSource hinzufügen, nicht angezeigt. Um auf das interne untergeordnete Fenster im DesktopWindowXamlSource zuzugreifen, verwenden Sie die Eigenschaft WindowHandle der IDesktopWindowXamlSourceNative- oder IDesktopWindowXamlSourceNative2-Schnittstelle.

  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 to do den obigen Prozess implementieren:

  1. Öffnen Sie im Ordner Source Files der project 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 mehrere Elemente, die für UWP-XAML-Inseln spezifisch sind.

    #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 UWP 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 sehen Sie mehrere Build-Warnungen, einschließlich 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 UWP-XAML-Steuerelements veranschaulichen, finden Sie in den folgenden Codedateien:

Verpacken 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 zeigen, wie Sie alle Komponenten in der Lösung in einem MSIX-Paket mithilfe der Windows Application Packaging Project in Visual Studio 2019 verpacken. 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 Lösung ein neues Windows Application Packaging Project hinzu. Wählen Sie beim Erstellen des project Windows 10, Version 1903 (10.0; Build 18362) für die Target-Version und Minimum.

  2. Klicken Sie im Verpackungsprojekt mit der rechten Maustaste auf den Knoten Applications und wählen Sie Verweis hinzufügen aus. Wählen Sie in der Liste der Projekte das C++-Desktopanwendungsprojekt in Ihrer Lösung aus, und klicken Sie auf OK.

  3. Erstellen Sie und führen Sie das Verpackungsprojekt aus. Vergewissern Sie sich, dass die App ausgeführt wird und die UWP-XAML-Steuerelemente erwartungsgemäß anzeigt.

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

Nächste Schritte

Die Codebeispiele in diesem Artikel beginnen mit dem grundlegenden Szenario des Hostens eines standardmäßigen UWP-XAML-Steuerelements in einer C++-Desktop-App. In den folgenden Abschnitten werden weitere Szenarien vorgestellt, die deine Anwendung möglicherweise unterstützen muss.

Hosten eines benutzerdefinierten UWP-XAML-Steuerelements

In vielen Szenarien müssen Sie möglicherweise ein benutzerdefiniertes UWP-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 finden Sie unter Hosten eines benutzerdefinierten UWP-XAML-Steuerelements in einer C++-Desktop-App mit der XAML-Hosting-API.

Erweiterte Szenarien

Viele Desktopanwendungen, die UWP-XAML-Inseln hosten, müssen zusätzliche Szenarien verarbeiten, um eine reibungslose Benutzererfahrung zu ermöglichen. Desktopanwendungen müssen z. B. tastatureingaben in UWP-XAML-Inseln, Fokusnavigation zwischen UWP-XAML-Inseln und anderen UI-Elementen sowie Layoutänderungen behandeln.

Weitere Informationen zum Behandeln dieser Szenarien und Zeiger auf verwandte Codebeispiele finden Sie in erweiterten Szenarien für UWP-XAML-Inseln.