概述

Visual类是所有显示元素的基类,提供了绘图、转换、边框计算、裁剪和命中测试。但不支持事件处理、布局、样式、数据绑定和全球化。Visual3D是所有3D可视元素的基类。

DrawingVisual继承自ContainerVisual是一个轻量绘图类,不提供事件和布局,适用于创建背景和剪贴画,使用DrawingContext绘制,最后使用AddVisualChild方法添加进VisualTree。

Drawing对象 描述
GeometryDrawing 绘制形状
ImageDrawing 绘制图像
GlyphRunDrawing 绘制文本
VideoDrawing 播放音频或视频
DrawingGroup 把以上绘制合并到单个复合图层

坐标系

2D坐标系:左上角为原点 (0,0),右方为+x,下方为+y

3D坐标系(右手):屏幕中心为原点(0,0,0),右方为+x,下方为-y,前方为-z

画笔

画笔类型

画笔 说明 支持 TileMode (平铺) 支持 Freeze (只读)
SolidColorBrush 纯色:用颜色填充区域
LinearGradientBrush 线性渐变:使用GradientStop指定渐变的颜色和位置
RadialGradientBrush 径向渐变:同上
ImageBrush 图像:使用Stretch属性指定缩放模式
DrawingBrush 图形:GeometryDrawingImageDrawingGlyphRunDrawingVideoDrawingDrawingGroup(复合其他图形),可作为DrawingImage的源供Image使用
VisualBrush 继承自Visual的控件都可以绘制,也可以把Visual属性绑定为其他命名的控件,复制绘图 Visual属性不是null时,不可冻结

画笔性能从高到底

尽量使用ImageBrush代替VisualBrushDrawingBrush,在2D中可以同通过RenderTargetBitmap可以将控件转换成Image,然后再使用ImageBrush

SolidColorBrush > LinearGradientBrush > ImageBrush > DrawingBrush (缓存) > VisualBrush(缓存) > RadialGradientBrush > DrawingBrush(未缓存) > VisualBrush(未缓存)

画笔的Transform和RelativeTransform的区别

应用流程:

确定显示的区域(Viewbox) --> 图像输出投影到1x1的转换矩形上 --> 应用RelativeTransform --> 绘制转换后的输出投影到实际大小的区域 --> 应用Transform -->最终显示

  • RelativeTransform 是在投影到1x1时应用转换的,所以应用转换的位置参数都是可以看作是位置的比例,比如中心位置就是0.5。

  • Transform是在投影到实际区域大小后应用的,所以它的转换参数都是实际的位置。

FrameworkElement的LayoutTransform和RenderTransform

  • LayoutTransform是在处理布局之前转换的,所以本元素转换后会改变本身的位置大小,会引起父元素的重新布局,父元素计算布局时会以当前元素转换后的大小计算。因为会影响父元素的重新布局所以对性能有影响。
  • RenderTransform是在布局完成后应用的,不会影响父元素的布局

缩略图

使用BitmapImage.DecodePixelWidth 设置加载图片的缩略图大小以节省内存

提升元素的呈现性能

一个比较复杂元素的含有很多子元素时,使用BitmapCache分配给复杂元素的CacheMode属性。

