Partilhar via


Usar a Camada visual com Win32

Você pode usar as APIs de Composição do Windows Runtime (também conhecidas como Camada visual) em seus aplicativos Win32 para criar experiências modernas que se destacam para os usuários do Windows.

O código completo para este tutorial está disponível no GitHub: Exemplo do HelloComposition para Win32.

Os aplicativos universais do Windows que necessitam de controle preciso sobre a composição da interface do usuário têm acesso ao namespace Windows.UI.Composition para exercer controle refinado sobre como a interface do usuário é composta e renderizada. No entanto, essa API de composição não está limitada a aplicativos UWP. Aplicativos Win32 da área de trabalho podem aproveitar os sistemas de composição modernos na UWP e no Windows.

Pré-requisitos

A API de hospedagem UWP tem estes pré-requisitos.

Como usar APIs de Composição de um aplicativo Win32 da área de trabalho

Neste tutorial, você criará um aplicativo Win32 C++ simples e adicionará elementos de composição UWP a ele. O foco está na configuração correta do projeto, na criação do código de interoperabilidade e no design de algo simples usando APIs Windows Composition. O aplicativo concluído tem esta aparência.

The running app UI

Criar um projeto C++ Win32 no Visual Studio

A primeira etapa é criar o projeto de aplicativo Win32 no Visual Studio.

Para criar um projeto de aplicativo Win32 no C++ chamado HelloComposition:

  1. Abra o Visual Studio e selecione Arquivo>Novo>Projeto.

    A caixa de diálogo Novo projeto é aberta.

  2. Na categoria Instalado, expanda o nó Visual C++ e, em seguida, selecione Área de trabalho do Windows.

  3. Selecione o modelo Aplicativo da área de trabalho do Windows.

  4. Insira o nome HelloComposition e clique em OK.

    O Visual Studio cria o projeto e abre o editor do arquivo do aplicativo principal.

Configurar o projeto para usar APIs do Windows Runtime

Para usar APIs do WinRT (Windows Runtime) em seu aplicativo Win32, usamos C++/WinRT. Você precisa configurar seu projeto do Visual Studio para adicionar suporte a C++/WinRT.

(Para ver mais detalhes, confira Introdução ao C++/WinRT – Modificar um projeto de aplicativo da área de trabalho do Windows para adicionar suporte a C++/WinRT).

  1. No menu Projeto, abra as propriedades do projeto (Propriedades do HelloComposition) e verifique se as seguintes configurações estão definidas com os valores especificados:

    • Para Configuração, selecione Todas as Configurações. Para Plataforma, selecione Todas as Plataformas.
    • Propriedades de Configuração>Geral>Versão do SDK do Windows = 10.0.17763.0 ou posterior

    Set SDK version

    • C/C++>Linguagem>Linguagem C++ Padrão = ISO C++ 17 Padrão (/STF: c++ 17)

    Set language standard

    • Vinculador>Entrada>Dependências Adicionais deve incluir "windowsapp.lib". Se não estiver incluído na lista, adicione-o.

    Add linker dependency

  2. Atualizar o cabeçalho pré-compilado

    • Renomeie stdafx.h e stdafx.cpp como pch.h e pch.cpp, respectivamente.

    • Defina a propriedade do projeto C/C++>Cabeçalhos Pré-compilados>Arquivo de Cabeçalho Pré-compilado para pch.h.

    • Localize e substitua #include "stdafx.h" por #include "pch.h" em todos os arquivos.

      (Editar>Localizar e Substituir>Localizar nos Arquivos)

      Find and replace stdafx.h

    • Em pch.h, inclua winrt/base.h e unknwn.h.

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

É uma boa ideia compilar o projeto neste momento para verificar se não há erros antes de continuar.

Criar uma classe para hospedar elementos de composição

Para hospedar o conteúdo criado com a camada visual, crie uma classe (CompositionHost) para gerenciar a interoperabilidade e criar elementos de composição. Neste momento, você faz a maior parte da configuração para hospedar APIs de composição, incluindo:

Tornamos essa classe um singleton para evitar problemas de threading. Por exemplo, só é possível criar uma fila de dispatcher por thread, portanto, criar uma segunda instância de CompositionHost no mesmo thread causaria um erro.

Dica

