Integrare un controllo XAML UWP standard in un'app desktop C++ (Win32)

Importante

Questo argomento usa o menziona i tipi del repository CommunityToolkit/Microsoft.Toolkit.Win32 GitHub. Per informazioni importanti sul supporto delle isole XAML UWP, vedi l'avviso XAML Islands Notice in tale repository.

Questo articolo illustra come usare l'API di hosting XAML UWP per ospitare un controllo XAML UWP standard (ovvero un controllo fornito dall'SDK Windows) in una nuova app desktop C++. Il codice si basa sull'esempio di 'Simple XAML Island', e questa sezione illustra alcune delle parti più importanti del codice. Se hai un progetto di app desktop C++ esistente, è possibile adattare questi passaggi ed esempi di codice per il progetto.

Annotazioni

Lo scenario illustrato in questo articolo non supporta la modifica diretta del markup XAML per i controlli XAML UWP ospitati nella tua app. Questo scenario limita l'utente alla modifica dell'aspetto e del comportamento dei controlli ospitati tramite codice. Per istruzioni che consentono di modificare direttamente il markup XAML quando si ospitano controlli XAML UWP, vedi Ospitare un controllo XAML UWP personalizzato in un'app desktop C++.

Creare un progetto di applicazione desktop

  1. In Visual Studio 2019 con il Windows 10, versione 1903 SDK (build 10.0.18362) o una versione successiva installata, crea un nuovo progetto applicazione desktop di Windows e chiamalo MyDesktopWin32App. Questo tipo di project è disponibile nei filtri C++, Windows e Desktop project.

  2. In Esplora soluzioni, Fare clic con il pulsante destro del mouse sul nodo della soluzione, scegliere Soluzione Di destinazione, selezionare il 10.0.18362.0 o una versione successiva dell'SDK, quindi fare clic su OK.

  3. Installare il pacchetto NuGet Microsoft.Windows.CppWinRT per abilitare il supporto per C++/WinRT nel tuo progetto:

    1. Fare clic con il pulsante destro del mouse sul project in Esplora soluzioni e scegliere Gestisci pacchetti NuGet.
    2. Seleziona la scheda Sfoglia, cerca il pacchetto Microsoft.Windows.CppWinRT e installa la versione più recente del pacchetto.

    Annotazioni

    Per i nuovi progetti, in alternativa è possibile installare C++/WinRT Visual Studio Extension (VSIX) e usare uno dei modelli di project C++/WinRT inclusi in tale estensione. Per altre informazioni, vedere Visual Studio supporto per C++/WinRT e VSIX.

  4. Nella scheda Browse della finestra NuGet Gestione pacchetti, cercare il pacchetto NuGet Microsoft.Toolkit.Win32.UI.SDK e installare la versione stabile più recente di questo pacchetto. Questo pacchetto fornisce diversi asset di compilazione ed esecuzione che consentono alle isole XAML UWP di funzionare nella tua app.

  5. Impostare il valore maxversiontested nel manifesto application manifest per specificare che l'applicazione è compatibile con Windows 10 versione 1903.

    1. Se nel project non è già presente un manifesto dell'applicazione, aggiungere un nuovo file XML al project e denominarlo app.manifest.

    2. Nel manifesto dell'applicazione includi l'elemento compatibility e gli elementi figlio mostrati nell'esempio seguente. Sostituire l'attributo Id dell'elemento maxversiontested con il numero della versione di Windows di destinazione (deve essere 10.0.18362.0 o una versione successiva). Tenere presente che l'impostazione di un valore superiore implica che le versioni precedenti di Windows non eseguiranno correttamente l'app perché ogni versione di Windows conosce solo le versioni che la precedono. Se vuoi che l'app venga eseguita in Windows 10 versione 1903 (build 10.0.18362), devi lasciare invariato il valore 10.0.18362.0 oppure aggiungere più maxversiontested elementi per i diversi valori supportati dall'app.

      <?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. Aggiungere un riferimento ai metadati Windows Runtime:

    1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nodo project References e selezionare Aggiungi riferimento.
    2. Fare clic sul pulsante Sfoglia nella parte inferiore della pagina e passare alla cartella UnionMetadata nel percorso di installazione dell'SDK. Per impostazione predefinita, l'SDK verrà installato in C:\Program Files (x86)\Windows Kits\10\UnionMetadata.
    3. Selezionare quindi la cartella denominata dopo la versione di Windows di destinazione (ad esempio 10.0.18362.0) e all'interno di tale cartella selezionare il Windows.winmd file.
    4. Scegliere OK per chiudere la finestra di dialogo Aggiungi riferimento.

