Share via


Usar la capa visual con Win32

Puede usar las API de Composition de Windows Runtime (también llamadas la capa visual) en las aplicaciones de Win32 para crear experiencias modernas que atraigan a los usuarios de Windows.

El código completo de este tutorial está disponible en GitHub: Ejemplo HelloComposition de Win32.

Las aplicaciones de la Plataforma universal de Windows que necesitan un control preciso sobre su composición de la interfaz de usuario tienen acceso al espacio de nombres Windows.UI.Composition para ejercer un control preciso sobre cómo se compone y representa su interfaz de usuario. Sin embargo, esta API de Composition no se limita a las aplicaciones para UWP. Las aplicaciones de escritorio de Win32 pueden aprovechar las ventajas de los sistemas de composición modernos en UWP y Windows.

Requisitos previos

La API de hospedaje de UWP tiene estos requisitos previos.

Cómo usar las API de Composition desde una aplicación de escritorio de Win32

En este tutorial, crearás una aplicación sencilla C++ de Win32 y le agregarás elementos de Composition de UWP. El objetivo es configurar correctamente el proyecto, crear el código de interoperabilidad y dibujar algo sencillo con las API de Composition de Windows. La aplicación finalizada tiene el siguiente aspecto.

The running app UI

Crear un proyecto de Win32 con C++ en Visual Studio

El primer paso es crear el proyecto de aplicación de Win32 en Visual Studio.

Para crear un nuevo proyecto de aplicación de Win32 en C++ llamado HelloComposition:

  1. Abre Visual Studio y selecciona Archivo>Nuevo>Proyecto.

    Se abre el cuadro de diálogo Nuevo proyecto.

  2. En la categoría Instalados, expande el nodo Visual C++ y, después, selecciona Escritorio de Windows.

  3. Selecciona la plantilla Aplicación de escritorio de Windows.

  4. Escribe el nombre HelloComposition y, a continuación, haz clic en Aceptar.

    Visual Studio crea el proyecto y abre el editor con el archivo de aplicación principal.

Configurar el proyecto para usar las API de Windows Runtime

Para usar las API de Windows Runtime (WinRT) en la aplicación de C++ para Win32, usamos C++/WinRT. Tienes que configurar el proyecto de Visual Studio para agregar compatibilidad con C++/WinRT.

(Para más información, consulta Introducción a C++a/WinRT: Modificar un proyecto de aplicación de escritorio de Windows para agregar compatibilidad con C++/WinRT).

  1. En el menú Proyecto, abre las propiedades del proyecto (Propiedades de HelloComposition) y asegúrate de establecer las opciones siguientes en los valores especificados:

    • En Configuración, selecciona Todas las configuraciones. En Plataforma, selecciona Todas las plataformas.
    • Propiedades de configuración>General>Windows SDK versión = 10.0.17763.0 o posterior

    Set SDK version

    • C/C++>Lenguaje>Estándar de lenguaje C++ = ISO C++ 17 Standard (/stf:c++17)

    Set language standard

    • En Enlazador>Entrada>Dependencias adicionales se debe incluir "windowsapp.lib". Si no está incluido en la lista, agrégalo.

    Add linker dependency

  2. Actualiza el encabezado precompilado.

    • Cambia el nombre de stdafx.h y stdafx.cpp a pch.h y pch.cpp, respectivamente.

    • Establece la propiedad del proyecto C/C++>Encabezados precompilados>Archivo de encabezado precompilado en pch.h.

    • Busca y reemplaza #include "stdafx.h" por #include "pch.h" en todos los archivos.

      (Editar>Buscar y reemplazar>Buscar en archivos)

      Find and replace stdafx.h

    • En pch.h, incluye winrt/base.h y unknwn.h.

      // reference additional headers your program requires here
      #include <unknwn.h>
      #include <winrt/base.h>
      

Es una buena idea compilar el proyecto en este momento para asegurarse de que no hay errores antes de continuar.

Crear una clase para hospedar elementos de Composition

Para hospedar el contenido que crea con la capa visual, crea una clase (CompositionHost) para administrar la interoperabilidad y crear elementos de Composition. Aquí es donde se realiza la mayoría de la configuración para hospedar las API de Composition, entre otra:

Esta clase se convierte en singleton para evitar problemas de subprocesamiento. Por ejemplo, solo puedes crear una cola de distribuidor por subproceso, por lo que al crear instancias de una segunda instancia de CompositionHost en el mismo subproceso se produciría un error.

Sugerencia

