使用 Win 2D 元件,就可以很輕松地繪制各種圖形,哪怕你沒有 D2D 相關基礎,也不必寫很複雜的 C++ 代碼。
先來說說如何擷取 Win 2D 元件。很簡單,建立 UWP 應用項目後,你打開“解決方案資料總管”視窗,然後在【引用】節點上右擊,從快捷菜單中選擇【管理 Nuget 程式包】指令,在打開的視窗中搜尋“Win 2D”,然後安裝帶有 uwp 辨別的那個就可以了。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iNzcDMyUzM2cTMtgjMwkDN1QDMxkDMxEzNxAjMtkDOzcjNz8CXxEzNxAjMvwVO4MzN2MzLcd2bsJ2Lc12bj5ycn9Gbi52YucTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
順便說一下,nuget 的包緩存在你的使用者檔案夾下,就是系統盤下的 \users\xxx,xxx是你登入系統的使用者名,在檔案夾下有個 .nuget 目錄,\packages 子目錄下就是緩存的包,大小取決你安裝的元件,大的時候 4、5 個G也有的。
在你的應用項目中,VS 隻是建立了一個 JSON 檔案來描述你引用的元件,Win 2D 添加引用成功後,你的引用清單應該是這樣的。
你如果看到 Win2D.uwp 這個項目,那就沒問題了。
不過,你得注意,直接輕按兩下它是無法在“對象浏覽器”中檢視的,你可以這樣:打開“對象浏覽器”視窗,然後把浏覽的子集改為“我的解決方案”,這樣,你就能看到你目前項目中所有引用的元件的類型結構了。
Microsoft.Graphics.Canvas 以及它的子命名空間都是 Win2D 元件中的類型。
其實,使用 Win2D 元件,你完全可以很簡單地繪制各種玩意兒,因為在 Microsoft.Graphics.Canvas.UI.Xaml 命名空間下,直接就提供了一些控件,你可以直接用到 XAML 文檔中,然後要畫什麼就用代碼畫就行了。比如,我介紹兩個比較典型的。
* CanvasControl —— 可以繪制各種你想要的東東,它就相當于一塊畫布,用代碼繪制時須處理 Draw 事件,然後就在事件處理代碼中随便 draw。
* CanvasAnimatedControl —— 跟上面的那個家夥差不多,隻是它可以在你繪制的内容上産生動畫。
大夥會看到,這兩個控件都有一個 CreateResources 事件,用來幹什麼鳥的呢?它的作用是這樣的,你可以在處理這個事件的代碼中執行個體化一些資源,這些資源一般在繪制過程不會頻繁改動的,比如加載的某個圖檔,某個畫刷等。
下面給大夥簡單示範一下 CanvasControl 控件的用法。
在 XAML 文檔中先要引入命名空間。
<Page
……
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
……
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml">
</Page>
然後就可以用了。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<canvas:CanvasControl Draw="OnDraw"/>
</Grid>
接着處理 Draw 事件,我們畫上幾筆試試手。要畫東東,我們要用到 Microsoft.Graphics.Canvas 命名空間下的另一個類——CanvasDrawingSession,它公開了許多 Draw 和 Fill 方法,Draw 是畫出某個東東,Fill 是填充一個區域。
private void OnDraw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
using (CanvasDrawingSession dss = args.DrawingSession)
{
// 畫線
dss.DrawLine(12f, 15f, 335f, 408f, Colors.DarkBlue, 5.5f);
// 畫矩形
dss.DrawRectangle(55f, 190f, 465f, 369f, Colors.Gold, 8f);
// 畫圓
dss.DrawEllipse(350f, 350f, 200f, 260f, Colors.Orange, 5f);
}
}
随便畫幾下,純屬鬼畫符。效果如下圖所示。
但是,我們今天的主題是跟 UI Composition 有關的,雖然上述繪圖法很超逸,卻不是咱們今天的主題。我們下面要做的,是使用 Composition API 來呈現自己繪制的内容。
大夥伴們可以思考一下,要在 Composition 組裝的對象上畫東西,需要什麼?前面咱們說過,你得有個 Brush,在 Composition API 中,隻有一個畫刷可以呈現自己繪制的内容,那就是 CompositionSurfaceBrush。
要建立 CompositionSurfaceBrush 執行個體容易,調用一下 Compositor.CreateSurfaceBrush 方法就行了。但問題的核心不在此,而在于,CompositionSurfaceBrush 畫刷建立後是空的,要能夠呈現内容,還得需要給 Surface 屬性賦個值,它是一個實作了 ICompositionSurface 接口的類型,在 Composition API 中,實作了該接口的隻有一個類:CompositionDrawingSurface。然而,你看到了,這個類是沒有構造函數公開的,它是由 CompositionGraphicsDevice 類的 CreateDrawingSurface 方法建立的。
好,現在我們可以理一下思路了。
1、你必須想方設法得到一個 CompositionGraphicsDevice 執行個體,可該類沒公開的構造函數,咋辦?是以才要使用 Win2D,稍後再說,Win2D 會有辦法獲得這個執行個體的。
2、調用 CompositionGraphicsDevice 執行個體的 CreateDrawingSurface 或 CreateDrawingSurface2 方法建立 CompositionDrawingSurface
執行個體。
3、在新建立的 CompositionDrawingSurface 執行個體上畫東西。這個也要用到 Win2D。
4、使用 Compositor 的靜态方法直接建立 CompositionSurfaceBrush 對象,并與上面畫好的 CompositionDrawingSurface 關聯。
5、把這個畫刷(CompositionSurfaceBrush)與可視化元素關聯,比如,SpriteVisual類就有一個 Brush 屬性。
如此一來,我們找到了兩個難點:a、如何建立 CompositionGraphicsDevice ; b、如何在 Surface 上畫東西(此 Surface 非彼 Surface)。
借助 Win2D,可以解決以上兩個難題。我們不講理論的,下面用執行個體來說明。
首先,在 XAML 文檔中随便聲明一個元素,隻要是 UIElement 的子類就行。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Canvas Name="myCvs"/>
</Grid>
記得配置設定一個名字,待會在代碼中要通路。
随後,我們就可以開始作畫了。
void DrawSomething()
{
// 擷取 XAML 元素上的 Visual
Visual canvasVisual = ElementCompositionPreview.GetElementVisual(myCvs);
// 擷取 Compositor 對象
Compositor compos = canvasVisual.Compositor;
// 建立 GraphicsDevice
CompositionGraphicsDevice graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compos, CanvasDevice.GetSharedDevice());
// 建立 Surface
// 畫布的大小
SizeInt32 cvsize = new SizeInt32
{
Width = 600,
Height = 400
};
CompositionDrawingSurface surface = graphicsDevice.CreateDrawingSurface2(cvsize, Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied);
// 開始繪畫
using (CanvasDrawingSession dss = CanvasComposition.CreateDrawingSession(surface))
{
// 刷牆,把牆面刷成黑色
dss.Clear(Colors.Black);
// 畫一個圓
dss.DrawEllipse(210f, 190f, 80f, 80f, Colors.Yellow, 4f);
// 再畫一個橢圓
dss.DrawEllipse(230f, 200f, 160f, 95f, Colors.SkyBlue, 3.5f);
// 填充一塊區域
dss.FillRectangle(400f, 250f, 150f, 100f, Colors.Pink);
}
// 建立 surface 畫刷
CompositionSurfaceBrush sfbrush = compos.CreateSurfaceBrush(surface);
// 建立一個支援畫刷的可視化對象
SpriteVisual newVisual = compos.CreateSpriteVisual();
// 設定畫刷
newVisual.Brush = sfbrush;
// 注意要設定可視化對象的大小
ExpressionAnimation anim = compos.CreateExpressionAnimation();
anim.Expression = "parn.Size";
anim.SetReferenceParameter("parn", canvasVisual);
newVisual.StartAnimation("Size", anim);
// 最後别忘了把新的可視化對象插入對象樹
ElementCompositionPreview.SetElementChildVisual(myCvs, newVisual);
}
我們上篇中講過的,與 XAML 互動,使用 ElementCompositionPreview輔助類可以獲得與 XAML 元素對應的 Visual 執行個體,這樣我們也能得到相關的 Compositor 對象了。
注意建立 CompositionGraphicsDevice 執行個體要借助 Win2D 的 CanvasComposition 類(位于 Microsoft.Graphics.Canvas.UI.Composition 命名空間),在調用 CreateCompositionGraphicsDevice 方法時,你除了得提供關聯的 Compositor 對象外,還得有一個相容的 CanvasDevice 對象。這個 CanvasDevice 對象有個很TMD簡單的擷取方法,就是直接調用它的 GetSharedDevice 靜态方法。
好了,有了 CompositionGraphicsDevice 執行個體,那建立 CompositionDrawingSurface對象就好辦了,就像這樣。
CompositionDrawingSurface surface = graphicsDevice.CreateDrawingSurface2(cvsize, Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied);
CreateDrawingSurface 和 CreateDrawingSurface2 都可以,帶“2”的是使用整數來表示像素值。
調用這個方法最麻煩的後面兩個參數,它們都是枚舉值,如果值設定不當會發生異常,是以,如果出錯了,你就得調整了。一般來說,在螢幕上顯示的東東,我們會選擇 BGRA 的順序,因為這個順序呈現出來不會偏色,尤其是圖像,如果用 RGBA 就會偏色。B8G8R8A8 表示都用8位的值,也就是一個位元組,這個我們平時處理一般圖形也夠用了(即32位顔色)。
接下來就是用 CanvasDrawingSession 來畫你想畫的各種玩意兒,畫完後要建立一個 CompositionSurfaceBrush 對象,并且要與剛剛畫好的 surface 對象關聯。
最後用這個畫刷填充一個支援畫刷的可視化對象即可,如 SpriteVisual。記住,一定要設定對象的 Size 屬性,因為預設值是0,不然它不會顯示出來的,這裡呢,我直接用一個表達式動畫,讓它的大小跟随 Canvas 的大小。
這個方法封裝好後,可以在适當的地方調用,以繪制内容,比如頁面類的構造函數中。
public MainPage()
{
this.InitializeComponent();
DrawSomething();
}
好了,看看效果吧。
OK,今天的内容就說到這裡了。