使用視覺層搭配 Win32
您可以在 Win32 應用程式中使用 Windows 執行階段 Composition API (又稱為視覺層),建立適用於 Windows 使用者的現代化體驗。
您可在 GitHub 取得本教學課程的完整程式碼: Win32 HelloComposition 範本。
需要精確控制其 UI 組合的通用 Windows 應用程式可以存取 Windows.UI.Composition 命名空間,更精細地控制其 UI 的組成與轉譯方式。 不過,此 Composition API 並不限於 UWP 應用程式。 Win32 傳統型應用程式可利用 UWP 和 Windows 中的新式組合系統。
必要條件
UWP 主控 API 具備下列先決條件。
- 假設您對使用 Win32 和 UWP 進行應用程式開發已有一些熟悉。 如需詳細資訊,請參閱:
- Windows 10 版本 1803 或更新版本
- Windows 10 SDK 17134 或更新版本
如何從 Win32 傳統型應用程式使用 Composition API
在本教學課程中,您會建立簡單的 Win32 C++ 應用程式,並在其中新增 UWP 組合 元素。 重點在於正確地設定專案、建立 Interop 程式碼,以及使用 Windows Composition API 繪製簡單的東西。 完成的應用程式看起來像這樣。
在 Visual Studio 中建立 C++ Win32 專案
第一個步驟是在 Visual Studio 中建立 Win32 應用程式專案。
若要使用 C++ 建立名為 HelloComposition 的 Win32 應用程式專案:
開啟 Visual Studio,然後選取 [檔案]>[新增]>[專案]。
[新增專案] 對話方塊隨即開啟。
在 [已安裝] 類別下,展開 [Visual C++] 節點,然後選取 [Windows Desktop]。
選取 [Windows 傳統型應用程式] 範本。
輸入名稱 HelloComposition,然後按一下 [確定]。
Visual Studio 會建立專案,並開啟主要應用程式檔案的編輯器。
將專案設定為使用 Windows 執行階段 API
若要在 Win32 應用程式中使用 Windows 執行階段 (WinRT) API,我們會使用 C++/WinRT。 您必須設定您的 Visual Studio 專案,以新增 C++/WinRT 支援。
(如需詳細資訊,請參閱開始使用 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) 來管理 Interop 並建立組合元素。 您可以在這裡進行用於託管 Composition API 的大部分組態,包括:
- 取得 Compositor,其可在 Windows.UI.Composition 命名空間中會建立和管理物件。
- 建立 DispatcherQueueController/DispatcherQueue 來管理 WinRT API 的工作。
- 建立 DesktopWindowTarget 和組合容器,以顯示組合物件。
我們將此類別設為單一實體,以避免執行緒問題。 例如,您只能為每個執行緒建立一個發送器佇列,因此在相同執行緒上具現化 CompositionHost 的第二個執行個體會造成錯誤。
提示
如有需要,請在本教學課程最後檢查完整的程式碼,以確保當您逐步進行教學課程時,所有程式碼皆位於正確的位置。
將新的類別檔案新增到您的專案。
- 在 [方案總管] 中,以滑鼠右鍵按一下 [HelloComposition] 專案。
- 在內容功能表中,選取 [新增]>[類別...]。
- 在 [新增類別] 對話方塊中,將類別命名為 CompositionHost.cs,然後按一下 [新增]。
包含組合 Interop 所需的標頭和 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 之後,新增下列 usings。
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.h 中,將建構函式設為私人的。
- 宣告公用靜態 GetInstance 方法。
class CompositionHost { public: ~CompositionHost(); static CompositionHost* GetInstance(); private: CompositionHost(); };
- 在 CompositionHost.cpp 中,新增 GetInstance 方法的定義。
CompositionHost* CompositionHost::GetInstance() { static CompositionHost instance; return &instance; }
在 CompositionHost.h 中,宣告 Compositor、DispatcherQueueController 和 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 };
新增公用方法,以初始化組合 Interop 物件。
注意
在 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 視覺層與 Win32 API 之間進行 Interop 所需的元件。 您現在可以將內容新增至您的應用程式。
新增組合元素
基礎結構已就緒之後,即可產生要顯示的 Compositio 內容。
在此範例中,您會新增程式碼來建立隨機彩色方塊 SpriteVisual,其中包含的動畫可使其在短暫延遲之後卸除。
新增 Composition 元素。
- 在 CompositionHost.h 中,宣告名為 AddElement 的公用方法,其採用 3 個 float 值作為引數。
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 UI。
在 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 switch 區塊。 在此情況下,您會初始化 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 函式中,處理 button click 以將組合元素新增至 UI。
將
case BTN_ADD
新增至 WM_COMMAND 區塊內的 wmId switch 區塊。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; }
您現在可以建置及執行您的應用程式。 如有需要,請查看本教學課程結尾的完整程式碼,確定所有程式碼的位置皆正確。
當您執行應用程式,並按一下按鈕時,應該會看見新增到 UI 的動畫方塊。
其他資源
- Win32 HelloComposition 範例 (GitHub)
- 開始使用 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 ...
// ...