Ospitare un controllo XAML WinRT standard in un'app desktop C++ (Win32)

Importante

Questo argomento usa o menziona tipi del repository GitHub CommunityToolkit/Microsoft.Toolkit.Win32. Per informazioni importanti sul supporto per le isole XAML, vedere l'avviso sulle isole XAML nel repository citato.

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

Nota

Lo scenario illustrato in questo articolo non supporta la modifica diretta del markup XAML per i controlli XAML WinRT ospitati nell'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 vengono ospitati controlli XAML WinRT, vedere Ospitare un controllo XAML WinRT personalizzato in un'app desktop C++.

Creare un progetto di applicazione desktop

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

  2. In Esplora soluzioni fai clic con il pulsante destro del mouse sul nodo della soluzione, scegli Ridestina soluzione, seleziona la versione 10.0.18362.0 o una versione più recente dell'SDK e quindi fai clic su OK.

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

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

    Nota

    Per i nuovi progetti, puoi installare in alternativa l'Estensione di Visual Studio C++/WinRT (VSIX) e usare uno dei modelli di progetto C++/WinRT inclusi in questa estensione. Per informazioni dettagliate, vedere Supporto di Visual Studio per C++/WinRT e VSIX.

  4. Nella scheda Sfoglia della finestra Gestione pacchetti NuGet cercare il pacchetto NuGet Microsoft.Toolkit.Win32.UI.SDK e installare la versione stabile più recente del pacchetto. Questo pacchetto offre diverse risorse di compilazione e di esecuzione che consentono alle isole XAML di funzionare nell'app.

  5. Imposta il valore maxversiontested nel manifesto dell'applicazione per specificare che l'applicazione è compatibile con Windows 10, versione 1903.

    1. Se nel progetto non è già presente un manifesto dell'applicazione, aggiungi un nuovo file XML al progetto e assegnagli il nome 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 si vuole che l'app venga eseguita in Windows 10 versione 1903 (build 10.0.18362), è necessario lasciare il valore 10.0.18362.0 così come è oppure aggiungere più elementi maxversiontested 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 di Windows Runtime:

    1. In Esplora soluzioni fare clic con il pulsante destro del mouse sul nodo Riferimenti del progetto e scegliere 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 con la versione di Windows destinazione (ad esempio 10.0.18362.0) e all'interno di questa selezionare il file Windows.winmd.
    4. Scegliere OK per chiudere la finestra di dialogo Aggiungi riferimento.

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

La procedura di base per l'uso dell'API di hosting XAML per ospitare un controllo XAML WinRT include i passaggi seguenti:

  1. Inizializzare il framework XAML WinRT per il thread corrente prima che l'app crei uno degli oggetti Windows.UI.Xaml.UIElement che ospiterà. Questa operazione può essere eseguita in diversi modi, 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.

    • Se tuttavia l'applicazione crea gli oggetti Windows.UI.Xaml.UIElement prima di creare l'oggetto DesktopWindowXamlSource che li ospiterà, deve chiamare il metodo statico WindowsXamlManager.InitializeForCurrentThread per inizializzare in modo esplicito il framework XAML WinRT prima che vengano create istanze degli oggetti 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.

    Nota

    Questo metodo restituisce un oggetto WindowsXamlManager che contiene un riferimento al framework XAML WinRT. Puoi creare il numero di oggetti WindowsXamlManager desiderato in un determinato thread. Tuttavia, poiché ogni oggetto include un riferimento al framework XAML WinRT, occorre eliminare gli oggetti per assicurarsi 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.

    A tale scopo, dovrai eseguire 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 figlio interna contenuta nell'oggetto DesktopWindowXamlSource. Per impostazione predefinita, la larghezza e l'altezza di questa finestra figlio interna sono pari a 0. Se non si impostano le dimensioni della finestra, i controlli XAML WinRT aggiunti all'oggetto DesktopWindowXamlSource non saranno visibili. Per accedere alla finestra figlio interna in DesktopWindowXamlSource, utilizza 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 implementare questo processo:

  1. Nella cartella File di origine del progetto, apri il file MyDesktopWin32App.cpp predefinito. Elimina l'intero contenuto del file e aggiungi le istruzioni include e using seguenti. Oltre alle intestazioni e agli spazi dei nomi standard C++ e UWP, queste istruzioni includono diversi elementi specifici delle isole XAML.

    #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 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. 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.

    Nota

    È 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 una serie di esempi completi che illustrano l'uso dell'API di hosting XAML per ospitare un controllo XAML WinRT, vedere i file di codice seguenti:

Creare un pacchetto per 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 MSIX di tutti i componenti della soluzione usando Progetto di creazione pacchetti per applicazioni Windows in Visual Studio 2019. Questi passaggi sono necessari solo se vuoi creare un pacchetto dell'app in un pacchetto MSIX.

Nota

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. Aggiungi un nuovo progetto alla soluzione usando Progetto di creazione pacchetti per applicazioni Windows. Quando crei il progetto, seleziona Windows 10, versione 1903 (10.0; build 18362) per Versione di destinazione e Versione minima.

  2. Nel progetto di creazione del pacchetto fai clic con il pulsante destro del mouse sul nodo Applicazioni e scegli Aggiungi riferimento. Nell'elenco dei progetti selezionare il progetto di applicazione desktop C++ nella soluzione e scegliere OK.

  3. Compila ed esegui il progetto di creazione del pacchetto. Verificare che l'app venga eseguita e che i controlli XAML WinRT vengano visualizzati nel modo previsto.

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

Passaggi successivi

Gli esempi di codice inclusi in questo articolo consentono di acquisire familiarità con lo scenario di base per l'hosting di un controllo XAML WinRT standard in un'app desktop C++. Le sezioni seguenti introducono scenari aggiuntivi che l'applicazione potrebbe dover supportare.

Ospitare un controllo XAML WinRT personalizzato

Per molti scenari, potrebbe essere necessario ospitare un controllo XAML WinRT personalizzato contenente diversi controlli singoli che interagiscono tra loro. 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, vedere Ospitare un controllo XAML WinRT personalizzato in un'app desktop C++ usando l'API di hosting XAML.

Scenari avanzati

Molte applicazioni desktop che ospitano le isole XAML dovranno probabilmente gestire scenari aggiuntivi per offrire un'esperienza utente uniforme. Potrebbero ad esempio dover gestire l'input da tastiera nelle isole XAML, lo spostamento dello stato attivo tra le isole XAML e altri elementi dell'interfaccia utente e le modifiche di layout.

Per altre informazioni sulla gestione di questi scenari e per riferimenti a esempi di codice correlati, vedere Scenari avanzati per le isole XAML nelle app desktop C++.