Hospedar un control XAML de UWP estándar en una aplicación de escritorio de C++ (Win32)

Importante

En este tema se usan o mencionan tipos del repositorio CommunityToolkit/Microsoft.Toolkit.Win32 GitHub. Para obtener información importante sobre la compatibilidad con XAML Islands para UWP, por favor consulta el Aviso de XAML Islands en ese repositorio.

En este artículo se muestra cómo usar la API de hospedaje XAML de UWP para hospedar un control XAML estándar de UWP (es decir, un control proporcionado por el SDK de Windows) en una nueva aplicación de escritorio de C++. El código se basa en el ejemplo simple de isla XAML, y en esta sección se comentan algunas de las partes más importantes del código. Si tiene un proyecto de aplicación de escritorio de C++ existente, puede adaptar estos pasos y ejemplos de código para su proyecto.

Nota:

El escenario que se muestra en este artículo no admite la edición directa del marcado XAML para los controles XAML de UWP hospedados en la aplicación. Este escenario solo permite modificar la apariencia y el comportamiento de los controles hospedados mediante código. Para obtener instrucciones que te permiten editar directamente el marcado XAML al hospedar controles XAML de UWP, consulta Hospedar un control XAML de UWP personalizado en una aplicación de escritorio de C++.

Crea un proyecto de aplicación de escritorio

  1. En Visual Studio 2019 con el SDK de Windows 10, versión 1903 (compilación 10.0.18362) o una versión posterior instalada, cree un nuevo Windows Desktop Application project y asígnelo el nombre MyDesktopWin32App. Este tipo de proyecto está disponible en los filtros de proyecto C++, Windows y Desktop.

  2. En Explorador de soluciones, Haga clic con el botón derecho en el nodo de solución, haga clic en Retarget solution seleccione la 10.0.18362.0 o una versión posterior del SDK y, a continuación, haga clic en OK.

  3. Instale el paquete NuGet <>Microsoft.Windows.CppWinRT para habilitar la compatibilidad con C++/WinRT en el project:

    1. Haga clic con el botón derecho en el project en Explorador de soluciones y elija Administrar paquetes NuGet.
    2. Selecciona la pestaña Examinar, busca el paquete Microsoft.Windows.CppWinRT e instala la última versión de dicho paquete.

    Nota:

    En el caso de los nuevos proyectos, también puede instalar la extensión de Visual Studio C++/WinRT (VSIX) y usar una de las plantillas de proyecto de C++/WinRT incluidas en esa extensión. Para obtener más información, consulte Visual Studio compatibilidad con C++/WinRT y VSIX.

  4. En la pestaña Browse de la ventana NuGet Administrador de paquetes, busque la ventana Microsoft.Toolkit.Win32.UI.SDK NuGet e instale la versión estable más reciente de este paquete. Este paquete proporciona varios recursos en tiempo de compilación y ejecución que permiten que las islas XAML de UWP funcionen en la aplicación.

  5. Establezca el valor maxversiontested en el manifiesto de application para especificar que la aplicación sea compatible con Windows 10, versión 1903.

    1. Si aún no tiene un manifiesto de aplicación en la project, agregue un nuevo archivo XML a la project y asígnele el nombre app.manifest.

    2. En el manifiesto de aplicación, incluye el elemento compatibility y los elementos secundarios que se muestran en el ejemplo siguiente. Reemplace el atributo Id del elemento maxversiontested por el número de versión de Windows que tenga como destino (debe ser 10.0.18362.0 o una versión posterior). Tenga en cuenta que establecer un valor superior significa que las versiones anteriores de Windows no ejecutarán la aplicación correctamente porque cada versión de Windows solo conoce las versiones anteriores a ella. Si quieres que la aplicación se ejecute en Windows 10, versión 1903 (compilación 10.0.18362), debes dejar el valor 10.0.18362.0 tal como está o agregar varios maxversiontested para los distintos valores que admite la aplicación.

      <?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. Agregue una referencia a los metadatos de Windows Runtime:

    1. En Explorador de soluciones, haga clic con el botón derecho en el nodo project References y seleccione Agregar referencia.
    2. Haga clic en el botón Examinar situado en la parte inferior de la página y navegue hasta la carpeta UnionMetadata en la ruta de instalación del SDK. De forma predeterminada, el SDK se instalará en C:\Program Files (x86)\Windows Kits\10\UnionMetadata.
    3. A continuación, seleccione la carpeta con el nombre de la versión de Windows de destino (por ejemplo, 10.0.18362.0) y, dentro de esa carpeta, seleccione el archivo Windows.winmd.
    4. Haga clic en Aceptar para cerrar el cuadro de diálogo Agregar referencia.

