C# 自定义绘制控件的糟糕性能
我正在C# 自定义绘制控件的糟糕性能,c#,wpf,rendering,C#,Wpf,Rendering,我正在wpf中制作简单的图形控件。我无法解释也无法解决性能问题:与winforms相比,它太慢了。也许我做错了什么 我准备演示来演示这个问题 以下是测试控制: public class Graph : FrameworkElement { private Point _mouse; private Point _offset = new Point(500, 500); public Graph() { Loaded += Graph_Loade
wpf
中制作简单的图形控件。我无法解释也无法解决性能问题:与winforms相比,它太慢了。也许我做错了什么
我准备演示来演示这个问题
以下是测试控制:
public class Graph : FrameworkElement
{
private Point _mouse;
private Point _offset = new Point(500, 500);
public Graph()
{
Loaded += Graph_Loaded;
}
private void Graph_Loaded(object sender, RoutedEventArgs e)
{
// use parent container with background to receive mouse events too
var parent = VisualTreeHelper.GetParent(this) as FrameworkElement;
if (parent != null)
parent.MouseMove += (s, a) => OnMouseMove(a);
}
protected override void OnRender(DrawingContext context)
{
// designer bugfix
if (DesignerProperties.GetIsInDesignMode(this))
return;
Stopwatch watch = new Stopwatch();
watch.Start();
// generate some big figure (try to vary that 2000!)
var radius = 1.0;
var figures = new List<LineSegment>();
for (int i = 0; i < 2000; i++, radius += 0.1)
{
var segment = new LineSegment(new Point(radius * Math.Sin(i) + _offset.X, radius * Math.Cos(i) + _offset.Y), true);
segment.Freeze();
figures.Add(segment);
}
var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
geometry.Freeze();
var pen = new Pen(Brushes.Black, 5);
pen.Freeze();
context.DrawGeometry(null, pen, geometry);
// measure time
var time = watch.ElapsedMilliseconds;
Dispatcher.InvokeAsync(() =>
{
Window.GetWindow(this).Title = string.Format("{0:000}ms; {1:000}ms", time, watch.ElapsedMilliseconds);
}, DispatcherPriority.Loaded);
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
var mouse = e.GetPosition(this);
if (e.LeftButton == MouseButtonState.Pressed)
{
// change graph location
_offset.X += mouse.X - _mouse.X;
_offset.Y += mouse.Y - _mouse.Y;
InvalidateVisual();
}
// remember last mouse position
_mouse = mouse;
}
}
公共类图:FrameworkElement
{
专用点鼠标;
专用点_偏移=新点(500500);
公共图()
{
加载+=图形_加载;
}
已加载私有无效图(对象发送器、路由目标)
{
//使用具有后台的父容器也可以接收鼠标事件
var parent=visualtreeheloper.GetParent(this)作为FrameworkElement;
如果(父项!=null)
parent.MouseMove+=(s,a)=>OnMouseMove(a);
}
受保护的覆盖void OnRender(DrawingContext上下文)
{
//设计器错误修复
if(DesignerProperties.GetIsInDesignMode(此))
返回;
秒表=新秒表();
watch.Start();
//生成一些大的数字(尝试改变2000!)
var半径=1.0;
var数字=新列表();
对于(int i=0;i<2000;i++,半径+=0.1)
{
var段=新线段(新点(半径*数学Sin(i)+_offset.X,半径*数学Cos(i)+_offset.Y),真);
段。冻结();
图.增加(部分);
}
var geometry=new PathGeometry(new[]{new PathFigure(图[0]。点,图,假)});
geometry.Freeze();
var笔=新笔(黑色,5);
笔。冻结();
DrawGeometry(空、笔、几何体);
//测量时间
var时间=watch.elapsedmillisons;
Dispatcher.InvokeAsync(()=>
{
GetWindow(this.Title=string.Format(“{0:000}ms;{1:000}ms”,time,watch.elapsedmillesons);
},DispatcherPriority.Loaded);
}
MouseMove上的受保护覆盖无效(MouseEventArgs e)
{
基地移动(e);
var mouse=e.GetPosition(这个);
如果(e.LeftButton==鼠标按钮状态。按下)
{
//更改图形位置
_offset.X+=mouse.X-_mouse.X;
_offset.Y+=mouse.Y-\u mouse.Y;
无效的(通常的);
}
//记得上次鼠标的位置吗
_鼠标=鼠标;
}
}
以下是如何在xaml中使用它:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525" WindowState="Maximized">
<Grid Background="White">
<local:Graph/>
</Grid>
</Window>
备注:控件将绘制图形,可通过鼠标移动:
它将在标题中显示两个度量值:第一个是完成OnRender()
所用的时间,第二个是实际渲染所用的时间(渲染后第一次调用)
尝试改变2000
:设置1000
使移动舒适,3000
就像重绘图形前的半秒延迟(在我的电脑上)
问题:
MouseMove
中使用InvalidateVisual()
更新图形偏移是否合适?如果是坏的,什么是正确的技术使其无效5ms
,但主观上移动需要更长的时间(200ms+)。为什么呢我找到了上一个问题的答案。使用鼠标移动时,渲染时间的测量无法正确工作。但如果调整窗口的大小,则第二次渲染时间将变为
300ms
(在我的电脑上显示2000
数字)。因此,鼠标无效不是错误的(第一个问题),但渲染速度确实非常慢。这是WPF不擅长的一种任务。我指的是一般的矢量图形。由于保留模式。它对控件渲染很好,但对频繁更新的繁忙图形不太好。我尝试在WPF地图上渲染GPS轨迹时遇到了同样的问题
我建议使用direct2d并将其托管在WPF中。类似于:
这将给您带来高性能
PS别误会我的意思。WPF没有什么不好的地方。它是为解决特定问题而设计的。它很容易组合控件和构建令人印象深刻的UI。我们对自动布局系统习以为常。但它不可能在所有可能的情况下都很聪明,微软也没有很好地解释这些情况,因为它不是这不是一个好的选择。让我给你一个例子。IPad之所以有性能,是因为它有固定的分辨率和绝对的布局。如果你固定WPF窗口大小并使用画布面板,你会得到同样的体验。下面是使用
StreamGeometry
对代码的重写,这可以给你5%-10%的提升
protected override void OnRender(DrawingContext context)
{
// designer bugfix
if (DesignerProperties.GetIsInDesignMode(this))
return;
Stopwatch watch = new Stopwatch();
watch.Start();
// generate some big figure (try to vary that 2000!)
var radius = 1.0;
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext ctx = geometry.Open())
{
Point start = new Point(radius * Math.Sin(0) + _offset.X, radius * Math.Cos(0) + _offset.Y);
ctx.BeginFigure(start, false, false);
for (int i = 1; i < 2000; i++, radius += 0.1)
{
Point current = new Point(radius * Math.Sin(i) + _offset.X, radius * Math.Cos(i) + _offset.Y);
ctx.LineTo(current, true, false);
}
}
//var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
geometry.Freeze();
Pen pen = new Pen(Brushes.Black, 5);
pen.Freeze();
context.DrawGeometry(null, pen, geometry);
// measure time
var time = watch.ElapsedMilliseconds;
Dispatcher.InvokeAsync(() =>
{
Window.GetWindow(this).Title = string.Format("{0:000}ms; {1:000}ms", time, watch.ElapsedMilliseconds);
}, DispatcherPriority.Loaded);
}
这很奇怪,这里没有人提到过,但是可以在wpf中使用gdi绘图(没有) 我首先发现了这个问题,它变成了普通的基于渲染的图形(使用
InvalidateVisuals()
重新绘制)
这种方法能够画出数十万条线,响应速度非常快
缺点:
- 不像纯gdi one图形那样平滑,
出现几次之后,会闪烁一点DrawImage
- 需要将所有wpf对象转换为gdi对象(有时是不可能的):笔、画笔、点、矩形等
- 没有动画,图形本身可以设置动画(例如,变换),但图形不能
public class Graph : UIElement
{
TranslateTransform _transform = new TranslateTransform() { X = 500, Y = 500 };
public Graph()
{
CacheMode = new BitmapCache(1.4); //decrease this number to improve performance on the cost of quality, increasing improves quality
this.RenderTransform = _transform;
IsHitTestVisible = false;
}
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
if (VisualParent != null)
(VisualParent as FrameworkElement).MouseMove += (s, a) => OnMouseMoveHandler(a);
}
protected override void OnRender(DrawingContext context)
{
// designer bugfix
if (DesignerProperties.GetIsInDesignMode(this))
return;
Stopwatch watch = new Stopwatch();
watch.Start();
// generate some big figure (try to vary that 2000!)
var radius = 1.0;
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext ctx = geometry.Open())
{
Point start = new Point(radius * Math.Sin(0), radius * Math.Cos(0));
ctx.BeginFigure(start, false, false);
for (int i = 1; i < 5000; i++, radius += 0.1)
{
Point current = new Point(radius * Math.Sin(i), radius * Math.Cos(i));
ctx.LineTo(current, true, false);
}
}
//var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
geometry.Freeze();
Pen pen = new Pen(Brushes.Black, 5);
pen.Freeze();
context.DrawGeometry(null, pen, geometry);
// measure time
var time = watch.ElapsedMilliseconds;
Dispatcher.InvokeAsync(() =>
{
Application.Current.MainWindow.Title = string.Format("{0:000}ms; {1:000}ms", time, watch.ElapsedMilliseconds);
}, DispatcherPriority.Loaded);
}
protected void OnMouseMoveHandler(MouseEventArgs e)
{
var mouse = e.GetPosition(VisualParent as FrameworkElement);
if (e.LeftButton == MouseButtonState.Pressed)
{
_transform.X = mouse.X;
_transform.Y = mouse.Y;
}
}
}
public class Graph : UIElement
{
DrawingVisual drawing;
VisualCollection _visuals;
TranslateTransform _transform = new TranslateTransform() { X = 200, Y = 200 };
public Graph()
{
_visuals = new VisualCollection(this);
drawing = new DrawingVisual();
drawing.Transform = _transform;
drawing.CacheMode = new BitmapCache(1);
_visuals.Add(drawing);
Render();
}
protected void Render()
{
// designer bugfix
if (DesignerProperties.GetIsInDesignMode(this))
return;
Stopwatch watch = new Stopwatch();
watch.Start();
using (DrawingContext context = drawing.RenderOpen())
{
// generate some big figure (try to vary that 2000!)
var radius = 1.0;
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext ctx = geometry.Open())
{
Point start = new Point(radius * Math.Sin(0), radius * Math.Cos(0));
ctx.BeginFigure(start, false, false);
for (int i = 1; i < 2000; i++, radius += 0.1)
{
Point current = new Point(radius * Math.Sin(i), radius * Math.Cos(i));
ctx.LineTo(current, true, false);
}
}
geometry.Freeze();
Pen pen = new Pen(Brushes.Black, 1);
pen.Freeze();
// measure time
var time = watch.ElapsedMilliseconds;
context.DrawGeometry(null, pen, geometry);
Dispatcher.InvokeAsync(() =>
{
Application.Current.MainWindow.Title = string.Format("{0:000}ms; {1:000}ms", time, watch.ElapsedMilliseconds);
}, DispatcherPriority.Normal);
}
}
protected override Visual GetVisualChild(int index)
{
return drawing;
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var mouse = e.GetPosition(VisualParent as FrameworkElement);
_transform.X = mouse.X;
_transform.Y = mouse.Y;
}
base.OnMouseMove(e);
}
}
protected override void OnRender(DrawingContext context)
{
using (var bitmap = new GDI.Bitmap((int)RenderSize.Width, (int)RenderSize.Height))
{
using (var graphics = GDI.Graphics.FromImage(bitmap))
{
// use gdi functions here, to ex.: graphics.DrawLine(...)
}
var hbitmap = bitmap.GetHbitmap();
var size = bitmap.Width * bitmap.Height * 4;
GC.AddMemoryPressure(size);
var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
image.Freeze();
context.DrawImage(image, new Rect(RenderSize));
DeleteObject(hbitmap);
GC.RemoveMemoryPressure(size);
}
}