Si es necesario, comprueba el código completo al final del tutorial para asegurarte de que todo el código está en el lugar correcto mientras trabajas en el tutorial.

  1. Agrega un nuevo archivo de clase al proyecto.

    • En el Explorador de soluciones, haz clic con el botón derecho en el proyecto HelloComposition.
    • En el menú contextual, selecciona Agregar>Clase... .
    • En el cuadro de diálogo Agregar clase, asigna a la clase el nombre CompositionHost.cs y, después, haz clic en Agregar.
  2. Incluye encabezados y las declaraciones using necesarias para la interoperabilidad de Composition.

    • En CompositionHost.h, agrega estas instrucciones include en la parte superior del archivo.
    #pragma once
    #include <winrt/Windows.UI.Composition.Desktop.h>
    #include <windows.ui.composition.interop.h>
    #include <DispatcherQueue.h>
    
    • En CompositionHost.cpp, agrega estas instrucciones using en la parte superior del archivo, después de cualquier instrucción include.
    using namespace winrt;
    using namespace Windows::System;
    using namespace Windows::UI;
    using namespace Windows::UI::Composition;
    using namespace Windows::UI::Composition::Desktop;
    using namespace Windows::Foundation::Numerics;
    
  3. Edita la clase para que use el patrón singleton.

    • En CompositionHost.h, haz que el constructor sea privado.
    • Declara un método GetInstance público estático.
    class CompositionHost
    {
    public:
        ~CompositionHost();
        static CompositionHost* GetInstance();
    
    private:
        CompositionHost();
    };
    
    • En CompositionHost.cpp, agrega la definición del método GetInstance.
    CompositionHost* CompositionHost::GetInstance()
    {
        static CompositionHost instance;
        return &instance;
    }
    
  4. En CompositionHost.h, declara las variables de miembro privado para Compositor, DispatcherQueueController y DesktopWindowTarget.

    winrt::Windows::UI::Composition::Compositor m_compositor{ nullptr };
    winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr };
    winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{ nullptr };
    
  5. Agrega un método público para inicializar los objetos de interoperabilidad de Composition.

    Nota

    En Initialize, se llama a los métodos EnsureDispatcherQueue, CreateDesktopWindowTarget y CreateCompositionRoot. Estos métodos se crean en los pasos siguientes.

    • En CompositionHost.h, declara un método público llamado Initialize que toma un HWND como argumento.
    void Initialize(HWND hwnd);
    
    • En CompositionHost.cpp, agrega la definición del método Initialize.
    void CompositionHost::Initialize(HWND hwnd)
    {
        EnsureDispatcherQueue();
        if (m_dispatcherQueueController) m_compositor = Compositor();
    
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
    
  6. Crea una cola de distribuidores en el subproceso que va a usar Windows Composition.

    Se debe crear un compositor en un subproceso que tenga una cola de distribución, por lo que se llama primero a este método durante la inicialización.

    • En CompositionHost.h, declara un método privado llamado EnsureDispatcherQueue.
    void EnsureDispatcherQueue();
    
    • En CompositionHost.cpp, agrega la definición del método EnsureDispatcherQueue.
    void CompositionHost::EnsureDispatcherQueue()
    {
        namespace abi = ABI::Windows::System;
    
        if (m_dispatcherQueueController == nullptr)
        {
            DispatcherQueueOptions options
            {
                sizeof(DispatcherQueueOptions), /* dwSize */
                DQTYPE_THREAD_CURRENT,          /* threadType */
                DQTAT_COM_ASTA                  /* apartmentType */
            };
    
            Windows::System::DispatcherQueueController controller{ nullptr };
            check_hresult(CreateDispatcherQueueController(options, reinterpret_cast<abi::IDispatcherQueueController**>(put_abi(controller))));
            m_dispatcherQueueController = controller;
        }
    }
    
  7. Registra la ventana de la aplicación como un destino de Composition.

    • En CompositionHost.h, declara un método privado llamado CreateDesktopWindowTarget que toma un HWND como argumento.
    void CreateDesktopWindowTarget(HWND window);
    
    • En CompositionHost.cpp, agrega la definición del método CreateDesktopWindowTarget.
    void CompositionHost::CreateDesktopWindowTarget(HWND window)
    {
        namespace abi = ABI::Windows::UI::Composition::Desktop;
    
        auto interop = m_compositor.as<abi::ICompositorDesktopInterop>();
        DesktopWindowTarget target{ nullptr };
        check_hresult(interop->CreateDesktopWindowTarget(window, false, reinterpret_cast<abi::IDesktopWindowTarget**>(put_abi(target))));
        m_target = target;
    }
    
  8. Cree un contenedor visual raíz para incluir los objetos visuales.

    • En CompositionHost.h, declara un método privado llamado CreateCompositionRoot.
    void CreateCompositionRoot();
    
    • En CompositionHost.cpp, agrega la definición del método CreateCompositionRoot.
    void CompositionHost::CreateCompositionRoot()
    {
        auto root = m_compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        root.Offset({ 124, 12, 0 });
        m_target.Root(root);
    }
    

Compila el proyecto ahora para asegurarte de que no haya errores.

Estos métodos configuran los componentes necesarios para la interoperabilidad entre la capa visual de UWP y las API de Win32. Ahora puedes agregar contenido a la aplicación.

Agregar elementos de Composition

Una vez implementada la infraestructura, ahora puedes generar el contenido de Composition que quieres mostrar.

En este ejemplo, se agrega código que crea un cuadrado de color aleatorio SpriteVisual con una animación que hace caiga después de un breve período de tiempo.

  1. Agrega un elemento de Composition.

    • En CompositionHost.h, declara un método público llamado AddElement que toma 3 valores Float como argumentos.
    void AddElement(float size, float x, float y);
    
    • En CompositionHost.cpp, agrega la definición del método AddElement.
    void CompositionHost::AddElement(float size, float x, float y)
    {
        if (m_target.Root())
        {
            auto visuals = m_target.Root().as<ContainerVisual>().Children();
            auto visual = m_compositor.CreateSpriteVisual();
    
            auto element = m_compositor.CreateSpriteVisual();
            uint8_t r = (double)(double)(rand() % 255);;
            uint8_t g = (double)(double)(rand() % 255);;
            uint8_t b = (double)(double)(rand() % 255);;
    
            element.Brush(m_compositor.CreateColorBrush({ 255, r, g, b }));
            element.Size({ size, size });
            element.Offset({ x, y, 0.0f, });
    
            auto animation = m_compositor.CreateVector3KeyFrameAnimation();
            auto bottom = (float)600 - element.Size().y;
            animation.InsertKeyFrame(1, { element.Offset().x, bottom, 0 });
    
            using timeSpan = std::chrono::duration<int, std::ratio<1, 1>>;
    
            std::chrono::seconds duration(2);
            std::chrono::seconds delay(3);
    
            animation.Duration(timeSpan(duration));
            animation.DelayTime(timeSpan(delay));
            element.StartAnimation(L"Offset", animation);
            visuals.InsertAtTop(element);
    
            visuals.InsertAtTop(visual);
        }
    }
    

Crear y mostrar la ventana

Ahora, puedes agregar un botón y el contenido de Composition de UWP a la interfaz de usuario de Win32.

  1. En HelloComposition.cpp, en la parte superior del archivo, incluye CompositionHost.h, define BTN_ADD y obtén una instancia de CompositionHost.

    #include "CompositionHost.h"
    
    // #define MAX_LOADSTRING 100 // This is already in the file.
    #define BTN_ADD 1000
    
    CompositionHost* compHost = CompositionHost::GetInstance();
    
  2. En el método InitInstance, cambia el tamaño de la ventana que se crea. (En esta línea, cambia CW_USEDEFAULT, 0 a 900, 672).

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
    
  3. En la función WndProc, agrega case WM_CREATE al bloque switch message. En este caso, inicializas CompositionHost y creas el botón.

    case WM_CREATE:
    {
        compHost->Initialize(hWnd);
        srand(time(nullptr));
    
        CreateWindow(TEXT("button"), TEXT("Add element"),
            WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
            12, 12, 100, 50,
            hWnd, (HMENU)BTN_ADD, nullptr, nullptr);
    }
    break;
    
  4. También en la función WndProc, controla el clic del botón para agregar un elemento de Composition a la interfaz de usuario.

    Agrega case BTN_ADD al bloque switch wmId dentro del bloque WM_COMMAND.

    case BTN_ADD: // addButton click
    {
        double size = (double)(rand() % 150 + 50);
        double x = (double)(rand() % 600);
        double y = (double)(rand() % 200);
        compHost->AddElement(size, x, y);
        break;
    }
    

Ahora puedes compilar y ejecutar la aplicación. Si es necesario, comprueba el código completo al final del tutorial para asegurarte de que todo el código está en el lugar correcto.

Al ejecutar la aplicación y hacer clic en el botón, verás que se agregan los cuadrados animados a la interfaz de usuario.

Recursos adicionales

Código completo

Este es el código completo de la clase CompositionHost y el método InitInstance.

CompositionHost.h

#pragma once
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <windows.ui.composition.interop.h>
#include <DispatcherQueue.h>

class CompositionHost
{
public:
    ~CompositionHost();
    static CompositionHost* GetInstance();

    void Initialize(HWND hwnd);
    void AddElement(float size, float x, float y);

private:
    CompositionHost();

    void CreateDesktopWindowTarget(HWND window);
    void EnsureDispatcherQueue();
    void CreateCompositionRoot();

    winrt::Windows::UI::Composition::Compositor m_compositor{ nullptr };
    winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{ nullptr };
    winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr };
};

