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

Importante

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

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

Nota

El escenario que se muestra en este artículo no permite editar directamente el marcado XAML de los controles XAML de WinRT hospedados en la aplicación. Este escenario solo permite modificar la apariencia y el comportamiento de los controles hospedados mediante código. Si quiere ver las instrucciones para editar directamente el marcado XAML al hospedar controles XAML de WinRT, consulte Hospedaje de un control XAML personalizado de WinRT en una aplicación de C++ de escritorio.

Creación de un proyecto de aplicación de escritorio

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

  2. En el Explorador de soluciones, haz clic con el botón derecho en el nodo de la solución, haz clic en Redestinar solución, selecciona la versión 10.0.18362.0 del SDK o una posterior y después haz clic en Aceptar.

  3. Instala el paquete NuGet Microsoft.Windows.CppWinRT para incluir compatibilidad con C++/WinRT en el proyecto:

    1. Haz clic con el botón derecho en el proyecto en el Explorador de soluciones y elige 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 proyectos nuevos, puedes instalar la extensión de Visual Studio para C++/WinRT (VSIX) y usar una de las plantillas de proyecto de C+/WinRT incluidas en esa extensión. Para más información, consulte Compatibilidad de Visual Studio con C++/WinRT, XAML, la extensión VSIX y el paquete NuGet.

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

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

    1. Si aún no tienes un manifiesto de aplicación en el proyecto, agrega un nuevo archivo XML al proyecto y asígnale 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 quiere que la aplicación se ejecute en Windows 10, versión 1903 (compilación 10.0.18362), debe dejar el valor 10.0.18362.0 tal y como está, o bien agregar varios elementos 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 el Explorador de soluciones, haga clic con el botón derecho en el nodo Referencias del proyecto 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.

Uso de la API de hospedaje de XAML para hospedar un control XAML de WinRT

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

  1. Inicialice el marco XAML de WinRT para el subproceso actual antes de que la aplicación cree cualquiera de los objetos Windows.UI.Xaml.UIElement que hospedará. Hay varias maneras de hacerlo, en función del momento en que planeas crear el objeto DesktopWindowXamlSource que hospedará a 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 marco XAML de WinRT antes de crear instancias de 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 WinRT. Puedes crear tantos objetos WindowsXamlManager como quieras en un subproceso determinado. Sin embargo, dado que cada objeto contiene una referencia al marco XAML de WinRT, debe eliminarlos para asegurarse de que se liberen los recursos XAML.

  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 ello, debes 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 establece el tamaño de la ventana, los controles XAML de WinRT que agregue al objeto DesktopWindowXamlSource no serán visibles. Para acceder a la ventana secundaria interna en DesktopWindowXamlSource, utiliza 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.

En los pasos y ejemplos de código siguientes se muestra cómo implementar el proceso anterior:

  1. En la carpeta Source files del proyecto, abre 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 nombre estándar de C++ y UWP, estas instrucciones incluyen varios elementos específicos de las islas 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. 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 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. 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 cómo usar la API de hospedaje de XAML para hospedar un control XAML de WinRT, consulte 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 proyecto de paquete de aplicación de Windows 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. Agrega un nuevo Proyecto de paquete de aplicación de Windows a la solución. Al crear el proyecto, selecciona Windows 10, versión 1903 (10.0; compilación 18362) para la versión de destino y la versión mínima.

  2. En el proyecto de empaquetado, haz clic con el botón derecho en el nodo Aplicaciones y elige Agregar referencia. En la lista de proyectos, seleccione el proyecto de aplicación de C++ de escritorio en la solución y haga clic en Aceptar.

  3. Compila y ejecuta el proyecto de empaquetado. Confirme si la aplicación se ejecuta y muestra los controles XAML de WinRT 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 son una introducción al escenario básico para hospedar un control XAML estándar de WinRT en una aplicación de C++ de escritorio. En las secciones siguientes se presentan escenarios adicionales que la aplicación quizá debería admitir.

Hospedaje de un control XAML personalizado de WinRT

En muchos casos, quizá necesite hospedar un control XAML personalizado de WinRT que contenga varios controles individuales que funcionan de forma conjunta. 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, consulte Hospedaje de un control XAML personalizado en una aplicación de C++ de escritorio mediante la API de hospedaje de XAML.

Escenarios avanzados

Muchas aplicaciones de escritorio que hospedan islas XAML deberán controlar escenarios adicionales para ofrecer una experiencia de usuario fluida. Por ejemplo, es posible que las aplicaciones de escritorio deban controlar la entrada del teclado en las islas XAML y la navegación del foco entre islas XAML y otros elementos de la interfaz de usuario, así como los cambios de diseño.

Para obtener más información acerca de cómo controlar estos escenarios y obtener vínculos a ejemplos de código relacionados, consulte Escenarios avanzados para islas XAML en aplicaciones de C++ de escritorio.