Partilhar via


Usando a camada visual com Win32

Você pode usar as APIs de composição do Tempo de Execução do Windows (também chamadas de camada Visual) nos seus aplicativos Win32 para criar experiências modernas que encantam os utilizadores do Windows.

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

Os aplicativos universais do Windows que precisam de controle preciso sobre a composição da interface do usuário têm acesso ao namespace Windows.UI.Composition para exercer um controle refinado sobre como sua interface do usuário é composta e renderizada. No entanto, essa API de composição não está limitada a aplicativos UWP. Os aplicativos de desktop Win32 podem tirar proveito dos modernos sistemas de composição em UWP e Windows.

Pré-requisitos

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

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

Neste tutorial, você cria um aplicativo Win32 C++ simples e adiciona elementos de composição UWP a ele. O foco está em configurar corretamente o projeto, criar o código de interoperabilidade e desenhar algo simples usando APIs de composição do Windows. O aplicativo concluído tem esta aparência.

A interface do usuário do aplicativo em execução

Criar um projeto C++ Win32 no Visual Studio

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

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

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

    Abre-se a caixa de diálogo New Project.

  2. Na categoria Instalado, expanda a opção Visual C++ e selecione Windows Desktop.

  3. Selecione o modelo Aplicativo da Área de Trabalho do Windows.

  4. Introduza o nome HelloCompositione, em seguida, clique em OK.

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

Configurar o projeto para usar APIs do Tempo de Execução do Windows

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

(Para obter detalhes, consulte 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 Project, abra as propriedades do projeto (HelloComposition Properties) e verifique se as seguintes configurações estão definidas para os valores especificados:

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

    Definir a versão do SDK

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

    Definir padrão de idioma

    • vinculador>entrada>dependências adicionais deve incluir "windowsapp.lib". Se não estiver incluído na lista, adicione-o.

    Adicionar dependência de ligador

  2. Atualizar o cabeçalho pré-compilado

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

    • Defina a propriedade do projeto C/C++>cabeçalhos pré-compilados> de arquivo de cabeçalho pré-compilado para pch.h.

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

      (Editar>Localizar e substituir>Localizar em Ficheiros)

      Encontrar e substituir 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 construir o projeto neste momento para garantir que não haja 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. É aqui que você faz a maior parte da configuração para hospedar APIs de composição, incluindo:

Tornamos esta classe um singleton para evitar problemas de threading. Por exemplo, você só pode criar uma fila de dispatcher por thread, portanto, instanciar uma segunda instância de CompositionHost no mesmo thread causaria um erro.

Sugestão

Se você precisar, verifique o código completo no final do tutorial para se certificar de que todo o código está nos lugares certos enquanto você trabalha no tutorial.

  1. Adicione um novo arquivo de classe ao seu projeto.

    • No Explorador 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 de classe, nomeie a classe CompositionHost.cse, em seguida, clique em Adicionar.
  2. Inclua cabeçalhos e usos necessários para a interoperabilidade de composição.

    • Em CompositionHost.h, adicione estes inclui 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 esses usings na parte superior do arquivo, depois que qualquer incluir.
    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 estático público GetInstance.
    class CompositionHost
    {
    public:
        ~CompositionHost();
        static CompositionHost* GetInstance();
    
    private:
        CompositionHost();
    };
    
    • Em CompositionHost.cpp, adicione a definição do GetInstance método.
    CompositionHost* CompositionHost::GetInstance()
    {
        static CompositionHost instance;
        return &instance;
    }
    
  4. Em CompositionHost.h, declare variáveis de membro privado para 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

    No Initialize, você chama os métodos EnsureDispatcherQueue, CreateDesktopWindowTargete CreateCompositionRoot. Você cria 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 Initialize método.
    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á a Composição do Windows.

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

    • Em CompositionHost.h, declare um método privado chamado EnsureDispatcherQueue.
    void EnsureDispatcherQueue();
    
    • Em CompositionHost.cpp, adicione a definição do EnsureDispatcherQueue método.
    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 CreateDesktopWindowTarget método.
    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 armazenar objetos visuais.

    • Em CompositionHost.h, declare um método privado chamado CreateCompositionRoot.
    void CreateCompositionRoot();
    
    • Em CompositionHost.cpp, adicione a definição do CreateCompositionRoot método.
    void CompositionHost::CreateCompositionRoot()
    {
        auto root = m_compositor.CreateContainerVisual();
        root.RelativeSizeAdjustment({ 1.0f, 1.0f });
        root.Offset({ 124, 12, 0 });
        m_target.Root(root);
    }
    

Crie o projeto agora para garantir que não haja 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 infraestrutura instalada, agora você pode gerar o conteúdo de composição que deseja mostrar.

Neste exemplo, adiciona-se código que cria um quadrado colorido aleatoriamente SpriteVisual com uma animação que faz com que ele caia após um pequeno atraso.

  1. Adicione um elemento de composição.

    • Em CompositionHost.h, declare um método público chamado AddElement que recebe 3 float valores como parâmetros.
    void AddElement(float size, float x, float y);
    
    • Em CompositionHost.cpp, adicione a definição do AddElement método.
    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. Em 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 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 à mensagem no bloco de comutação. 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 comutador 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 criar e executar seu aplicativo. Se precisar, verifique o código completo no final do tutorial para se certificar de que todo o código está nos lugares certos.

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

Recursos adicionais

Código completo

Aqui está o código completo para a classe CompositionHost e o método InitInstance.

ComposiçãoHost.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 ...
// ...