天天看點

【Win 10 應用開發】UI Composition 劄記(四):繪制圖形

使用 Win 2D 元件,就可以很輕松地繪制各種圖形,哪怕你沒有 D2D 相關基礎,也不必寫很複雜的 C++ 代碼。

先來說說如何擷取 Win 2D 元件。很簡單,建立 UWP 應用項目後,你打開“解決方案資料總管”視窗,然後在【引用】節點上右擊,從快捷菜單中選擇【管理 Nuget 程式包】指令,在打開的視窗中搜尋“Win 2D”,然後安裝帶有 uwp 辨別的那個就可以了。

【Win 10 應用開發】UI Composition 劄記(四):繪制圖形

順便說一下,nuget 的包緩存在你的使用者檔案夾下,就是系統盤下的 \users\xxx,xxx是你登入系統的使用者名,在檔案夾下有個 .nuget 目錄,\packages 子目錄下就是緩存的包,大小取決你安裝的元件,大的時候 4、5 個G也有的。

在你的應用項目中,VS 隻是建立了一個 JSON 檔案來描述你引用的元件,Win 2D 添加引用成功後,你的引用清單應該是這樣的。

【Win 10 應用開發】UI Composition 劄記(四):繪制圖形

你如果看到 Win2D.uwp 這個項目,那就沒問題了。

不過,你得注意,直接輕按兩下它是無法在“對象浏覽器”中檢視的,你可以這樣:打開“對象浏覽器”視窗,然後把浏覽的子集改為“我的解決方案”,這樣,你就能看到你目前項目中所有引用的元件的類型結構了。

【Win 10 應用開發】UI Composition 劄記(四):繪制圖形
 現在,你就能看到 Win2D 庫的基本内容了。
【Win 10 應用開發】UI Composition 劄記(四):繪制圖形

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);
            }
        }      

随便畫幾下,純屬鬼畫符。效果如下圖所示。

【Win 10 應用開發】UI Composition 劄記(四):繪制圖形

但是,我們今天的主題是跟 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();
        }      

好了,看看效果吧。

【Win 10 應用開發】UI Composition 劄記(四):繪制圖形

 OK,今天的内容就說到這裡了。