gpt4 book ai didi

c# - WPF MouseMove InvalidateVisual OnRender 更新非常慢

转载 作者:太空狗 更新时间:2023-10-30 01:00:39 24 4
gpt4 key购买 nike

我没有在 Google 或 Stack Overflow 上找到任何有用的信息,或者根本没有答案(或者我可能只是不知道要搜索什么)——我能找到的最接近的问题是这个:The reason behind slow performance in WPF

但我想深入了解这个简单程序中的延迟,也许我只是做错了什么。

我在 UI 元素的 OnRender() 中渲染大约 2000 个点,它们之间有线,本质上是创建一个折线图。没关系,但我想用 MouseMove 平移图形。这工作正常,但问题是 LAG。每当用鼠标拖动时,我都希望能顺利更新,我认为重新绘制 2000 个点并在它们之间画线对于 i5 CPU 来说就像在公园里散步一样。但它非常慢,即使在我家里的笔记本电脑上使用低分辨率也是如此。所以我检查了性能分析器。 OnRender() 函数几乎不使用任何 CPU。

MouseMove and OnRender hardly use much CPU

事实证明是布局发生了变化并占用了如此多的 CPU。

The Layout is using most CPU

“布局”花费的时间最多

Layout takes the most time It says that changes to the Visual tree were made -- but no changes were made - just InvalidateVisual was called

现在,我听说过 Visual Tree 这个术语,但在这个简单的项目中几乎没有任何视觉效果。只是主窗口上的一个 UI 元素。它使用绘图上下文,我认为绘图上下文绘制得像位图,或者它是否使用自己的事件/点击框等绘制 UI 元素?因为我只想让 UIElement 像图像一样工作,但也可以处理鼠标事件,这样我就可以拖动整个东西(或使用鼠标滚轮缩放)。

所以问题:

  1. 如果布局导致缓慢/滞后,我该如何防止这种情况?
  2. 我还注意到很多垃圾收集是有道理的,但我不希望它在渲染期间发生。我宁愿在空闲时这样做。但是如何呢?

这是来源:

.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()。它仅在布局过程结束时使用,它积累的对象实际上是事件对象,您可以在它完成后更新。


做你想做的事情的有效方法是为你的图表创建一个 DrawingGroup“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
}

如果您想查看更多示例代码,请查看此处:

https://github.com/jeske/SoundLevelMonitor/blob/master/SoundLevelMonitorWPF/SoundLevelMonitor/AudioLevelsUIElement.cs#L172


但是,如果视觉对象相对静止,而您只想平移和缩放,则还有其他选择。您可以创建一个 Canvas,并将 Shapes 实例化到其中,然后在鼠标移动过程中您会扰乱 Canvas 转换以进行平移和缩放。

关于c# - WPF MouseMove InvalidateVisual OnRender 更新非常慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44336308/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com