繁体   English   中英

WPF MouseMove InvalidateVisual OnRender更新非常慢

[英]WPF MouseMove InvalidateVisual OnRender update VERY SLOW

我没有在Google或Stack Overflow上找到任何有用的东西,或者根本没有答案(或者我只是不知道要搜索什么) - 我能得到的最接近的问题是这个: 导致性能下降的原因WPF

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

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

MouseMove和OnRender几乎没有使用太多的CPU

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

布局使用的是大多数CPU

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

布局花费最多时间 它表示对Visual树进行了更改 - 但没有进行任何更改 - 只调用了InvalidateVisual

现在,我听说过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变换来平移和缩放。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM