本文將告訴大家如何從最簡單的控制檯開始搭建,讓 Win2D 和 WinUI 3 關聯起來,讓 Win2D 可以將內容渲染到 WinUI 3 應用上
本文適合想了解 WinUI 3 基礎機制以及 Win2D 與 WinUI 3 協同的方式的夥伴。閱讀本文將可以瞭解到一個簡單的方式,簡單到使用控制檯專案即可進行搭建整個簡單應用
在 上一篇部落格 裡,告訴大家可以如何簡單從控制檯搭建起一個 WinUI 3 應用。本文將在此基礎上告訴大家如何關聯上 Win2D 進行基礎介面繪製
大概製作出來的應用的介面如下圖
上圖裡面的左上角的灰色矩形就是使用 Win2D 繪製出來的內容,中間的文字則是 TextBlock 控制元件所提供的介面內容
本文的重點都在於如何讓 Win2D 繪製出上圖的左上角的灰色矩形
當然了,只要 Win2D 能在上面繪製出灰色矩形,自然也就能繪製出更多有趣的介面內容了
按照 dotnet 的慣例,在開始之前,咱需要安裝 Win2D 的 NuGet 庫。如按照 dotnet WinUI3 Win2D 翻轉圖片 部落格提供的方法,快速編輯 csproj 專案檔案,替換為如下程式碼即可完成初始化部署邏輯
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0-windows10.0.19041</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PlatformTarget>x86</PlatformTarget>
<RuntimeIdentifiers>win10-x86;win10-x64</RuntimeIdentifiers>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseWinUI>true</UseWinUI>
<WindowsPackageType>None</WindowsPackageType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.2.0" />
</ItemGroup>
</Project>
接下來按照 上一篇部落格 提供的方法建立一個簡單 WinUI 3 應用,程式碼如下
public class App : Application
{
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var window = new Window()
{
Title = "控制檯建立應用"
};
window.Content = new Grid()
{
Children =
{
new TextBlock()
{
Text = "控制檯應用",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
}
};
window.Activated += (sender, eventArgs) =>
{
... // 忽略其他程式碼
};
window.Activate();
base.OnLaunched(args);
}
}
internal class Program
{
static void Main(string[] args)
{
global::WinRT.ComWrappersSupport.InitializeComWrappers();
global::Microsoft.UI.Xaml.Application.Start(p =>
{
var app = new App();
_ = app;
});
}
}
將 Win2D 關聯到 WinUI 3 應用,核心的一點就是讓 Win2D 能夠繪製到 WinUI 3 應用的平面上
下面程式碼寫到 Window 的 Activated 事件裡面,更具體來說這裡只是隨便找一個事件,確保視窗等初始化完成之後執行關聯的程式碼而已
先建立或獲取共享的裝置,按照 DirectX 的使用概念裡面,需要先有明確的裝置
var canvasDevice = new CanvasDevice();
// 或
var canvasDevice = CanvasDevice.GetSharedDevice();
接著取出視窗的 Compositor 用於呼叫 Win2D 的 CreateCompositionGraphicsDevice 方法,建立出 CompositionGraphicsDevice 裝置
var compositor = window.Compositor;
CompositionGraphicsDevice compositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice);
此時的 CompositionGraphicsDevice 即可打通 Win2D 和 WinUI 3 之間的渲染關聯
為了使用 Win2D 繪製內容,需要建立出一個平面讓 Win2D 繪製。這裡的平面大家可以理解為畫布,即建立一個畫布讓 Win2D 在上面繪製內容
CompositionDrawingSurface compositionDrawingSurface = compositionGraphicsDevice.CreateDrawingSurface(new Windows.Foundation.Size(200, 200),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
建立畫布的時候,最重要的兩個引數就是畫布的尺寸以及使用的顏色格式。這裡的 B8G8R8A8UIntNormalized 的意思就是顏色格式採用 Blue 藍色 8 個 bit 長度,和 Green 綠色 8 個 bit 長度,和 Red 紅色 8 個 bit 長度,和 Alpha 透明度 8 個 bit 長度。這是十分標準且通用性非常好,且 GPU 友好的顏色畫素格式
最後一個 Premultiplied 引數的意思就是是否進行 Alpha 預乘,這屬於 WinUI 3 渲染層所要求。儘管預乘可能會造成精度丟失問題,但是可以減少後續步驟重複的計算過程。詳細請看 圖片Alpha預乘的作用[轉] - 孤海傲月 - 部落格園
拿到了 CompositionDrawingSurface 之後,即可在此平面上繪製,如以下程式碼
using (CanvasDrawingSession drawingSession =
CanvasComposition.CreateDrawingSession(compositionDrawingSurface))
{
drawingSession.FillRectangle(new Rect(10, 10, 100, 100),
Windows.UI.Color.FromArgb(0xFF, 0x56, 0x56, 0x56));
}
從上面程式碼可以在 CanvasComposition.CreateDrawingSession
返回咱十分熟悉的 CanvasDrawingSession 物件
所有的 Win2D 繪製邏輯都可在 CanvasDrawingSession 的輔助下進行。大家可以將以上的 FillRectangle 方法換成自己的使用 Win2D 繪製複雜的介面的程式碼,如此即可畫出好看的介面內容
完成上述步驟,只是將 Win2D 繪製的內容放在一個平面上,接下來需要將這個平面放入到 WinUI 3 的框架裡面進行顯示
本文選用的方式是走貼圖 Brush 的方式,將 Brush 貼到 SpriteVisual 上,再讓 SpriteVisual 加入到視窗的內容裡面
如此即可使用 SpriteVisual 的貼圖顯示出 Win2D 繪製的內容
實現的邏輯程式碼如下,先將 CompositionDrawingSurface 建立為畫刷作為貼圖
// 在 Win2d 渲染到平面完成之後,將這個平面作為一個畫刷用於在之後的效果
CompositionSurfaceBrush surfaceBrush = compositor.CreateSurfaceBrush(compositionDrawingSurface);
換回 Compositor 建立出 SpriteVisual 且應用畫刷
SpriteVisual visual = compositor.CreateSpriteVisual();
visual.Brush = surfaceBrush;
visual.Size = new Vector2(200, 200);
visual.Offset = new Vector3(20, 20, 0);
上述程式碼核心只是 visual.Brush = surfaceBrush;
但是新增尺寸是必須的,否則介面將看不見 Win2D 的內容。至於 Offset 只是為了控制在哪顯示,可以不寫
再將 SpriteVisual 放到視窗內容的最上方,程式碼如下
Visual elementVisual = ElementCompositionPreview.GetElementVisual(window.Content);
if (elementVisual is ContainerVisual containerVisual)
{
containerVisual.Children.InsertAtTop(visual);
}
如此即完成了邏輯,可以將 Win2D 繪製的內容作為 WinUI 3 介面的一部分,這個過程全從控制檯開始搭建,減少了許多中間的封裝
整個建立 Win2D 和 WinUI 3 關聯的核心程式碼全部如下
window.Activated += (sender, eventArgs) =>
{
var canvasDevice = new CanvasDevice();
var compositor = window.Compositor;
var compositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice);
var compositionDrawingSurface = compositionGraphicsDevice.CreateDrawingSurface(
new Windows.Foundation.Size(200, 200),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
using (CanvasDrawingSession? drawingSession =
CanvasComposition.CreateDrawingSession(compositionDrawingSurface))
{
drawingSession.FillRectangle(new Rect(10, 10, 100, 100),
Windows.UI.Color.FromArgb(0xFF, 0x56, 0x56, 0x56));
}
// 在 Win2d 渲染到平面完成之後,將這個平面作為一個畫刷用於在之後的效果
CompositionSurfaceBrush surfaceBrush = compositor.CreateSurfaceBrush(compositionDrawingSurface);
SpriteVisual visual = compositor.CreateSpriteVisual();
visual.Brush = surfaceBrush;
visual.Size = new Vector2(200, 200);
visual.Offset = new Vector3(20, 20, 0);
Visual elementVisual = ElementCompositionPreview.GetElementVisual(window.Content);
if (elementVisual is ContainerVisual containerVisual)
{
containerVisual.Children.InsertAtTop(visual);
}
};
可以看到關聯的程式碼不多,整體過程如下圖
先建立或獲取共享裝置,透過 CanvasComposition.CreateCompositionGraphicsDevice
混合 Compositor 和 CanvasDevice 建立出 CompositionGraphicsDevice 物件。這個 CompositionGraphicsDevice 物件同樣也是 Win2D 的裝置物件概念,只是加上了 Composition 的能力,可以和 WinUI 3 互動。使用 CompositionGraphicsDevice 建立出 CompositionDrawingSurface 平面。將 CompositionDrawingSurface 作為畫布,呼叫 CanvasComposition.CreateDrawingSession 建立出 CanvasDrawingSession 用於呼叫 Win2D 的繪製邏輯。再將 CompositionDrawingSurface 作為 CompositionSurfaceBrush 畫刷,貼到 SpriteVisual 上。最後將 SpriteVisual 放在視窗內容的最上層
全部的程式碼如下
public class App : Application
{
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var window = new Window()
{
Title = "控制檯建立應用"
};
window.Content = new Grid()
{
Children =
{
new TextBlock()
{
Text = "控制檯應用",
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
}
};
window.Activated += (sender, eventArgs) =>
{
var canvasDevice = new CanvasDevice();
var compositor = window.Compositor;
var compositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice);
var compositionDrawingSurface = compositionGraphicsDevice.CreateDrawingSurface(
new Windows.Foundation.Size(200, 200),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
using (CanvasDrawingSession? drawingSession =
CanvasComposition.CreateDrawingSession(compositionDrawingSurface))
{
drawingSession.FillRectangle(new Rect(10, 10, 100, 100),
Windows.UI.Color.FromArgb(0xFF, 0x56, 0x56, 0x56));
}
// 在 Win2d 渲染到平面完成之後,將這個平面作為一個畫刷用於在之後的效果
CompositionSurfaceBrush surfaceBrush = compositor.CreateSurfaceBrush(compositionDrawingSurface);
SpriteVisual visual = compositor.CreateSpriteVisual();
visual.Brush = surfaceBrush;
visual.Size = new Vector2(200, 200);
visual.Offset = new Vector3(20, 20, 0);
Visual elementVisual = ElementCompositionPreview.GetElementVisual(window.Content);
if (elementVisual is ContainerVisual containerVisual)
{
containerVisual.Children.InsertAtTop(visual);
}
};
window.Activate();
base.OnLaunched(args);
}
}
internal class Program
{
static void Main(string[] args)
{
global::WinRT.ComWrappersSupport.InitializeComWrappers();
global::Microsoft.UI.Xaml.Application.Start(p =>
{
var app = new App();
_ = app;
});
}
}
本文程式碼放在 github 和 gitee 上,可以使用如下命令列拉取程式碼。我整個程式碼倉庫比較龐大,使用以下命令列可以進行部分拉取,拉取速度比較快
先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 9d873f09744d27de84d1877c61fc3e1b0526e4f9
以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令列繼續輸入以下程式碼,將 gitee 源換成 github 源進行拉取程式碼。如果依然拉取不到程式碼,可以發郵件向我要程式碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 9d873f09744d27de84d1877c61fc3e1b0526e4f9
獲取程式碼之後,進入 Workbench/HuremluhuhaChilejelawlai 資料夾,即可獲取到原始碼
更多技術部落格,請參閱 部落格導航