形状元素能提供一种便利的方式与图形一起工作,在一些情形中,添加表示绘图的元素到UI树中,可能是比它的价值更加麻烦。你的数据可能被构造以一种易于编写代码的方式——简单地表现一系列基于数据的绘图操作,而不是构造一棵对象树。
WPF提供一个“可视化层”API,作为一个对形状元素较低级别的折中。(实际上,形状元素全都在可视化层得顶部被实现。)这个API使我们编写按需生成的代码。
可视化是一个可见的对象。WPF应用程序的外观是将它所有的可视化组合到屏幕上形成的。由于WPF生成在在可视化层的顶级,每个元素都是可视化的。FrameworkElement基类间接派生于Visual。在可视化层编程,简单地解决了创建一个可视化和编写代码告诉WPF我们想要在可视化中显示什么。
即使在这个低的级别,WPF的表现非常不同于Win32。图形加速的方式是托管的,这意味着你的按需生成的代码很少被调用——少于在经典Windows应用程序中。
7.5.1 按需生成
按需生成的关键定制为OnRender方法。这个方法被WPF调用在它需要你的组件生成它的外观时。(这是内嵌形状的类生成它们自身的方式。)
OnRender虚方法定义在OnDemandVisual类。大多数元素都间接的通过FrameworkElement派生于此,这就增加了核心样式如外观和输入处理。
示例7-47显示了一个字定义的元素,覆写了OnRender。
示例7-47
{
protected override void OnRender(DrawingContext drawingContext)
{
Debug.WriteLine("OnRender");
base.OnRender(drawingContext);
drawingContext.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 50));
FormattedText text = new FormattedText("Hello, world",
CultureInfo.CurrentUICulture, FlowDirection.LeftToRightThenTopToBottom,
new Typeface("Verdana"), 24, Brushes.Black);
drawingContext.DrawText(text, new Point(3, 3));
}
}
OnRender方法传递了一个单独的DrawingContext类型的参数。这是一个WPF中低级别的绘图API。它提供了一组基础绘图操作,这些都在表7-4中列出。示例7-47使用了DrawRectangle和DrawText方法。
注意到DrawingContext使用了Brush和Pen类来指出形状是如何填充和画轮廓的,正如我们之前看到的高级别形状对象。我们还可以传递同样的Geometry和Drawing对象——也是在本章前面看到的。
Operation |
Usage |
---|---|
DrawDrawing |
Draws a Drawing object. |
DrawEllipse |
Draws an ellipse. |
DrawGeometry |
Draws any Geometry object. |
DrawGlyphRun |
Draws a series of glyphs (i.e., text elements) offering detailed control over typography. |
DrawImage |
Draws a bitmap image. |
DrawLine |
Draws a line (a single segment). |
DrawRectangle |
Draws a rectangle. |
DrawRoundedRectangle |
Draws a rectangle with rounded corners. |
DrawText |
Draws text. |
DrawVideo |
Draws a rectangular region that can display video. |
PushTransform |
Sets a transform that will be applied to all subsequent drawing operations until Pop is called; if a transform is already in place, the net effect will be the combination of all the transforms currently pushed. |
PushClip |
Sets a clip region that will be applied to all subsequent drawing operations until Pop is called; as with PushTransform, multiple active clip regions will combine. |
PushOpacity |
Sets a level of opacity that will be applied to all subsequent drawing operations until Pop is called; as with transforms and clips, multiple opacities are combined. |
Pop |
Removes the transform, clip region, or opacity added most recently by PushTransform, PushClip, or PushOpacity. If those methods have been called multiple times, calls to Pop remove their effects in reverse order. (The transforms, clip regions, and opacities behave like a stack.) |
因为我们的自定义元素派生于FrameworkElement,它自然地集成到任何WPF应用程序中。示例7-48为一个窗体的标记,其中包含了自定义元素,我们可以使用它就像我们之前使用的任何自定义元素。这个窗体如图7-56所示。
示例7-48
<Window x:Class="VisualRender.Window1"
xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
xmlns:cc="controls"
Text="Visual Layer Rendering"
>
<Canvas>
<cc:CustomRender Canvas.Top="10" Canvas.Left="10" x:Name="customRender" />
</Canvas>
</Window>
图7-56
注意到,示例7-47中的OnRender方法调用了Debug.WriteLine。如果程序运行在VS2008调试器的内部,这将在每次调用OnRender方法的时候,打印出一条消息到“输出”窗体。这支持我们看到WPF如何经常要求我们的自定义可视化来生成它自身。如果你习惯于如何在在标准Win32和Windows Forms中的按需绘制下工作,你可能希望看到这被规则地调用——无论窗体重新调整大小还是部分模糊或隐藏。实际上,它只会被调用一次。
结果是按需生成并不相似于Win32的旧有样式生成,正如你可能想到的。WPF会调用你的OnRender方法当它需要知道你的可视化显示什么内容,但是图形加速的工作方式意味着这种情况的发生远远少于Win32中等价的重画。WPF缓存了生成指令。这种缓存的范围和形式并没有文本化,但是它清晰的发生了。进一步而言,这比简单的基于图像的缓存要微妙的多。我们可以添加这些代码到示例7-48的宿主窗体(在后台代码文件中可能是这样的)。
{
VisualOperations.SetTransform(CustomRender, new ScaleTransform(6, 6));
}
先前的片断应用了转换到我们的元素,以因数6放大。当点击用户界面时,自定义可视化会如你想希望的方式延伸,而且OnRender并没有被调用。不仅如此,放大的可视化并没有显示任何你可以看到的像素化或模糊化的人造物,通过一个简单的图像缩放。它继续是锐利的,正如你在图7-57看到的。
图7-57
这指出了WPF获取关于可视化内容的缩放信息。它能重绘我们的可视化的屏幕外观,而不使用我们的OnRender方法,即使当转换有所改变。这在局部上取决于加速构架,而且同样因为转换支持在大多数基础的级别生成在WPF中。
WPF重绘的能力——不使用OnRender,允许用户界面在屏幕上保持完整,即使我们的应用程序很繁忙。它同样支持动画系统工作而不用很多来自应用程序的干扰,因为所有的基础绘制操作都被保留了,WPF可以重建任何部分的UI——当独立的元素改变的时候。
如果我们对象的状态必须在某种程度上改变——需要更新外观,我们可以调用InvalidateVisual方法。这将导致WPF调用我们的OnRender方法,允许我们重建外观。
注意到,当你重写OnRender的时候,你还应该典型地覆写MeasureOverrid和ArrangeOverride方法。否则,WPF的外观系统不会知道你的元素有多大。我们得到的唯一原因,而不用在这里这么做,是我们在Canvas上使用该元素,这将不会关心它的子元素有多大。为了在其它的面板上工作,这是实质的让外观系统知道你的大小。第二章描述了MeasureOverrid和ArrangeOverride方法。