Usare l'API di hosting XAML per ospitare un controllo XAML UWP

Il processo di base dell'uso dell'API di hosting XAML per ospitare un controllo XAML UWP segue questi passaggi generali:

  1. Inizializzare il framework XAML UWP per il thread corrente prima che l'app crei uno dei Windows. UI. Xaml.UIElement oggetti che ospiteranno. Esistono diversi modi per fare questo, a seconda di quando si prevede di creare l'oggetto DesktopWindowXamlSource che ospiterà i controlli.

    • Se l'applicazione crea l'oggetto DesktopWindowXamlSource prima di creare uno degli oggetti Windows.UI.Xaml.UIElement che ospiterà, questo framework verrà inizializzato automaticamente quando creerai un'istanza dell'oggetto DesktopWindowXamlSource. In questo scenario non devi aggiungere codice personalizzato per inizializzare il framework.

    • Tuttavia, se l'applicazione crea oggetti Windows.UI.Xaml.UIElement prima di creare l'oggetto DesktopWindowXamlSource che li ospiterà, l'applicazione deve chiamare il metodo statico WindowsXamlManager.InitializeForCurrentThread per inizializzare esplicitamente il framework XAML UWP prima che vengano istanziati i Windows.UI.Xaml.UIElement. L'applicazione deve in genere chiamare questo metodo quando viene creata un'istanza dell'elemento dell'interfaccia utente padre che ospita l'oggetto DesktopWindowXamlSource.

    Annotazioni

    Questo metodo restituisce un oggetto WindowsXamlManager che contiene un riferimento al framework XAML UWP. Puoi creare il numero di oggetti WindowsXamlManager desiderato in un determinato thread. Tuttavia, poiché ogni oggetto contiene un riferimento al framework XAML UWP, devi eliminare gli oggetti per assicurarti che le risorse XAML vengano rilasciate.

  2. Crea un oggetto DesktopWindowXamlSource e collegalo a un elemento dell'interfaccia utente padre nell'applicazione associato a un handle di finestra.

    Per fare questa operazione, è necessario seguire questa procedura:

    1. Crea un oggetto DesktopWindowXamlSource ed eseguine il cast nell'interfaccia COM IDesktopWindowXamlSourceNative o IDesktopWindowXamlSourceNative2.

    2. Chiama il metodo AttachToWindow dell'interfaccia IDesktopWindowXamlSourceNative o IDesktopWindowXamlSourceNative2 e passa l'handle della finestra dell'elemento padre dell'interfaccia utente nell'applicazione.

      Importante

      Assicurarsi che il codice chiami il metodo AttachToWindow una sola volta per ogni oggetto DesktopWindowXamlSource. Chiamare questo metodo più volte per un oggetto DesktopWindowXamlSource potrebbe causare una perdita di memoria.

    3. Imposta le dimensioni iniziali della finestra figlia interna contenuta nell'oggetto DesktopWindowXamlSource. Per impostazione predefinita, la larghezza e l'altezza di questa finestra figlio interna sono pari a 0. Se non imposti le dimensioni della finestra, i controlli XAML UWP aggiunti a DesktopWindowXamlSource non saranno visibili. Per accedere alla finestra figlio interna nel DesktopWindowXamlSource, utilizzare la proprietà WindowHandle dell'interfaccia IDesktopWindowXamlSourceNative o IDesktopWindowXamlSourceNative2.

  3. Assegna infine l'oggetto Windows.UI.Xaml.UIElement che vuoi ospitare nella proprietà Content dell'oggetto DesktopWindowXamlSource.

