【Win 10 应用开发】UI Composition 札记(四):绘制图形

使用 Win 2D 组件,就可以很轻松地绘制各种图形,哪怕你没有 D2D 相关基础,也不必写很复杂的 C++ 代码。

先来说说如何获取 Win 2D 组件。很简单,创建 UWP 应用项目后,你打开“解决方案资源管理器”窗口,然后在【引用】节点上右击,从快捷菜单中选择【管理 Nuget 程序包】命令,在打开的窗口中搜索“Win 2D”,然后安装带有 uwp 标识的那个就可以了。

 

顺便说一下,nuget 的包缓存在你的用户文件夹下,就是系统盘下的 \users\xxx,xxx是你登录系统的用户名,在文件夹下有个 .nuget 目录,\packages 子目录下就是缓存的包,大小取决你安装的组件,大的时候 4、5 个G也有的。

 

在你的应用项目中,VS 只是创建了一个 JSON 文件来描述你引用的组件,Win 2D 添加引用成功后,你的引用列表应该是这样的。

你如果看到 Win2D.uwp 这个项目,那就没问题了。

 

不过,你得注意,直接双击它是无法在“对象浏览器”中查看的,你可以这样:打开“对象浏览器”窗口,然后把浏览的子集改为“我的解决方案”,这样,你就能看到你当前项目中所有引用的组件的类型结构了。

 

 现在,你就能看到 Win2D 库的基本内容了。

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,今天的内容就说到这里了。

 

posted @ 2017-11-09 12:22  东邪独孤  阅读(1306)  评论(1编辑  收藏  举报