Использование визуального уровня на платформе Win32
Вы можете использовать API Composition среды выполнения Windows (также называемые визуальным уровнем) в приложениях Win32, чтобы создавать современные интерфейсы для пользователей Windows.
Полный код для этого руководства доступен на сайте GitHub: пример Win32 HelloComposition.
Приложения универсальной платформы Windows, которым необходим точный контроль над композицией пользовательского интерфейса, имеют доступ к пространству имен Windows.UI.Composition, которое позволяет точно контролировать композицию и отрисовку пользовательского интерфейса. Однако этот API Composition не ограничивается только приложениями UWP. Классические приложения Win32 могут использовать современные системы компоновки в UWP и Windows.
Необходимые компоненты
Для использования API размещения для UWP накладываются приведенные ниже предварительные требования.
- Предполагается, что у вас есть опыт разработки приложений с помощью Win32 и UWP. Дополнительные сведения см. в следующем разделе:
- Get Started with Win32 and C++ (Приступая к работе с Win32 и C++)
- Начало работы с приложениями Windows
- Улучшение классического приложения для Windows
- Windows 10 версии 1803 или более поздней версии.
- Пакет SDK для Windows 10 версии 17134 или более поздней версии.
Как использовать интерфейсы API Composition из классического приложения Win32
При работе с этим руководством вы создадите простое приложение Win32 на C++ и добавите в него элементы композиции UWP. Основное внимание уделяется правильной настройке проекта, созданию кода взаимодействия и отрисовке простых элементов с помощью интерфейсов API Windows Composition. Завершенное приложение выглядит следующим образом.
Создание проекта C++ для Win32 в Visual Studio
Первым шагом является создание проекта приложения Win32 в Visual Studio.
Чтобы создать проект приложения Win32 на C++ с именем HelloComposition, сделайте следующее.
Откройте Visual Studio и выберите Файл>Создать>Проект.
Появится диалоговое окно Создать проект.
В категории Установлены разверните узел Visual C++, а затем выберите Windows Desktop.
Выберите шаблон Классическое приложение Windows.
Введите имя HelloComposition, а затем нажмите кнопку ОК.
Visual Studio создаст проект и откроет редактор для основного файла приложения.
Настройка проекта для использования интерфейсов API среды выполнения Windows
Чтобы применить интерфейсы API среды выполнения Windows (WinRT) в приложении Win32, мы используем C++/WinRT. Чтобы добавить поддержку C++/WinRT, необходимо настроить проект Visual Studio.
(Дополнительные сведения см. в разделе Добавление поддержки C++/WinRT в проект классического приложения для Windows статьи "Начало работы с C++/WinRT".)
В меню Проект откройте свойства проекта (Свойства HelloComposition) и убедитесь, что для следующих параметров заданы указанные значения.
- Для параметра Конфигурации выберите Все конфигурации. Для параметра Платформа выберите Все платформы.
- Свойства конфигурации>Общие>Версия Windows SDK = 10.0.17763.0 или более поздняя версия.
- C/C++>Язык>Стандарт языка C++ = Стандарт ISO C++ 17 (/stf:c++17)
- Компоновщик>Ввод>Дополнительные зависимости — этот раздел должен содержать windowsapp.lib. Если эта зависимость отсутствует в списке, добавьте ее.
Изменение предварительно скомпилированного заголовка
Переименуйте
stdafx.h
иstdafx.cpp
вpch.h
иpch.cpp
соответственно.Установите для свойства проекта C/C++>Предварительно скомпилированные заголовки>Предварительно скомпилированный заголовочный файл значение pch.h.
Найдите
#include "stdafx.h"
и замените его#include "pch.h"
во всех файлах.(Изменить>Найти и заменить>Найти в файлах.)
В
pch.h
добавьтеwinrt/base.h
иunknwn.h
.// reference additional headers your program requires here #include <unknwn.h> #include <winrt/base.h>
На этом этапе рекомендуется выполнить сборку проекта, чтобы убедиться в отсутствии ошибок, прежде чем продолжить.
Создание класса для размещения элементов композиции
Чтобы разместить содержимое, созданное с помощью визуального уровня, создайте класс (CompositionHost) для управления взаимодействием и создания элементов композиции. Это потребует выполнить большую часть настройки для размещения интерфейсов API Composition. Потребуется:
- получить Compositor, который создает объекты и управляет ими в пространстве имен Windows.UI.Composition;
- создать DispatcherQueueController/DispatcherQueue для управления задачами интерфейсов API WinRT;
- создать DesktopWindowTarget и контейнера Composition для отображения объектов композиции.
Мы сделаем этот класс singleton, чтобы избежать проблем с потоками. Например, можно создать только одну очередь диспетчера для каждого потока, поэтому создание второго экземпляра CompositionHost в том же потоке вызовет ошибку.
Совет
При необходимости сверьтесь с полным кодом в конце руководства, чтобы убедиться в том, что весь код добавлен в соответствующе места при работе с этим руководством.
Добавьте новый файл класса в проект.
- В обозревателе решений щелкните проект HelloComposition правой кнопкой мыши.
- В контекстном меню выберите Добавить>Класс.
- В диалоговом окне Добавление класса укажите имя класса, CompositionHost.cs, а затем щелкните Добавить.
Добавьте заголовки и операторы using, необходимые для взаимодействия композиции.
- В начало файла CompositionHost.h добавьте приведенные ниже операторы include.
#pragma once #include <winrt/Windows.UI.Composition.Desktop.h> #include <windows.ui.composition.interop.h> #include <DispatcherQueue.h>
- В начале файла CompositionHost.cpp после каждого оператора include добавьте оператор using.
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;
Измените класс, чтобы использовать шаблон singleton.
- В CompositionHost.h сделайте конструктор частным.
- Объявите общедоступный статический метод GetInstance.
class CompositionHost { public: ~CompositionHost(); static CompositionHost* GetInstance(); private: CompositionHost(); };
- В CompositionHost.cpp добавьте определение метода GetInstance.
CompositionHost* CompositionHost::GetInstance() { static CompositionHost instance; return &instance; }
В CompositionHost.h объявите частные переменные-члены DispatcherQueueController и DesktopWindowTarget для Compositor.
winrt::Windows::UI::Composition::Compositor m_compositor{ nullptr }; winrt::Windows::System::DispatcherQueueController m_dispatcherQueueController{ nullptr }; winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget m_target{ nullptr };
Добавьте общедоступный метод для инициализации объектов взаимодействия композиции.
Примечание.
В методе Initialize вызываются методы EnsureDispatcherQueue, CreateDesktopWindowTarget и CreateCompositionRoot. Эти методы создаются на следующих шагах.
- В CompositionHost.h объявите общедоступный метод Initialize, который принимает HWND в качестве аргумента.
void Initialize(HWND hwnd);
- В CompositionHost.cpp добавьте определение метода Initialize.
void CompositionHost::Initialize(HWND hwnd) { EnsureDispatcherQueue(); if (m_dispatcherQueueController) m_compositor = Compositor(); CreateDesktopWindowTarget(hwnd); CreateCompositionRoot(); }
Создайте очередь диспетчера в потоке, который будет использовать Windows Composition.
Необходимо создать Compositor в потоке с очередью диспетчера, поэтому этот метод вызывается первым во время инициализации.
- В CompositionHost.h объявите частный метод EnsureDispatcherQueue.
void EnsureDispatcherQueue();
- В CompositionHost.cpp добавьте определение метода 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; } }
Зарегистрируйте окно приложения в качестве цели композиции.
- В CompositionHost.h объявите частный метод CreateDesktopWindowTarget, который принимает HWND в качестве аргумента.
void CreateDesktopWindowTarget(HWND window);
- В CompositionHost.cpp добавьте определение метода 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; }
Создайте корневой контейнер для размещения визуальных объектов.
- В CompositionHost.h объявите частный метод CreateCompositionRoot.
void CreateCompositionRoot();
- В CompositionHost.cpp добавьте определение метода CreateCompositionRoot.
void CompositionHost::CreateCompositionRoot() { auto root = m_compositor.CreateContainerVisual(); root.RelativeSizeAdjustment({ 1.0f, 1.0f }); root.Offset({ 124, 12, 0 }); m_target.Root(root); }
Выполните сборку проекта, чтобы убедиться в отсутствии ошибок.
Эти методы настраивают компоненты, необходимые для взаимодействия между визуальным уровнем UWP и интерфейсами API Win32. Теперь вы можете добавить содержимое в приложение.
Добавление элементов композиции
Теперь, когда инфраструктура настроена, можно создать содержимое Composition, которое необходимо отобразить.
В этом примере вы добавите код, создающий квадрат SpriteVisual произвольного цвета с анимацией, которая удаляет его после небольшой задержки.
Добавьте элемент композиции.
- В CompositionHost.h объявите общедоступный метод AddElement, который принимает 3 значения с плавающей запятой в качестве аргументов.
void AddElement(float size, float x, float y);
- В CompositionHost.cpp добавьте определение метода 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); } }
Создание и отображение окна
Теперь можно добавить кнопку и содержимое композиции UWP в пользовательский интерфейс Win32.
В начало файла HelloComposition.cpp добавьте CompositionHost.h, определите BTN_ADD и получите экземпляр CompositionHost.
#include "CompositionHost.h" // #define MAX_LOADSTRING 100 // This is already in the file. #define BTN_ADD 1000 CompositionHost* compHost = CompositionHost::GetInstance();
В методе
InitInstance
измените размер создаваемого окна. (В этой строке изменитеCW_USEDEFAULT, 0
на900, 672
.)HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
В функции WndProc добавьте
case WM_CREATE
в блок параметров message. В этом случае инициализируется CompositionHost и создается кнопка.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;
Кроме того, в функции WndProc обработайте нажатие кнопки, чтобы добавить элемент композиции в пользовательский интерфейс.
Добавьте
case BTN_ADD
в блок параметров wmId в блоке 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; }
Выполните сборку и запустите приложение. При необходимости сверьтесь с полным кодом в конце руководства, чтобы убедиться в том, что весь код добавлен в соответствующе места.
Если запустить приложение и нажать кнопку, в пользовательском интерфейсе должны отобразиться анимированные квадраты.
Дополнительные ресурсы
- Пример приложения Win32 HelloComposition (GitHub)
- Get Started with Win32 and C++ (Приступая к работе с Win32 и C++)
- Начало работы с приложениями для Windows (UWP)
- Улучшение классического приложения для Windows (UWP)
- Пространство имен Windows.UI.Composition (UWP)
Полный код
Ниже приведен полный код класса CompositionHost и метода 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 (неполный)
#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 ...
// ...
Windows developer