CompositionHost.cpp

#include "pch.h"
#include "CompositionHost.h"

using namespace winrt;
using namespace Windows::System;
using namespace Windows::UI;
using namespace Windows::UI::Composition;
using namespace Windows::UI::Composition::Desktop;
using namespace Windows::Foundation::Numerics;

CompositionHost::CompositionHost()
{
}

CompositionHost* CompositionHost::GetInstance()
{
    static CompositionHost instance;
    return &instance;
}

CompositionHost::~CompositionHost()
{
}

void CompositionHost::Initialize(HWND hwnd)
{
    EnsureDispatcherQueue();
    if (m_dispatcherQueueController) m_compositor = Compositor();

    if (m_compositor)
    {
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
}

void CompositionHost::EnsureDispatcherQueue()
{
    namespace abi = ABI::Windows::System;

    if (m_dispatcherQueueController == nullptr)
    {
        DispatcherQueueOptions options
        {
            sizeof(DispatcherQueueOptions), /* dwSize */
            DQTYPE_THREAD_CURRENT,          /* threadType */
            DQTAT_COM_ASTA                  /* apartmentType */
        };

        Windows::System::DispatcherQueueController controller{ nullptr };
        check_hresult(CreateDispatcherQueueController(options, reinterpret_cast<abi::IDispatcherQueueController**>(put_abi(controller))));
        m_dispatcherQueueController = controller;
    }
}

void CompositionHost::CreateDesktopWindowTarget(HWND window)
{
    namespace abi = ABI::Windows::UI::Composition::Desktop;

    auto interop = m_compositor.as<abi::ICompositorDesktopInterop>();
    DesktopWindowTarget target{ nullptr };
    check_hresult(interop->CreateDesktopWindowTarget(window, false, reinterpret_cast<abi::IDesktopWindowTarget**>(put_abi(target))));
    m_target = target;
}

void CompositionHost::CreateCompositionRoot()
{
    auto root = m_compositor.CreateContainerVisual();
    root.RelativeSizeAdjustment({ 1.0f, 1.0f });
    root.Offset({ 124, 12, 0 });
    m_target.Root(root);
}

void CompositionHost::AddElement(float size, float x, float y)
{
    if (m_target.Root())
    {
        auto visuals = m_target.Root().as<ContainerVisual>().Children();
        auto visual = m_compositor.CreateSpriteVisual();

        auto element = m_compositor.CreateSpriteVisual();
        uint8_t r = (double)(double)(rand() % 255);;
        uint8_t g = (double)(double)(rand() % 255);;
        uint8_t b = (double)(double)(rand() % 255);;

        element.Brush(m_compositor.CreateColorBrush({ 255, r, g, b }));
        element.Size({ size, size });
        element.Offset({ x, y, 0.0f, });

        auto animation = m_compositor.CreateVector3KeyFrameAnimation();
        auto bottom = (float)600 - element.Size().y;
        animation.InsertKeyFrame(1, { element.Offset().x, bottom, 0 });

        using timeSpan = std::chrono::duration<int, std::ratio<1, 1>>;

        std::chrono::seconds duration(2);
        std::chrono::seconds delay(3);

        animation.Duration(timeSpan(duration));
        animation.DelayTime(timeSpan(delay));
        element.StartAnimation(L"Offset", animation);
        visuals.InsertAtTop(element);

        visuals.InsertAtTop(visual);
    }
}

HelloComposition.cpp (parcial)

#include "pch.h"
#include "HelloComposition.h"
#include "CompositionHost.h"

#define MAX_LOADSTRING 100
#define BTN_ADD 1000

CompositionHost* compHost = CompositionHost::GetInstance();

// Global Variables:

// ...
// ... code not shown ...
// ...

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);

// ...
// ... code not shown ...
// ...
}

// ...

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
// Add this...
    case WM_CREATE:
    {
        compHost->Initialize(hWnd);
        srand(time(nullptr));

        CreateWindow(TEXT("button"), TEXT("Add element"),
            WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
            12, 12, 100, 50,
            hWnd, (HMENU)BTN_ADD, nullptr, nullptr);
    }
    break;
// ...
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
// Add this...
        case BTN_ADD: // addButton click
        {
            double size = (double)(rand() % 150 + 50);
            double x = (double)(rand() % 600);
            double y = (double)(rand() % 200);
            compHost->AddElement(size, x, y);
            break;
        }
// ...
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        // TODO: Add any drawing code that uses hdc here...
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// ...
// ... code not shown ...
// ...