WPF异步高效绘制过万级别的矩形图形矢量图,无限放大缩小,显示效果极佳,先看效果如下:
想在WPF上绘制图形,有哪些方式?
1.通过Canvas静态绘图方法,例如:
<Canvas>
<Ellipse Width="50" Height="50" Fill="Blue" Canvas.Left="10" Canvas.Top="10"/>
<Rectangle Width="80" Height="40" Fill="Red" Canvas.Left="50" Canvas.Top="50"/>
</Canvas>
2.DrawingContext进行绘图,例如:
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
// 创建一个画刷和画笔
SolidColorBrush brush = new SolidColorBrush(Colors.Green);
Pen pen = new Pen(Brushes.Black, 2);
// 绘制一个矩形
drawingContext.DrawRectangle(brush, pen, new Rect(10, 10, 100, 50));
}
3.使用DrawingVisual进行低级别的绘图
DrawingVisual是一个轻量级的可视化对象,允许你在低级别上执行绘图。通过重写OnRender方法,你可以使用DrawingContext在DrawingVisual上进行绘图。
例如:
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
using (DrawingContext dc = drawingContext.RenderOpen())
{
// 在DrawingVisual上进行绘图
dc.DrawEllipse(Brushes.Blue, new Pen(Brushes.Black, 2), new Point(50, 50), 30, 30);
}
}
4.使用graphics绘制Image流可以直接绑定到界面
但是如果要考率绘制大批量图形,我们就要性能和内存等多个方面考虑了,否则绘制1w的矩形需要1分钟,哪个客户也接受不了啊。
我们考虑这些方面:
- 1.使用UI 虚拟化控件: 如果你有过万级别的矩形,不要一次性全部绘制。考虑采用虚拟化的方式,只在当前视图区域内绘制可见的矩形。这可以通过一些控件如VirtualizingStackPanel或者自定义的虚拟化机制来实现。
- 2.使用独立的绘制线程: 考虑在一个独立的绘制线程中进行绘制操作,以避免对UI主线程的影响。这样可以更好地控制绘制操作的调度和频率。
- 3.多线程异步绘制 将绘制操作异步化,可以避免UI主线程阻塞。使用Task或async/await模式来在后台线程进行绘制操作,然后在UI线程更新视图。
- 4.自定义UIElement: 如果使用WPF,可以考虑创建自定义的UIElement,通过重写OnRender方法实现高效的绘制。确保只在需要更新时进行绘制。
- 5.可以使用Freezable类来冻结Pen对象,这个对提高性能有非常明显的对比,最后我在这里做个测评
实现过程:
XAML代码
<Window x:Class="WpfAppTest.shiliangTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfAppTest" PreviewMouseWheel="Window_PreviewMouseWheel"
mc:Ignorable="d"
Title="shiliangTest" Height="800" Width="1000">
<Grid>
<Canvas x:Name="DrawingContainer" Width="800" Height="800" Panel.ZIndex="1" />
</Grid>
</Window>
后台代码:
public class VisualHost1 : FrameworkElement
{
private readonly DrawingVisual _drawingVisual;
public VisualHost1()
{
_drawingVisual = new DrawingVisual();
}
public DrawingContext RenderOpen()
{
return _drawingVisual.RenderOpen();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawDrawing(_drawingVisual.Drawing);
}
}
public VisualHost1 _visualHost;
double scale;
public shiliangTest()
{
InitializeComponent();
_visualHost = new VisualHost1();
DrawingContainer.Children.Add(_visualHost);
double canvasWidth = DrawingContainer.Width;
double canvasHeight = DrawingContainer.Height;
//double canvasWidth = 3000;
//double canvasHeight = 3000;
new SelectBoxRect(DrawingContainer);
using (new PerformanceCounter("ghfgh"))
{
DrawRectangles1(canvasWidth, canvasHeight);
}
scale = 1;
}
private void Window_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (Keyboard.IsKeyDown(Key.LeftCtrl))
{
if (e.Delta < 0)
{
scale -= 0.01;
}
else
{
scale += 0.01;
}
// scale += (double)e.Delta / 35000;
ScaleTransform transfrom = new ScaleTransform();
transfrom.ScaleX = transfrom.ScaleY = scale;
this.DrawingContainer.RenderTransform = transfrom;
}
}
private Pen CreateAndFreezePen()
{
// 创建Pen
Pen pen = new Pen(Brushes.Black, 1);
// 冻结Pen
if (pen.CanFreeze)
{
pen.Freeze();
}
return pen;
}
private void DrawRectangles1(double canvasWidth, double canvasHeight)
{
int rows = 100; // 纵向矩形数量
int columns = 100; // 横向矩形数量
using (DrawingContext drawingContext = _visualHost.RenderOpen())
{
double rectangleWidth = canvasWidth / columns;
double rectangleHeight = canvasHeight / rows;
var pen = CreateAndFreezePen();
for (int i = 0; i < columns; i++)
{
for (int j = 0; j < rows; j++)
{
double x = i * rectangleWidth;
double y = j * rectangleHeight;
// 创建矩形几何图形
Rect rectangleRect = new Rect(new Point(x, y), new Size(rectangleWidth, rectangleHeight));
Geometry rectangleGeometry = new RectangleGeometry(rectangleRect);
// 绘制矩形
//drawingContext.DrawGeometry(Brushes.Blue, new Pen(Brushes.Black, 1), rectangleGeometry);
drawingContext.DrawGeometry(Brushes.Blue, pen, rectangleGeometry);
}
}
}
}
对比是否冻结Pen的结果
这是未冻结的耗时
这是冻结后的耗时