Se necessário, verifique o código completo no final do tutorial para garantir que todo o código esteja nos locais certos enquanto você trabalha no tutorial.

  1. Adicione um novo arquivo de classe ao projeto.

    • No Gerenciador de Soluções, clique com o botão direito do mouse no projeto HelloComposition.
    • No menu de contexto, selecione Adicionar>Classe.... .
    • Na caixa de diálogo Adicionar Classe, nomeie a classe CompositionHost.cs e clique em Adicionar.
  2. Inclua cabeçalhos e usos necessários para a interoperabilidade de composição.

    • Em CompositionHost.h, adicione estas inclusões na parte superior do arquivo.
    #pragma once
    #include <winrt/Windows.UI.Composition.Desktop.h>
    #include <windows.ui.composition.interop.h>
    #include <DispatcherQueue.h>
    
    • Em CompositionHost.cpp, adicione estes usos na parte superior do arquivo, depois de qualquer inclusão.
    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. Edite a classe para usar o padrão singleton.

    • Em CompositionHost.h, torne o Construtor privado.
    • Declare um método GetInstance público.
    class CompositionHost
    {
    public:
        ~CompositionHost();
        static CompositionHost* GetInstance();
    
    private:
        CompositionHost();
    };
    
    • Em CompositionHost.cpp, adicione a definição do método GetInstance.
    CompositionHost* CompositionHost::GetInstance()
    {
        static CompositionHost instance;
        return &instance;
    }
    
  4. Em CompositionHost.h, declare variáveis de membro privado para o Compositor, DispatcherQueueController e 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. Adicione um método público para inicializar os objetos de interoperabilidade de composição.

    Observação

    Em Inicializar, chame os métodos EnsureDispatcherQueue, CreateDesktopWindowTarget e CreateCompositionRoot. Você criará esses métodos nas próximas etapas.

    • Em CompositionHost.h, declare um método público chamado Initialize que usa um HWND como argumento.
    void Initialize(HWND hwnd);
    
    • Em CompositionHost.cpp, adicione a definição do método Initialize.
    void CompositionHost::Initialize(HWND hwnd)
    {
        EnsureDispatcherQueue();
        if (m_dispatcherQueueController) m_compositor = Compositor();
    
        CreateDesktopWindowTarget(hwnd);
        CreateCompositionRoot();
    }
    
  6. Crie uma fila de dispatcher no thread que usará Windows Composition.

    Um Compositor deve ser criado em um thread que tenha uma fila de dispatcher, para que esse método seja chamado primeiro durante a inicialização.

    • Em CompositionHost.h, declare um método particular chamado EnsureDispatcherQueue.
    void EnsureDispatcherQueue();
    
    • Em CompositionHost.cpp, adicione a definição do 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. Registre a janela do seu aplicativo como um destino de composição.

    • Em CompositionHost.h, declare um método privado chamado CreateDesktopWindowTarget que usa um HWND como argumento.
    void CreateDesktopWindowTarget(HWND window);
    
    • Em CompositionHost.cpp, adicione a definição do 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. Crie um contêiner visual raiz para manter objetos visuais.

    • Em CompositionHost.h, declare um método particular chamado CreateCompositionRoot.
    void CreateCompositionRoot();
    
    • Em CompositionHost.cpp, adicione a definição do 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);
    }
    

Compile o projeto agora para verificar se não há erros.

Esses métodos configuram os componentes necessários para a interoperabilidade entre a camada visual UWP e as APIs do Win32. Agora você pode adicionar conteúdo ao seu aplicativo.

Adicionar elementos de composição

Com a criação da infraestrutura, agora você pode gerar o conteúdo de composição que deseja mostrar.

Para este exemplo, você adiciona um código que cria um quadrado de cor aleatória SpriteVisual com uma animação que faz com que ele seja descartado após um pequeno período.

  1. Adicione um elemento de composição.

    • Em CompositionHost.h, declare um método público chamado AddElement que usa três valores de float como argumentos.
    void AddElement(float size, float x, float y);
    
    • Em CompositionHost.cpp, adicione a definição do 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);
        }
    }
    

Criar e mostrar a janela

Agora, você pode adicionar um botão e o conteúdo de composição UWP à sua interface do usuário do Win32.

  1. No HelloComposition.cpp, na parte superior do arquivo, inclua CompositionHost.h, defina BTN_ADD e obtenha uma instância de CompositionHost.

    #include "CompositionHost.h"
    
    // #define MAX_LOADSTRING 100 // This is already in the file.
    #define BTN_ADD 1000
    
    CompositionHost* compHost = CompositionHost::GetInstance();
    
  2. No método InitInstance, altere o tamanho da janela que é criada. (Nesta linha, altere CW_USEDEFAULT, 0 para 900, 672.)

    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
    
  3. Na função WndProc, adicione case WM_CREATE ao bloco de opção de mensagem. Nesse caso, você inicializa o CompositionHost e cria o botão.

    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. Também na função WndProc, manipule o clique do botão para adicionar um elemento de composição à interface do usuário.

    Adicione case BTN_ADD ao bloco de opção wmId dentro do bloco 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;
    }
    

Agora você pode compilar e executar seu aplicativo. Se necessário, verifique o código completo no final do tutorial para garantir que todo o código esteja nos locais certos.

Ao executar o aplicativo e clicar no botão, você deverá ver quadrados animados adicionados à interface do usuário.

Recursos adicionais

Código completo

Confira o código completo para a classe CompositionHost e o 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 ...
// ...