Usar la API de hospedaje XAML para hospedar un control XAML para UWP

El proceso básico de usar la API de hospedaje XAML para hospedar un control XAML para UWP sigue estos pasos generales:

  1. Inicialice el marco XAML de UWP para el subproceso actual antes de que su aplicación cree cualquiera de los objetos Windows.UI.Xaml.UIElement que hospedará. Hay varias maneras de hacer esto, dependiendo de cuándo planeas crear el objeto DesktopWindowXamlSource que alojará los controles.

    • Si la aplicación crea el objeto DesktopWindowXamlSource antes de crear cualquiera de los objetos Windows.UI.Xaml.UIElement que se van a hospedar, el marco de trabajo se inicializará al crear una instancia del objeto DesktopWindowXamlSource. En este escenario, no es necesario agregar ningún código propio para inicializar el marco de trabajo.

    • Sin embargo, si la aplicación crea los objetos Windows.UI.Xaml.UIElement antes de crear el objeto DesktopWindowXamlSource que los hospedará, la aplicación debe llamar al método estático WindowsXamlManager.InitializeForCurrentThread para inicializar explícitamente el UWP XAML framework antes de que se instancien los objetos Windows.UI.Xaml.UIElement. Normalmente, la aplicación debe llamar a este método cuando se crea una instancia del elemento principal de la interfaz de usuario que hospeda a DesktopWindowXamlSource.

    Nota:

    Este método devuelve un objeto WindowsXamlManager que contiene una referencia al marco XAML de UWP. Puedes crear tantos objetos WindowsXamlManager como quieras en un subproceso determinado. Sin embargo, dado que cada objeto contiene una referencia al marco XAML de UWP, debes eliminar los objetos para asegurarte de que los recursos XAML se publiquen finalmente.

  2. Crea un objeto DesktopWindowXamlSource y asócialo a un elemento principal de la interfaz de usuario de la aplicación que esté asociado con un identificador de ventana.

    Para hacer esto, necesitará seguir estos pasos:

    1. Crea un objeto DesktopWindowXamlSource y conviértelo a la interfaz COM IDesktopWindowXamlSourceNative o IDesktopWindowXamlSourceNative2.

    2. Llama al método AttachToWindow de la interfaz IDesktopWindowXamlSourceNative o IDesktopWindowXamlSourceNative2 y pasa el identificador de ventana del elemento principal de la interfaz de usuario de la aplicación.

      Importante

      Asegúrese de que el código llama al método AttachToWindow solo una vez por cada objetoDesktopWindowXamlSource. Llamar a este método más de una vez para un objeto DesktopWindowXamlSource podría provocar una pérdida de memoria.

    3. Establece el tamaño inicial de la ventana secundaria interna contenida en el objeto DesktopWindowXamlSource. De forma predeterminada, esta ventana secundaria interna tiene configurado un ancho y un alto de 0. Si no estableces el tamaño de la ventana, los controles XAML de UWP que agregues al DesktopWindowXamlSource no estarán visibles. Para acceder a la ventana secundaria interna de la DesktopWindowXamlSource, use la propiedad WindowHandle de la interfaz IDesktopWindowXamlSourceNative o IDesktopWindowXamlSourceNative2.

  3. Por último, asigna el elemento Windows.UI.Xaml.UIElement que quieres hospedar a la propiedad Content del objeto DesktopWindowXamlSource.

