WPF MouseMove InvalidateVisual OnRender更新非常慢

时间:2017-06-02 19:40:34

标签: c# wpf uielement drawingcontext

我在谷歌或Stack Overflow上找不到任何有用的东西,或者根本没有答案(或者我只是不知道要搜索什么) - 我能得到的最接近的问题是:{{3 }}

但是我想在这个简单的程序中找到这个滞后的底部,也许我只是没有做正确的事情。

我在UI元素的OnRender()中使用它们之间的线条渲染大约2000个点,实质上是创建折线图。没关系,但我想用MouseMove平移图表。这工作正常,但问题是LAG。无论何时用鼠标拖动我都希望平滑更新,我认为用它们之间的线重绘2000点就可以在公园里走i5 CPU了。但即使在家里的笔记本电脑上分辨率很低,它也非常慢。所以我检查了Performance Profiler。 OnRender()函数几乎不使用任何CPU。

The reason behind slow performance in WPF

事实证明,布局正在改变并使用如此多的CPU。

MouseMove and OnRender hardly use much CPU

“布局”花费最多的时间来完成

The Layout is using most CPU Layout takes the most time

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

所以问题:

  1. 如果布局导致缓慢/滞后,我该如何防止这种情况?
  2. 我还注意到很多有意义的垃圾收集,但我不希望它在渲染过程中发生。我宁愿在它空闲的时候那样做。但是如何?
  3. 以下是来源:

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

1 个答案:

答案 0 :(得分:5)

不要为此致电InvalidateVisual()。它触发了UI的完全重新布局,这非常慢。

WPF良好性能的关键在于理解它是保留的绘图系统。 OnRender()应该真正命名为AccumulateDrawingObjects()。它仅在布局过程结束时使用,并且它正在累积的对象实际上是活动对象,您可以在 完成后更新

执行您要做的事情的有效方法是创建一个DrawingGroup&#34; backingStore&#34;为您的图表。 OnRender()需要做的唯一事情是将backingStore添加到DrawingContext。然后,您可以随时使用backingStore.Open()并仅绘制它来更新它。 WPF将自动更新您的UI。

您会发现StreamGeometry是绘制到DrawingContext的最快方式,因为它会优化非动画几何体。

您还可以在笔上使用.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变换进行平移和缩放。