WPF MouseMove InvalidateVisual OnRender 更新非常慢

Posted

技术标签:

【中文标题】WPF MouseMove InvalidateVisual OnRender 更新非常慢【英文标题】:WPF MouseMove InvalidateVisual OnRender update VERY SLOW 【发布时间】:2017-11-04 07:12:59 【问题描述】:

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

但我想在这个简单的程序中彻底解决这个滞后问题,也许我只是做的不对。

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

原来是布局在发生变化并使用了如此多的 CPU。

“布局”完成时间最长

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

所以问题:

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

这里是来源:

.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>

【问题讨论】:

最好使用 TranslateTransform 而不是在平移时重新渲染所有内容。你也可以画一条折线而不是 2000 条线。 我在这个问题/答案***.com/questions/25450979/…(使用翻译转换)中找到了更相关的解决方案。欢呼 【参考方案1】:

不要为此打电话给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
 

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

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


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

【讨论】:

以上是关于WPF MouseMove InvalidateVisual OnRender 更新非常慢的主要内容,如果未能解决你的问题,请参考以下文章

MouseEnter/MouseMove 面板,包括空格

鼠标单击并拖动事件 WPF

WPF设置窗口模式(Windowstyle=“None”)

在 wpf 中绘制时为线条设置动画

WPF将Point数组转换为PathGeometry

如何让label有弹起效果 wpf