使用 Win 2D 组件,就可以很轻松地绘制各种图形,哪怕你没有 D2D 相关基础,也不必写很复杂的 C++ 代码。
先来说说如何获取 Win 2D 组件。很简单,创建 UWP 应用项目后,你打开“解决方案资源管理器”窗口,然后在【引用】节点上右击,从快捷菜单中选择【管理 Nuget 程序包】命令,在打开的窗口中搜索“Win 2D”,然后安装带有 uwp 标识的那个就可以了。
顺便说一下,nuget 的包缓存在你的用户文件夹下,就是系统盘下的 usersxxx,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,今天的内容就说到这里了。