WPF绘制图形有三种方式:
- 使用FrameworkElement的派生类
- 使用图元转换器绘制几何图形
- 使用DrawingContext绘制
1. 使用FrameworkElement派生类
FrameworkElement类继承自UIElement类,意味它的派生类,都是UI元素,可以直接显示在界面上中。例如Shape的子类,Control的子类等。这是最简单的方式,就不再赘述。
2. 使用图元转换器绘制图形
上一篇文章已经介绍过Geometry了。但Geometry创建的仅仅是“理论上”的几何图形,不能直接显示出来。如果还要显示出来,还需要借助以下两个类:
- XXDrawing类(Drawing在这里不是动词,而是名词“图画”的意思,XX代表不同的内容)。
- DrawingXX类(“转换器”,XX代表不同的内容)
2.1 图元包装器:Drawing
Drawing类用于包装2D图元(图元可以是Geometry,也可以是GlyphRun、Image、Video),并为其添加一些额外的信息,比如Pen,Brush等属性。
它是一个抽象类,可以使用它的派生类来包装不同的图形:
类名 | 功能 |
GeometryDrawing | 包装几何图形 |
GlyphRunDrawing | 包装字符 |
ImageDrawing | 包装图片 |
VideoDrawing | 包装视频 |
DrawingGroup | 将多个Drawing对象组合到一个绘图器中,进行统一包装 |
2.2 图元转换器:DrawingXX
使用Drawing类的子类包装之后,它依旧不能直接显示出来。因为它还只是“理论上”的图元,还需要借助“图元转换器”来显示。图元转换器有两种:
类名 | 功能 |
DrawingBrush | 将Drawing对象当成画刷。类似与Photoshop中的画刷功能 |
DrawingImage | 将Drawing对象当成ImageSource。 |
2.2.1 DrawingBrush
DrawingBrush的作用是将Drawing对象转换为Brush。在任何需要用到Brush的地方,就可以使用它。
// 创建几何图形
EllipseGeometry cicle = new(new Point(5, 5), 5, 5);
EllipseGeometry cicle2 = new(new Rect(new Size(20, 16)));
GeometryGroup geoGroup = new() { Children = { cicle, cicle2 }, FillRule= FillRule.EvenOdd };
// 创建图画
GeometryDrawing drawing = new(Brushes.Red, new Pen(Brushes.Blue, 2), geoGroup);
// 创建DrawingBrush
DrawingBrush brushes = new DrawingBrush(drawing);
border.BorderThickness = new Thickness(50);
border.Background = brushes;
2.2.2. DrawingImage
DrawingImage的作用是把Drawing对象转换为ImageSource。在任何需要ImageSource的地方,就可以使用它。
// 创建几何图形
EllipseGeometry cicle = new(new Point(5, 5), 5, 5);
EllipseGeometry cicle2 = new(new Rect(new Size(20, 16)));
GeometryGroup geoGroup = new() { Children = { cicle, cicle2 }, FillRule= FillRule.EvenOdd };
// 创建图画
GeometryDrawing drawing = new(Brushes.Red, new Pen(Brushes.Blue, 2), geoGroup);
// 创建DrawingBrush
DrawingBrush brushes = new DrawingBrush(drawing);
DrawingImage image = new(drawing);
img.Source = image;
3. 使用DrawingContext绘制图形
DrawingContext是真正的“绘图”的类。它提供了一系列的方法来绘制图元。但是它是一个抽象类,不能直接创建类的实例。而且也不能创建它的子类(因为它没有公开的构造函数)。
3.1 创建DrawingContext对象
要创建DrawingContext对象,有两种方式:
-
- 使用类的方法
- 继承自UIElement类
3.1.1 使用类的方法:
某些类提供一个方法以创建DrawingContext对象,例如DrawingVisual类的RendOpen()方法、DrawingGroup类Open()方法。
// DrawingVisual.RendOpen()方法
DrawingVisual dv = new DrawingVisual();
using DrawingContext context = dv.RenderOpen();
context.DrawXX(...); // 绘制图形
// DrawingGroup.Open()方法
DrawingGroup dg = new DrawingGroup();
using DrawingContext dc = dg.Open();
dc.DrawXX(...); // 绘制图形
3.1.2 继承自UIElement类
public class visualElement:UIElement
{
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
}
}
注释:
-
- UIElement类提供了OnRender()方法,但在工作中,不一定要直接继承UIElement,可以从Framework,或者Control类继承。
- OnRender()方法中的DrawingContext对象,是系统传递过来的,而不是自己的代码创建的。因此可以直接使用。
3.2 显示DrawingContext对象
创建完DrawingContext对象,并用其绘制了图元后的,发现它依旧无法显示出来。为什么呢?这是因为DrawingContext不是一个Visual对象,而仅仅只是一个绘图环境,绘制的图形仅保存在系统中。如果要显示出来,就必须将其转换为Visual对象。
3.2.1 使用DrawingVisual对象
在3.1.1的代码示例中,使用DrawingVisual类的RendOpen()方法创建了DrawinContext对象。而DrawingVisual就是一个Visual的对象,那么要如何才能显示它呢?
根据微软官方的解释,需要为DrawingVisual对象提供一个容器,它的约束条件如下:
-
-
- 容器必须继承自FrameworkElement;
- 必须重写GetVisualChild()方法;
- 必须重写VisualChildrenCount属性。
- 在界面元素中添加该容器的实例。
-
示例代码如下:
(1)创建可视化对象的内容
public class VisualElement : FrameworkElement
{
readonly DrawingVisual dv = new();
// 重写VisualChildrenCount属性
protected override int VisualChildrenCount => dv.Children.Count;
// 重写GetVisualChild()方法
protected override Visual GetVisualChild(int index)
{
return dv.Children[index];
}
public VisualElement()
{
// 确定dv在VisualTree的父级。否则无法支持鼠标事件
this.AddVisualChild(dv);
// 确定dv在逻辑树上的父级。
this.AddLogicalChild(dv);
}
// 添加视觉对象
public virtual void AddVisual(Visual visual)
{
if (visual == null) return;
dv.Children.Add(visual);
}
public void DrawLine(Pen pen, Point start, Point end)
{
DrawingVisual visual = new();
using DrawingContext dc = visual.RenderOpen();
dc.DrawLine(pen, start, end);
AddVisual(visual);
}
}
(2)显示容器中的内容
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// 创建容器对象,并绘制图像
VisualElement element = new();
element.DrawingLine(new Pen(Brushes.Yellow,2),new Point(0,0),new Point(200,300));
// 因为容器是一个FrameworkElement,所有可以加入到Grid的子集中。
grid.Children.Add(element);
}
}
注释:
-
-
- Visual类有一个AddVisualChildren()的方法。该方法不是为了显示“可视化”对象的,而是为了确定可视化对象的父子关系。WPF不是一个实时绘图程序,只有在需要的时候才绘制界面。所以该方法不能用来“显示”。
- DrawingVisual类,表示一个可视化的对象。它继承自ContainerVisual,进而继承自Visual,因此它是一个可以显示在界面上的东西。
- DrawingContext类,表示一个绘图环境,在这个环境中绘图完毕之后,需要将其关闭。一旦绘图环境关闭,就不能在打开并修改了。
-
3.2.2 使用DrawingXX对象
在3.1.1示例代码中,使用DrawingGroup类的Open()方法创建了DrawinContext对象。
但DrawingGroup是一个Drawing对象,而不是Visual对象,无法直接显示。那要怎么显示呢?
只能利用第2节提到的图元转换器:DrawingBrush、DrawingImage类。详细参见第2节,这里不再一一赘述。
3.2.3 继承自UIElement类
在3.1.2的示例代码中,创建了一个继承自的UIElement的类,并重写了OnRender()方法。
因为子类是继承自UIElement的,因此子类本身就是一个可以现在在界面上的元素。那么在OnRender()方法中绘制的图元,可以直接显示出来。这里就不再赘述。