I passaggi e gli esempi di codice seguenti illustrano come to do implementare il processo precedente:

  1. Nella cartella Source Files del project aprire il file predefinito MyDesktopWin32App.cpp. Elimina l'intero contenuto del file e aggiungi le istruzioni include e using seguenti. Oltre alle intestazioni e ai namespace standard di C++ e UWP, queste istruzioni includono diversi elementi specifici per le XAML Islands UWP.

    #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. Dopo la sezione precedente, copia il codice seguente. Questo codice definisce la funzione WinMain per l'app. Questa funzione inizializza una finestra di base e usa l'API di hosting XAML per ospitare un controllo UWP TextBlock semplice nella finestra.

    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. Dopo la sezione precedente, copia il codice seguente. Questo codice definisce la procedura di finestra per la finestra.

    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. Salva il file di codice e quindi compila ed esegui l'app. Verifica che il controllo UWP TextBlock sia visualizzato nella finestra dell'app.

    Annotazioni

    È possibile che vengano visualizzati alcuni avvisi di compilazione, tra cui warning C4002: too many arguments for function-like macro invocation 'GetCurrentTime' e manifest authoring warning 81010002: Unrecognized Element "maxversiontested" in namespace "urn:schemas-microsoft-com:compatibility.v1". Si tratta di problemi noti relativi agli strumenti e ai pacchetti NuGet correnti e possono essere ignorati.

Per esempi completi che illustrano l'uso dell'API di hosting XAML per ospitare un controllo XAML UWP, vedi i file di codice seguenti:

Imballa l'app

Facoltativamente, puoi creare per l'app un pacchetto MSIX da usare per la distribuzione. MSIX è la moderna tecnologia per la creazione di pacchetti di app per Windows ed è basata su una combinazione delle tecnologie di installazione MSI, .appx, App-V e ClickOnce.

Le istruzioni seguenti illustrano come creare un pacchetto di tutti i componenti della soluzione in un pacchetto MSIX usando Windows Application Packaging Project in Visual Studio 2019. Questi passaggi sono necessari solo se vuoi creare un pacchetto dell'app in un pacchetto MSIX.

Annotazioni

Scegliendo di non creare un pacchetto MSIX dell'applicazione per la distribuzione, nei computer che eseguono l'app deve essere installato il runtime di Visual C++.

  1. Aggiungere un nuovo Windows Application Packaging Project alla soluzione. Quando si crea il progetto, selezionare Windows 10 versione 1903 (10.0; Build 18362) sia per la versione target che per la versione minima.

  2. Nel progetto di packaging, fare clic con il pulsante destro del mouse sul nodo Applicazioni e scegliere Aggiungi riferimento. Nell'elenco dei progetti, selezionare il progetto applicazione desktop C++ nella tua soluzione e fare clic su OK.

  3. Compilare ed eseguire il progetto di confezionamento. Verificare che l'app venga eseguita e visualizzi i controlli XAML UWP come previsto.

  4. Per informazioni sulla distribuzione del pacchetto, vedere Gestire la distribuzione MSIX.

Passaggi successivi

Gli esempi di codice in questo articolo illustrano come iniziare a usare lo scenario di base per l'hosting di un controllo XAML UWP standard in un'app desktop C++. Le sezioni seguenti introducono scenari aggiuntivi che l'applicazione potrebbe dover supportare.

Ospitare un controllo XAML UWP personalizzato

Per molti scenari, potrebbe essere necessario ospitare un controllo XAML UWP personalizzato che contiene diversi controlli singoli che interagiscono. Il processo di hosting di un controllo personalizzato (ovvero un controllo definito dall'utente o fornito da terze parti) in un'app desktop C++ è più complesso rispetto all'hosting di un controllo standard e richiede codice aggiuntivo.

Per una procedura dettagliata completa, vedi Ospitare un controllo XAML UWP personalizzato in un'app desktop C++ usando l'API di hosting XAML.

Scenari avanzati

Molte applicazioni desktop che ospitano isole XAML UWP dovranno gestire scenari aggiuntivi per offrire un'esperienza utente uniforme. Ad esempio, le applicazioni desktop potrebbero dover gestire l'input da tastiera nelle isole XAML UWP, la navigazione tra le isole XAML UWP e altri elementi dell'interfaccia utente, e le modifiche al layout.

Per altre informazioni sulla gestione di questi scenari e puntatori a esempi di codice correlati, vedi Scenari avanzati per le isole XAML UWP.