效果和可视化对象
Contents
可视化对象
通过几何图形、图画和路径可以降低2D图形的开销。但是不适合需要渲染大量图形元素的绘图密集型应用程序,例如绘图程序,演示例子碰撞的物理模型或横向卷轴形式的游戏。即使使用量级更轻的Geometry对象代替Path元素,需要的开销也仍会较大影响应用程序性能。
解决此类问题的方案是,使用低级的可视化层(visual layer)模型。
绘制可视化对象
Visual类是抽象类,所以不能创建该类的实例。最有用的派生类是DrawingVisual类。该类继承自ContainerVisual类,并增加了支持“绘制”希望防止到可视化对象中的图形内容的功能。
为了使用DrawingVisual类绘制内容,需要调用DrawingVisual.RendorOpen()方法。该方法返回一个可用于定义可视化内容的DrawingContext对象。绘制完毕后,需要调用DrawingContext.Close()方法
DrawingVisual visual=new DrawingVisual();
DrawingContext dc=visual.RenderOpen();
//(Perform drawing here.)
dc.close();
本质上,DrawingContext类由各种为可视化对象增加了一些图形细节的方法构成。可调用这些方法绘制各种图形、应用变换以及改变不透明度等。
名称 | 说明 |
---|---|
DrawLine() DrawRectangle() DrawRoundedRectangle() DrawEllips() |
在指定的位置,使用指定的填充和轮廓绘制特定的形状 |
DrawGeometry() DrawDrawing() |
绘制更复杂的Geometry对象和Drawing对象 |
DrawText() | 在指定的位置绘制文本,通过为方法传递FormattedText对象,可指定文本、字体、填充以及其他细节。如果设置了FormattedText.MaxTextWidth属性,可使用该方法绘制换行的文本 |
DrawImage() | 在指定区域(由Rect对象定义)绘制衣服位图图像 |
DrawVideo() | 在特定区域绘制视频内容(封装在MediaPlayer对象中) |
Pop() | 反转最后调用的PushXxx()方法,可使用PushXxx()方法暂时应用一个或多个效果,并且Pop()方法会反转他们 |
PushClip() | 为绘图限制在特定剪裁区域中。这个区域外的内容不被绘制 |
PushEffect() | 为随后的绘图操作应用BitmapEffect对象 |
PushOpacity() PushOpacityMask() |
为了使后续的绘图操作部分透明,应用新的不透明设置或不透明掩码 |
PushTransform() | 设置将应用于后续绘制操作的Transform对象。可使用变换来缩放、移动、旋转或扭曲内容 |
DrawingVisual visual=new DrawingVisual();
using(DrawingContext dc=visual.RenderOpen())
{
Pen drawingPen=new Pen(Brushes.Black,3);
dc.DrawingLine(drawingPen,new Point(0,50),new Point(50,0));
dc.DrawingLine(drawingPen,new Point(50,0),new Point(100,50));
dc.DrawingLine(drawingPen,new Point(0,50),new Point(100,50));
}
当调用DrawingContext方法时,没有实际绘制可视化对象——而只是定义了可视化外观。当通过调用Close()方法结束绘制时,完成的图画被存储在和石化对象中,并通过只读的DrawsingVisual.Drawing属性提供这些图画。WPF会保存Drawing对象,从而当需要时可以重新绘制窗口。
绘制代码的顺序很重要。后面的绘图操作可在已经存在的图形上绘制内容,PushXxx()方法应用的设置会被应用到后续的绘图操作中。例如,可使用PushOpacity()方法改变不透明级别,该设置会影响所有的后续绘图操作。可使用Pop()方法恢复最近的PushXxx()方法。如果多次调用PushXxx()方法,可一次使用一系列Pop方法调用关闭他们。
一旦关闭DrawingContext对象,就不能再修改可视化对象。但可以使用DrawingVisual类的Transform和Opacity属性应用变换或改变整个可视化对象的透明度。如果希望提供全新的内容,可以再次调用RenderOpen()方法并重复绘制过程。
提示:
许多绘图方法都使用Pen和Brush对象,如果计划使用相同的笔画和填充绘制许多可视化对象,就值得事先创建所需的Pen和Brush对象,并在窗口的整个生命周期中保存他们。
还可以通过重写OnRender()方法,使用可视化对象渲染自定义绘图元素,例如下面Rectangle元素用于绘制自身渲染代码
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen=base.GetPen();
drawingContext.DrawRoundedRectangle(base.Fill,pen,this._rect,this.RadiusX,this.RadiusY);
}
在元素中封装可视化对象
为了显示可视化对象,还需要借助于功能完备的WPF元素,WPF元素将可视化对象添加到可视化树种。单个元素具有显示任意数量可视化对象的能力,因此,可以很容易地创建只包含一两个元素,但却主流了几千个可视化对象的窗口。
为在元素中主流可视化对象,需要执行以下任务:
– 为元素调用AddVisualChild()和AddLogicalChild()方法来注册可视化对象。从技术角度看,为了显示可视化对象,不需要执行这些任务,但为了确保正确跟踪可视化对象、在可视化树和逻辑树中显示可视化对象以及使用其他WPF特性(如命中测试),需要执行这些操作。
– 重写VisualChildrenCount属性并返回已经增加了的可视化对象的数量。
– 重写GetViusalChild()方法,当通过索引号请求可视化对象时,添加返回可视化对象所需的代码。
当重写VisualChildrenCount属性和GetVisualChild()方法时,本质上是劫持了那个元素。如果使用的是能够包含嵌套元素的内容控件、装饰元素或面板,这些元素将不再被渲染。例如,如果在自定义窗口中重写了这两个方法,就看不到窗口的其他内容,只会看到添加的可视化对象。
因此,通常创建专用的自定义累来封装希望显示的可视化对象。例如,分析下图中显示的窗口,该窗口允许用户自定义的Canvas面板添加正方形(每个正方形是可视化对象)。
class DrawingCanvas:Canvas
{
private List<Visual> visuals = new List<Visual>();
protected override int VisualChildrenCount => visuals.Count;
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
public void AddVisual(Visual visual)
{
visuals.Add(visual);
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
public void DeletedVisual(Visual visual)
{
visuals.Remove(visual);
base.RemoveVisualChild(visual);
base.RemoveLogicalChild(visual);
}
}
<local:DrawingCanvas x:Name="drawingSurface" Background="White" ClipToBounds="True" MouseLeftButtonDown="drawingSurface_MouseLeftButtonDown"
MouseLeftButtonUp="drawingSurface_MouseLeftButtonUp" MouseMove="drawingSurface_MouseMove"></local:DrawingCanvas>
private void drawingSurface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Point pointClicked = e.GetPosition(drawingSurface);
DrawingVisual visual = new DrawingVisual();
DrawingSquare(visual, pointClicked, false);
drawingSurface.AddVisual(visual);
}
Brush drawingBrush = Brushes.AliceBlue;
Brush selectedDrawingBrush = Brushes.LightGoldenrodYellow;
Pen drawingPen = new Pen(Brushes.SteelBlue, 3);
Size squareSize = new Size(30, 30);
private void DrawingSquare(DrawingVisual visual,Point topLeftCorner,bool isSelected)
{
using (DrawingContext dc = visual.RenderOpen())
{
Brush brush = drawingBrush;
dc.DrawRectangle(brush, drawingPen, new Rect(topLeftCorner, squareSize));
dc.DrawText(new FormattedText("hjefef",CultureInfo.CurrentCulture,FlowDirection.LeftToRight,new Typeface("华文行楷"),12,Brushes.Black), topLeftCorner);
}
}
private void drawingSurface_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
}
private void drawingSurface_MouseMove(object sender, MouseEventArgs e)
{
}