WPF MouseMove InvalidateVisual OnRender 更新非常慢
WPF MouseMove InvalidateVisual OnRender update VERY SLOW
我在 Google 或 Stack Overflow 上都没有找到任何有用的东西,或者根本没有答案(或者我可能只是不知道要搜索什么)——我能找到的最接近的问题是这个:The reason behind slow performance in WPF
但我想深入了解这个简单程序中的延迟,也许我只是做错了什么。
我在 UI 元素的 OnRender() 中渲染大约 2000 个点,它们之间有线,本质上是创建一个折线图。没关系,但我想用 MouseMove 平移图形。这工作正常,但问题是 LAG。每当用鼠标拖动时,我都希望能顺利更新,我认为重新绘制 2000 个点并在它们之间画线对于 i5 CPU 来说就像在公园里散步一样。但它非常慢,即使在我家里的笔记本电脑上使用低分辨率也是如此。所以我检查了性能分析器。 OnRender() 函数几乎不使用任何 CPU.
原来是布局发生了变化并使用了这么多 CPU。
"Layout" 完成时间最长
现在,我听说过 Visual Tree 这个词,但在这个简单的项目中几乎没有任何视觉效果。只是 Main Window 上的一个 UI 元素。它正在使用绘图上下文,我认为绘图上下文绘制得像位图,或者它是否使用自己的 events/hit 框等绘制 UI 元素?因为我想要的只是 UI 元素既能像图像一样工作,又能处理鼠标事件,这样我就可以拖动整个东西(或使用鼠标滚轮缩放)。
所以问题:
- 如果布局导致 slowness/lag,我该如何预防?
- 我也注意到很多垃圾收集是有道理的,但我不希望它在渲染期间发生。我宁愿在空闲时这样做。但是如何?
来源如下:
.cs 文件
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
namespace SlowChart
{
public class SlowChartClass : UIElement
{
List<Point> points = new List<Point>();
double XAxis_Width = 2000;
double XAxis_LeftMost = 0;
double YAxis_Height = 300;
double YAxis_Lowest = -150;
Point mousePoint;
double XAxis_LeftMostPan = 0;
double YAxis_LowestPan = 0;
public SlowChartClass()
{
for (int i = 0; i < 2000; i++)
{
double cos = (float)Math.Cos(((double)i / 100) * Math.PI * 2);
cos *= 100;
points.Add(new Point(i, cos));
}
MouseDown += SlowChartClass_MouseDown;
MouseUp += SlowChartClass_MouseUp;
MouseMove += SlowChartClass_MouseMove;
}
private void SlowChartClass_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (IsMouseCaptured)
{
XAxis_LeftMost = XAxis_LeftMostPan - (e.GetPosition(this).X - mousePoint.X);
YAxis_Lowest = YAxis_LowestPan + (e.GetPosition(this).Y - mousePoint.Y);
InvalidateVisual();
}
}
private void SlowChartClass_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
ReleaseMouseCapture();
}
private void SlowChartClass_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
mousePoint = e.GetPosition(this);
XAxis_LeftMostPan = XAxis_LeftMost;
YAxis_LowestPan = YAxis_Lowest;
CaptureMouse();
}
double translateYToScreen(double Y)
{
double y = RenderSize.Height - (RenderSize.Height * ((Y - YAxis_Lowest) / YAxis_Height));
return y;
}
double translateXToScreen(double X)
{
double x = (RenderSize.Width * ((X - XAxis_LeftMost) / XAxis_Width));
return x;
}
protected override void OnRender(DrawingContext drawingContext)
{
bool lastPointValid = false;
Point lastPoint = new Point();
Rect window = new Rect(RenderSize);
Pen pen = new Pen(Brushes.Black, 1);
// fill background
drawingContext.DrawRectangle(Brushes.White, null, window);
foreach (Point p in points)
{
Point screenPoint = new Point(translateXToScreen(p.X), translateYToScreen(p.Y));
if (lastPointValid)
{
// draw from last to this one
drawingContext.DrawLine(pen, lastPoint, screenPoint);
}
lastPoint = screenPoint;
lastPointValid = true;
}
// draw axis
drawingContext.DrawText(new FormattedText(XAxis_LeftMost.ToString("0.0") + "," + YAxis_Lowest.ToString("0.0"),CultureInfo.InvariantCulture,FlowDirection.LeftToRight,new Typeface("Arial"),12,Brushes.Black),new Point(0,RenderSize.Height-12));
}
}
}
.XAML 文件
<Window x:Class="SlowChart.MainWindow"
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:SlowChart"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:SlowChartClass/>
</Grid>
</Window>
不要为此打电话给 InvalidateVisual()
。它会触发您的 UI 的完全重新布局,这非常慢。
在 WPF 中获得良好性能的关键是了解它是一个保留 绘图系统。 OnRender()
真的应该命名为 AccumulateDrawingObjects()
。它仅在布局过程结束时使用,它正在累积的对象实际上是活动对象,您可以在它完成后 更新。
完成您想要做的事情的有效方法是为您的图表创建绘图组 "backingStore"。您的 OnRender() 唯一需要做的就是将 backingStore 添加到 DrawingContext。然后,您可以随时使用 backingStore.Open() 更新它,只需在其中绘图即可。 WPF 将自动更新您的 UI。
您会发现 StreamGeometry
是绘制到 DrawingContext
的最快方法,因为它针对非动画几何体进行了优化。
您还可以通过在 Pen 上使用 .Freeze()
获得一些额外的性能,因为它不是动画。虽然我怀疑你只画了 2000 点时会注意到。
看起来像这样:
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
// Call render anytime, to update visual
// without triggering layout or OnRender()
public void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
private void Render(DrawingContext drawingContext) {
// move the code from your OnRender() here
}
如果您想查看更多示例代码,请查看此处:
但是,如果视觉对象相对静止,而您只想平移和缩放,则还有其他选择。您可以创建一个 Canvas
,并将 Shapes 实例化到其中,然后在鼠标移动过程中,您会扰乱 Canvas 变换以进行平移和缩放。
我在 Google 或 Stack Overflow 上都没有找到任何有用的东西,或者根本没有答案(或者我可能只是不知道要搜索什么)——我能找到的最接近的问题是这个:The reason behind slow performance in WPF
但我想深入了解这个简单程序中的延迟,也许我只是做错了什么。
我在 UI 元素的 OnRender() 中渲染大约 2000 个点,它们之间有线,本质上是创建一个折线图。没关系,但我想用 MouseMove 平移图形。这工作正常,但问题是 LAG。每当用鼠标拖动时,我都希望能顺利更新,我认为重新绘制 2000 个点并在它们之间画线对于 i5 CPU 来说就像在公园里散步一样。但它非常慢,即使在我家里的笔记本电脑上使用低分辨率也是如此。所以我检查了性能分析器。 OnRender() 函数几乎不使用任何 CPU.
原来是布局发生了变化并使用了这么多 CPU。
"Layout" 完成时间最长
现在,我听说过 Visual Tree 这个词,但在这个简单的项目中几乎没有任何视觉效果。只是 Main Window 上的一个 UI 元素。它正在使用绘图上下文,我认为绘图上下文绘制得像位图,或者它是否使用自己的 events/hit 框等绘制 UI 元素?因为我想要的只是 UI 元素既能像图像一样工作,又能处理鼠标事件,这样我就可以拖动整个东西(或使用鼠标滚轮缩放)。
所以问题:
- 如果布局导致 slowness/lag,我该如何预防?
- 我也注意到很多垃圾收集是有道理的,但我不希望它在渲染期间发生。我宁愿在空闲时这样做。但是如何?
来源如下:
.cs 文件
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
namespace SlowChart
{
public class SlowChartClass : UIElement
{
List<Point> points = new List<Point>();
double XAxis_Width = 2000;
double XAxis_LeftMost = 0;
double YAxis_Height = 300;
double YAxis_Lowest = -150;
Point mousePoint;
double XAxis_LeftMostPan = 0;
double YAxis_LowestPan = 0;
public SlowChartClass()
{
for (int i = 0; i < 2000; i++)
{
double cos = (float)Math.Cos(((double)i / 100) * Math.PI * 2);
cos *= 100;
points.Add(new Point(i, cos));
}
MouseDown += SlowChartClass_MouseDown;
MouseUp += SlowChartClass_MouseUp;
MouseMove += SlowChartClass_MouseMove;
}
private void SlowChartClass_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (IsMouseCaptured)
{
XAxis_LeftMost = XAxis_LeftMostPan - (e.GetPosition(this).X - mousePoint.X);
YAxis_Lowest = YAxis_LowestPan + (e.GetPosition(this).Y - mousePoint.Y);
InvalidateVisual();
}
}
private void SlowChartClass_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
ReleaseMouseCapture();
}
private void SlowChartClass_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
mousePoint = e.GetPosition(this);
XAxis_LeftMostPan = XAxis_LeftMost;
YAxis_LowestPan = YAxis_Lowest;
CaptureMouse();
}
double translateYToScreen(double Y)
{
double y = RenderSize.Height - (RenderSize.Height * ((Y - YAxis_Lowest) / YAxis_Height));
return y;
}
double translateXToScreen(double X)
{
double x = (RenderSize.Width * ((X - XAxis_LeftMost) / XAxis_Width));
return x;
}
protected override void OnRender(DrawingContext drawingContext)
{
bool lastPointValid = false;
Point lastPoint = new Point();
Rect window = new Rect(RenderSize);
Pen pen = new Pen(Brushes.Black, 1);
// fill background
drawingContext.DrawRectangle(Brushes.White, null, window);
foreach (Point p in points)
{
Point screenPoint = new Point(translateXToScreen(p.X), translateYToScreen(p.Y));
if (lastPointValid)
{
// draw from last to this one
drawingContext.DrawLine(pen, lastPoint, screenPoint);
}
lastPoint = screenPoint;
lastPointValid = true;
}
// draw axis
drawingContext.DrawText(new FormattedText(XAxis_LeftMost.ToString("0.0") + "," + YAxis_Lowest.ToString("0.0"),CultureInfo.InvariantCulture,FlowDirection.LeftToRight,new Typeface("Arial"),12,Brushes.Black),new Point(0,RenderSize.Height-12));
}
}
}
.XAML 文件
<Window x:Class="SlowChart.MainWindow"
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:SlowChart"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:SlowChartClass/>
</Grid>
</Window>
不要为此打电话给 InvalidateVisual()
。它会触发您的 UI 的完全重新布局,这非常慢。
在 WPF 中获得良好性能的关键是了解它是一个保留 绘图系统。 OnRender()
真的应该命名为 AccumulateDrawingObjects()
。它仅在布局过程结束时使用,它正在累积的对象实际上是活动对象,您可以在它完成后 更新。
完成您想要做的事情的有效方法是为您的图表创建绘图组 "backingStore"。您的 OnRender() 唯一需要做的就是将 backingStore 添加到 DrawingContext。然后,您可以随时使用 backingStore.Open() 更新它,只需在其中绘图即可。 WPF 将自动更新您的 UI。
您会发现 StreamGeometry
是绘制到 DrawingContext
的最快方法,因为它针对非动画几何体进行了优化。
您还可以通过在 Pen 上使用 .Freeze()
获得一些额外的性能,因为它不是动画。虽然我怀疑你只画了 2000 点时会注意到。
看起来像这样:
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
// Call render anytime, to update visual
// without triggering layout or OnRender()
public void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
private void Render(DrawingContext drawingContext) {
// move the code from your OnRender() here
}
如果您想查看更多示例代码,请查看此处:
但是,如果视觉对象相对静止,而您只想平移和缩放,则还有其他选择。您可以创建一个 Canvas
,并将 Shapes 实例化到其中,然后在鼠标移动过程中,您会扰乱 Canvas 变换以进行平移和缩放。