Los siguientes pasos y ejemplos de código muestran cómo implementar el proceso anterior.

  1. En la carpeta Source Files del project, abra el archivo predeterminado MyDesktopWin32App.cpp. Elimina todo el contenido del archivo y agrega las siguientes instrucciones include y using. Además de los encabezados y espacios de nombres estándar de C++ y UWP, estas instrucciones incluyen diversos elementos específicos de UWP XAML Islands.

    #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. Copia el código siguiente después de la sección anterior. En este código se define la función WinMain de la aplicación. Esta función inicializa una ventana básica y usa la API de hospedaje de XAML para hospedar un control sencillo TextBlock de UWP en la ventana.

    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. Copia el código siguiente después de la sección anterior. En este código se define el procedimiento de la ventana.

    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. Guarda el archivo de código y compila y ejecuta la aplicación. Confirma que el control TextBlock de UWP se muestra en la ventana de la aplicación.

    Nota:

    Es posible que aparezcan varias advertencias de compilación, como warning C4002: too many arguments for function-like macro invocation 'GetCurrentTime' y manifest authoring warning 81010002: Unrecognized Element "maxversiontested" in namespace "urn:schemas-microsoft-com:compatibility.v1". Estas advertencias son sobre problemas conocidos con las herramientas actuales y los paquetes NuGet, por lo que pueden omitirse.

Para obtener ejemplos completos que muestran el uso de la API de hospedaje XAML para hospedar un control XAML para UWP, consulta los siguientes archivos de código:

Empaquetado de la aplicación

También pueden empaquetar la aplicación en un paquete MSIX para implementarla. MSIX es una tecnología de empaquetado de aplicaciones moderna para Windows y está basada en una combinación de las tecnologías de instalación .msi, .appx, App-V y ClickOnce.

Las instrucciones siguientes muestran cómo empaquetar todos los componentes de la solución en un paquete MSIX mediante el Windows Application Packaging Project en Visual Studio 2019. Estos pasos solo son necesarios si quieres empaquetar la aplicación en un paquete MSIX.

Nota:

Si decide no empaquetar la aplicación en un paquete MSIX para su implementación, los equipos que ejecuten esa aplicación deberán tener instalado el entorno de ejecución de Visual C++.

  1. Agregue un nuevo Windows Application Packaging Project a la solución. Al crear el project, seleccione Windows 10, versión 1903 (10.0; Compilación 18362) para la versión Target y Minimum.

  2. En el proyecto de empaquetado, haga clic con el botón derecho en el nodo Applications y elija Añadir referencia. En la lista de proyectos, seleccione el proyecto de aplicación de escritorio en C++ de su solución y haga clic en OK.

  3. Compila y ejecuta el proyecto de empaquetado. Confirme que la aplicación se ejecuta y muestra los controles XAML de UWP según lo previsto.

  4. Para más información sobre cómo distribuir o implementar el paquete, consulte Administración de la implementación MSIX.

Pasos siguientes

Los ejemplos de código de este artículo te ayudarán a empezar con el escenario básico de hospedar un control XAML estándar para UWP en una aplicación de escritorio de C++. En las secciones siguientes se presentan escenarios adicionales que la aplicación quizá debería admitir.

Hospedar un control XAML de UWP personalizado

En muchos escenarios, es posible que tengas que hospedar un control XAML de UWP personalizado que contenga varios controles individuales que funcionen juntos. El proceso para hospedar un control personalizado (ya sea un control definido por el usuario o un control proporcionado por un tercero) en una aplicación de C++ de escritorio es más complejo que hospedar un control estándar y requiere código adicional.

Para ver un tutorial completo, consulta Hospedar un control XAML de UWP personalizado en una aplicación de escritorio de C++ mediante la API de hospedaje XAML.

Escenarios avanzados

Muchas aplicaciones de escritorio que hospedan islas XAML para UWP tendrán que controlar escenarios adicionales para proporcionar una experiencia de usuario fluida. Por ejemplo, es posible que las aplicaciones de escritorio necesiten controlar la entrada del teclado en islas XAML para UWP, gestionar la navegación de enfoque entre las islas XAML de UWP y otros elementos de la interfaz de usuario, y gestionar los cambios en el diseño.

Para obtener más información sobre cómo gestionar estos escenarios y enlaces a ejemplos de código relacionados, consulta Escenarios avanzados para islas XAML para UWP.