如果元素要重复给其他元素使用则使用BitmapCacheBrush作为资源,分配这个元素给Target属性,然后配置BitmapCacheBrushBitmapCache`的属性。

命中测试

默认命中测试始终返回z顺序中最顶层的对象。

自定义命中测试:

  • 重写 HitTestCore 方法,当需要命中测试(例如鼠标点击时)和调用VisualTreeHelper.HitTest会调用此方法。可以只重写当前边框区域的命中测试,然后使用base.HitTestCore使用默认方法测试当前呈现内容。
  • VisualTreeHelper.HitTest中使用回调方法HitTestFilterCallbackHitTestResultCallback,适当的返回结构已达到自己的目的。

3D

一个3D场景的示例

xaml
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <DockPanel> <Viewbox> <Canvas Width="321" Height="201"> <!-- The Viewport3D provides a rendering surface for 3-D visual content. --> <Viewport3D ClipToBounds="True" Width="150" Height="150" Canvas.Left="0" Canvas.Top="10"> <!-- Defines the camera used to view the 3D object. --> <Viewport3D.Camera> <PerspectiveCamera Position="0,0,2" LookDirection="0,0,-1" FieldOfView="60" /> </Viewport3D.Camera> <!-- The ModelVisual3D children contain the 3D models --> <Viewport3D.Children> <!-- This ModelVisual3D defines the light cast in the scene. Without light, the 3D object cannot be seen. Also, the direction of the lights affect shadowing. If desired, you can create multiple lights with different colors that shine from different directions. --> <ModelVisual3D> <ModelVisual3D.Content> <DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" /> </ModelVisual3D.Content> </ModelVisual3D> <ModelVisual3D> <ModelVisual3D.Content> <GeometryModel3D> <!-- The geometry specifies the shape of the 3D plane. In this sample, a flat sheet is created. --> <GeometryModel3D.Geometry> <MeshGeometry3D TriangleIndices="0,1,2 3,4,5 " Normals="0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 " TextureCoordinates="0,0 1,0 1,1 1,1 0,1 0,0 " Positions="-0.5,-0.5,0.5 0.5,-0.5,0.5 0.5,0.5,0.5 0.5,0.5,0.5 -0.5,0.5,0.5 -0.5,-0.5,0.5 " /> </GeometryModel3D.Geometry> <!-- The material specifies the material applied to the 3D object. In this sample a linear gradient covers the surface of the 3D object.--> <GeometryModel3D.Material> <MaterialGroup> <DiffuseMaterial> <DiffuseMaterial.Brush> <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5"> <LinearGradientBrush.GradientStops> <GradientStop Color="Yellow" Offset="0" /> <GradientStop Color="Red" Offset="0.25" /> <GradientStop Color="Blue" Offset="0.75" /> <GradientStop Color="LimeGreen" Offset="1" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </DiffuseMaterial.Brush> </DiffuseMaterial> </MaterialGroup> </GeometryModel3D.Material> <!-- Apply a transform to the object. In this sample, a rotation transform is applied, rendering the 3D object rotated. --> <GeometryModel3D.Transform> <RotateTransform3D> <RotateTransform3D.Rotation> <AxisAngleRotation3D Axis="0,3,0" Angle="40" /> </RotateTransform3D.Rotation> </RotateTransform3D> </GeometryModel3D.Transform> </GeometryModel3D> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D.Children> </Viewport3D> </Canvas> </Viewbox> </DockPanel> </Page>

3D性能优化

  • Viewport3D.ClipToBounds: 如果不需要裁剪以适应父元素大小,则设置为False
  • Viewport3D.IsHitTestVisible: 如果不需要响应鼠标位置检测,则设置为False
  • GeometryModel3D:在需要材质或者转换的时候,尽可能少创建新实例。如何多个使用相同的材质和变换,则合并到一个GeometryModel3DMeshGeometry3D中。
  • MeshGeometry3D:在使用动画更改顶点时从Visual Tree中移除(detach),更改后再添加(reattach)进去。
  • 抗锯齿:如果不需要,设置Viewport3D.EdgeMode=Aliased。默认4x抗锯齿。
  • 文字:如果文字不改变的化,使用RenderTargetBitmap渲染文字,而不是使用DrawingBrush或者VisualBrush
  • TileBrush画笔:如果在3D场景使用DrawingBrush或者VisualBrush,设置CachingHint=Cache,设置CacheInvalidationThresholdMinimumCacheInvalidationThresholdMaximum使得画笔不会频繁重新生成。
  • BitmapEffect:尽量不要使用,因为不能硬件加速。
  • ImageBrush:当使用ImageBrushRenderTargetBitmap时,图像应尽可能的小,但是会降低图像质量,自己把握。
  • Opactity:在3d模型中使用透明度,尽量使用在画笔或材质上(OpacityColor属性),不要使用Viewport3D.Opacity
  • Viewport3D:尽量合并模型在同一个Viewport3D对象,不要创建很多分散开的对象。
  • Freeezable:对于不需要改变的笔刷、材质和其他实现Freezable的对象,调用Freeze方法冻结,可以节省时间和空间。
  • BackMaterial:背面材质,除非必要,不要使用。
  • 灯光性能:AmbientLight环境光 > DirectionalLight日光 > PointLight点光 > SpotLight聚光灯。不要使用很多灯光,日光最多支持110,点光最多支持70,聚光最多支持40个,纯黑色的灯光没有用。
  • MeshGeometry3D:Positions 最多20001个Point3D实例,TriangleIndices 最多 60003个 Int32实例
  • 材质性能:EmissiveMaterial(自发光) > DiffuseMaterial(漫反射) > SpecularMaterial(镜面)
  • MaterialGroup:尽量减少当中的材质数量。
  • Transform3DGroup:如果不需要动画和数据绑定,使用MatrixTransform3D代替。
  • ModelVisual3D:运动的物体或共享的GeometryModel3D模型放入ModelVisual3D中。GeometryModel3D表示模型,ModelVisual3D表示场景。

动画

From/To/By 动画

From:起始值,To:到达值,By:偏移值(可以单独使用或结合From使用)。

To和By结合使用时,忽略By。

属性类型 对应的 From/To/By 动画类
Byte ByteAnimation
Color ColorAnimation
Decimal DecimalAnimation
Double DoubleAnimation
Int16 Int16Animation
Int32 Int32Animation
Int64 Int64Animation
Point PointAnimation
Quaternion QuaternionAnimation
Rect RectAnimation
Rotation3D Rotation3DAnimation
Single SingleAnimation
Size SizeAnimation
Thickness ThicknessAnimation
Vector3D Vector3DAnimation
Vector VectorAnimation

关键帧动画

  • Linear(线性):在指定时间内,线性速度增加或减少值。

  • Discrete(离散):指定时间后,立即到设定值。

  • Spline(曲线):指定时间内,按指定的曲线控制(KeySpline)设置值,贝塞尔曲线,从(0,0)到(1,1)。

以上在一个关键帧动画中可以组合使用。

关键帧:

  • 支持百分比 (例如:80%)作为KeyTime的值,表示该帧停止的时间。

  • 支持特殊值Uniform作为KeyTime的值,会平分整个动画的持续时间

  • 支持特殊值Paced作为KeyTime的值,根据关键帧长度分配可用时间,会保持整个动画的变化速度。

属性类型 对应的 From/To/By 动画类 支持的内插方法
Boolean BooleanAnimationUsingKeyFrames 离散
Byte ByteAnimationUsingKeyFrames 离散、线性、曲线
Color ColorAnimationUsingKeyFrames 离散、线性、曲线
Decimal DecimalAnimationUsingKeyFrames 离散、线性、曲线
Double DoubleAnimationUsingKeyFrames 离散、线性、曲线
Int16 Int16AnimationUsingKeyFrames 离散、线性、曲线
Int32 Int32AnimationUsingKeyFrames 离散、线性、曲线
Int64 Int64AnimationUsingKeyFrames 离散、线性、曲线
Matrix MatrixAnimationUsingKeyFrames 离散
Object ObjectAnimationUsingKeyFrames 离散
Point PointAnimationUsingKeyFrames 离散、线性、曲线
Quaternion QuaternionAnimationUsingKeyFrames 离散、线性、曲线
Rect RectAnimationUsingKeyFrames 离散、线性、曲线
Rotation3D Rotation3DAnimationUsingKeyFrames 离散、线性、曲线
Single SingleAnimationUsingKeyFrames 离散、线性、曲线
String StringAnimationUsingKeyFrames 离散
Size SizeAnimationUsingKeyFrames 离散、线性、曲线
Thickness ThicknessAnimationUsingKeyFrames 离散、线性、曲线
Vector3D Vector3DAnimationUsingKeyFrames 离散、线性、曲线
Vector VectorAnimationUsingKeyFrames 离散、线性、曲线

路径动画

通过PathGeometry属性提供路径输入

属性类型 相应的路径动画类 示例
Double DoubleAnimationUsingPath 沿着路径针对对象进行动画处理(双精度动画)
Matrix MatrixAnimationUsingPath 沿着路径针对对象进行动画处理(矩阵动画)
Point PointAnimationUsingPath 沿着路径针对对象进行动画处理(点动画)

自定义动画

  1. 自定义关键帧:继承一个关键帧对象,比如DoubleKeyFrame
  2. 自定义动画:继承AnimationTimeline或者*AnimationBase(例如:DoubleAnimationBase·)。
  3. 使用Per-Frame Callback:注册CompositionTarget.Rendering事件,获取当前帧的时间,设置要处理的属性,完全绕过WPF的动画系统。

缓动函数

可以继承EasingFunctionBase 自定义缓动函数

使用:设置动画(例如DoubleAnimation)的EasingFunction属性

预定义的缓动:

  • BackEase:先退动画运动,然后再开始在指示的路径中进行动画处理。
  • BounceEase:创建弹跳效果。
  • CircleEase:使用循环函数创建加速和/或减速的动画。
  • CubicEase:使用公式 f (t) = t3创建加速和/或减速的动画。
  • ElasticEase:创建类似于弹簧前后振荡的动画,直到其进入 rest 状态。
  • ExponentialEase:使用指数公式创建加速和/或减速的动画。
  • PowerEase:使用公式 f (t) = tp 创建加速和/或减速的动画,其中 p 等于 Power 属性。
  • QuadraticEase:使用公式 f (t) = t2创建加速和/或减速的动画。
  • QuarticEase:使用公式 f (t) = t4创建加速和/或减速的动画。
  • QuinticEase:使用公式 f (t) = t5创建加速和/或减速的动画。
  • SineEase:使用正弦公式创建加速和/或减速的动画。

IsAdditive

IsAdditive=true 时 动画From的值会累积到当前属性。

比如当前属性Width=50,动画中From=100 To=200

  • IsAdditive=true:动画播放时Width=50+100=150,在等于250时结束。
  • IsAdditive=false:动画播放时width=100,在等于200时结束。

IsCumulative

在多次重复的动画中,累计属性值,每次加上To-From的值,所以下一次的From=本次的To

例如在RepeatBehavior="2x"From="10",To="15",动画播放两次

  • IsCumulative=true:第一次From="10",To="15",第二次From="15",To="20"
  • IsCumulative=false:两次都是From="10",To="15"

其他

在对属性进行动画处理后,有些时候,即使动画结束也无法更改该属性的值。这是因为,动画仍然在更改这个属性的值,可以删除动画或者指定FillBehavior=Stop

时间线类型 时钟类型 时钟用途
Animation (继承自 AnimationTimeline) AnimationClock 为依赖属性生成输出值。
MediaTimeline MediaClock 处理媒体文件。
ParallelTimeline ClockGroup 分组和控制其子 Clock 对象
Storyboard ClockGroup 分组和控制其子 Clock 对象

AnimationTimeline表示时间线,每一个时间线都会创建一个AnimationClock对象。属性中描述动画的输出值、持续时间、开始时间、结束时间和其他动画信息。

AnimationClock处理这些值,维护计时相关的状态,Clock 由TimeManager管理,每次时间更新都会去更新所有在线的Clock中的时间,然后Clock在TimeLine中获取当前动画应该的值,在使用多个Clock更改同一个属性时,Clock是从上一个Clock获得的值(需要应用HandoffBehavior=Compose)。

音视频

播放器(媒体对象)

  • MediaElement:控件型,可以在XAML中使用。
  • MeidaPlayer:主要用于Drawing对象,不能在XAML中使用。

播放模式

播放模式由Clock属性确定,非null则是时钟模式,默认独立模式。

  • 独立模式:Source 可指定Uri媒体,可控制和播放(对于MediaElement需要设置LoadedBehavior=Manual),可修改PositonSpeedRatio
  • 时钟模式:媒体对象可以视为动画目标,通过MediaTimeline间接的设置媒体的Source,在TimeLine种会创建相应的Clock,由Clock控制播放,不能直接使用媒体对象的控制方法。在Storyboard中把MediaElement设为目标也是这么加载的。

MediaPlayer示例

csharp
// Create a VideoDrawing. MediaPlayer player = new MediaPlayer(); player.Open(new Uri(@"sampleMedia\xbox.wmv", UriKind.Relative)); VideoDrawing aVideoDrawing = new VideoDrawing(); aVideoDrawing.Rect = new Rect(0, 0, 100, 100); aVideoDrawing.Player = player; // Play the video once. player.Play();

参考