Uso del livello visivo con Win32
Puoi usare le API di composizione di Windows Runtime (note anche come API livello visivo) nelle app Win32 per creare esperienze moderne disponibili per gli utenti di Windows.
Il codice completo per questa esercitazione è disponibile in GitHub: campione Win32HelloComposition.
Le applicazioni di Windows universale che devono esercitare un controllo accurato sulla composizione dell'interfaccia utente hanno accesso allo spazio dei nomi Windows.UI.Composition per esercitare un controllo granulare sulla composizione e il rendering dell'interfaccia utente. Questa API di composizione non è tuttavia limitata alle app UWP. Le applicazioni desktop Win32 possono sfruttare i moderni sistemi di composizione disponibili su piattaforma UWP e in Windows.
Prerequisiti
L'API di hosting UWP presenta questi prerequisiti.
- Si presuppone che tu abbia familiarità con lo sviluppo di app con Win32 e la piattaforma UWP. Per altre informazioni, vedere:
- Windows 10 versione 1803 o successive
- Windows 10 SDK 17134 o versioni successive
Come usare le API di composizione da un'applicazione desktop Win32
In questa esercitazione creerai una semplice app Win32 C++ e aggiungerai elementi di composizione UWP. Questo argomento è incentrato su come configurare correttamente il progetto, creare il codice di interoperabilità ed eseguire una semplice progettazione tramite le API di composizione di Windows. L'app completata ha un aspetto simile al seguente.
Creare un progetto Win32 C++ in Visual Studio
Il primo passaggio consiste nel creare il progetto di un'app Win32 in Visual Studio.
Per creare un nuovo progetto di un'applicazione Win32 in C++ denominato HelloComposition:
Apri Visual Studio e seleziona File>Nuovo>Progetto.
Verrà visualizzata la finestra di dialogo Nuovo progetto.
Nella categoria Installati espandi il nodo Visual C++ e quindi seleziona Windows Desktop.
Seleziona il modello Applicazione desktop di Windows.
Immetti il nome HelloComposition e quindi fai clic su OK.
Visual Studio creerà il progetto e aprirà l'editor per il file dell'app principale.
Configurare il progetto per l'uso delle API di Windows Runtime
Per usare le API di Windows Runtime (WinRT) in un'app Win32, viene usato C++/WinRT. Devi configurare il progetto di Visual Studio in modo da aggiungere il supporto di C++/WinRT.
Per informazioni dettagliate, vedi Introduzione a C++/WinRT - Modificare un progetto di applicazione desktop di Windows per aggiungere il supporto di C++/WinRT.
Dal menu Progetto apri le proprietà del progetto (Proprietà di HelloComposition) e assicurati che per le impostazioni seguenti siano definiti i valori specificati:
- Per Configurazione seleziona Tutte le configurazioni. Per Piattaforma seleziona Tutte le piattaforme.
- Proprietà di configurazione>Generale>Versione di Windows SDK = 10.0.17763.0 o versioni successive.
- C/C++>Linguaggio>Standard del linguaggio C++ = Standard C++ 17 ISO (/stf:c++17)
- Linker>Input>Dipendenze aggiuntive deve includere "windowsapp.lib". Se questa dipendenza non è inclusa nell'elenco, aggiungila.
Aggiorna l'intestazione precompilata
Cambia il nome di
stdafx.h
estdafx.cpp
rispettivamente inpch.h
epch.cpp
.Imposta la proprietà del progetto C/C++>Intestazioni precompilate>File di intestazione precompilata su pch.h.
Trova e sostituisci
#include "stdafx.h"
con#include "pch.h"
in tutti i file.Modifica>Trova e sostituisci>Cerca nei file
In
pch.h
includiwinrt/base.h
eunknwn.h
.// reference additional headers your program requires here #include <unknwn.h> #include <winrt/base.h>
A questo punto è consigliabile compilare il progetto per assicurarsi che non vi siano errori prima di procedere.
Creare una classe per ospitare elementi di composizione
Per ospitare il contenuto creato con il livello visivo, crea una classe (CompositionHost) per gestire l'interoperabilità e creare elementi di composizione. In questa classe viene eseguita la maggior parte della configurazione per l'hosting delle API di composizione, tra cui:
- il recupero di una classe Compositor, che crea e gestisce gli oggetti nello spazio dei nomi Windows.UI.Composition;
- la creazione di DispatcherQueueController/DispatcherQueue per gestire le attività per le API WinRT;
- la creazione di DesktopWindowTarget e del contenitore di composizione per visualizzare gli oggetti di composizione.
Questa classe è un singleton per evitare problemi relativi al threading. Puoi ad esempio creare una sola coda del dispatcher per thread. La creazione di una seconda istanza di CompositionHost nello stesso thread causerebbe pertanto un errore.
Suggerimento
Se necessario, controlla il codice completo alla fine dell'esercitazione per assicurarti che tutto il codice sia nelle posizioni corrette man mano che procedi con l'esercitazione.
Aggiungere un nuovo file di classe al progetto.
- In Esplora soluzioni fai clic con il pulsante destro del mouse sul progetto HelloComposition.
- Scegli Aggiungi>Classe... dal menu di scelta rapida.
- Nella finestra di dialogo Aggiungi classe assegna il nome CompositionHost.cs alla classe e quindi fai clic su Aggiungi.
Includi intestazioni e istruzioni using necessarie per l'interoperabilità di composizione.
- In CompositionHost.h aggiungi queste direttive include all'inizio del file.
#pragma once #include <winrt/Windows.UI.Composition.Desktop.h> #include <windows.ui.composition.interop.h> #include <DispatcherQueue.h>
- In CompositionHost.cpp aggiungi queste istruzioni using all'inizio del file, dopo le direttive 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;
Modifica la classe in modo che usi il modello singleton.
- In CompositionHost.h rendi privato il costruttore.
- Dichiara un metodo GetInstance statico pubblico.
class CompositionHost { public: ~CompositionHost(); static CompositionHost* GetInstance(); private: CompositionHost(); };
- In CompositionHost.cpp aggiungi la definizione del metodo GetInstance.
CompositionHost* CompositionHost::GetInstance() { static CompositionHost instance; return &instance; }
In CompositionHost.h dichiara le variabili membro private per 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 };
Aggiungi un metodo pubblico per inizializzare gli oggetti di interoperabilità di composizione.
Nota
In Initialize chiama i metodi EnsureDispatcherQueue, CreateDesktopWindowTarget e CreateCompositionRoot. Questi metodi vengono creati nei passaggi successivi.
- In CompositionHost.h dichiara un metodo pubblico denominato Initialize che accetta come argomento un HWND.
void Initialize(HWND hwnd);
- In CompositionHost.cpp aggiungi la definizione del metodo Initialize.
void CompositionHost::Initialize(HWND hwnd) { EnsureDispatcherQueue(); if (m_dispatcherQueueController) m_compositor = Compositor(); CreateDesktopWindowTarget(hwnd); CreateCompositionRoot(); }
Crea una coda del dispatcher sul thread che userà Windows Composition.
È necessario creare un elemento Compositor in un thread che dispone di una coda del dispatcher, in modo che questo metodo venga chiamato per primo durante l'inizializzazione.
- In CompositionHost.h dichiara un metodo privato denominato EnsureDispatcherQueue.
void EnsureDispatcherQueue();
- In CompositionHost.cpp aggiungi la definizione del metodo 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; } }
Registra la finestra dell'app come destinazione della composizione.
- In CompositionHost.h dichiara un metodo privato denominato CreateDesktopWindowTarget che accetta come argomento un HWND.
void CreateDesktopWindowTarget(HWND window);
- In CompositionHost.cpp aggiungi la definizione del metodo 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; }
Crea un contenitore radice di oggetti visivi.
- In CompositionHost.h dichiara un metodo privato denominato CreateCompositionRoot.
void CreateCompositionRoot();
- In CompositionHost.cpp aggiungi la definizione del metodo 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 ora il progetto per assicurarti che non siano presenti errori.
Questi metodi configurano i componenti necessari per l'interoperabilità tra il livello visivo UWP e le API Win32. A questo punto puoi aggiungere contenuto all'app.
Aggiungere elementi di composizione
Dopo aver definito l'infrastruttura, puoi generare il contenuto di composizione che vuoi mostrare.
Per questo esempio, aggiungi il codice che crea un quadrato SpriteVisual colorato in modo casuale con un'animazione che ne provoca l'eliminazione dopo un breve intervallo.
Aggiungi un elemento di composizione.
- In CompositionHost.h dichiara un metodo pubblico denominato AddElement che accetta tre valori di float come argomenti.
void AddElement(float size, float x, float y);
- In CompositionHost.cpp aggiungi la definizione del metodo 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); } }
Creare e visualizzare la finestra
A questo punto puoi aggiungere un pulsante e il contenuto della composizione UWP all'interfaccia utente Win32.
In HelloComposition.cpp, all'inizio del file, includi CompositionHost.h, definisci BTN_ADD e ottieni un'istanza di CompositionHost.
#include "CompositionHost.h" // #define MAX_LOADSTRING 100 // This is already in the file. #define BTN_ADD 1000 CompositionHost* compHost = CompositionHost::GetInstance();
Nel metodo
InitInstance
modifica le dimensioni della finestra creata. In questa riga modifica il valore diCW_USEDEFAULT, 0
impostando900, 672
.HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 900, 672, nullptr, nullptr, hInstance, nullptr);
Nella funzione WndProc aggiungi
case WM_CREATE
al blocco switch message. In questo caso inizializza CompositionHost e crea il pulsante.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;
Nella funzione WndProc gestisci anche il clic del pulsante per aggiungere un elemento di composizione all'interfaccia utente.
Aggiungi
case BTN_ADD
al blocco switch wmId nel blocco 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; }
A questo punto puoi compilare ed eseguire l'app. Se necessario, controlla il codice completo alla fine dell'esercitazione per assicurarti che tutto il codice sia nelle posizioni corrette.
Quando esegui l'app e fai clic sul pulsante, dovresti visualizzare quadrati animati aggiunti all'interfaccia utente.
Risorse aggiuntive
- Esempio HelloComposition per Win32 (GitHub)
- Introduzione a Win32 e C++
- Iniziare a usare le app di Windows (piattaforma UWP)
- Migliorare un'applicazione desktop per Windows (piattaforma UWP)
- Spazio dei nomi Windows.UI.Composition (UWP)
Codice completo
Ecco il codice completo per la classe CompositionHost e il metodo 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 (parziale)
#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 ...
// ...