2017-06-02 167 views
1

我還沒有發現任何有用的無論是谷歌或堆棧溢出或根本沒有答案(或者我只是不知道要搜索什麼) - 我可以得到的最接近的問題是這一個:The reason behind slow performance in WPFWPF MouseMove InvalidateVisual OnRender更新非常慢

但我想在這個簡單的程序中得到這個滯後的底部,也許我只是沒有做正確的事情。

我在UI元素的OnRender()中使用它們之間的連線渲染了大約2000個點,本質上創建了一個折線圖。沒關係,但我想用MouseMove平移圖形。這工作正常,但它是問題的LAG。每當用鼠標拖動時我都會期待一個平滑的更新,我認爲用它們之間的連線重新繪製2000點將會是在公園裏散步的一個i5 CPU。但即使在家中筆記本電腦的低分辨率下,速度也非常慢。所以我查了一下Performance Profiler。 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

現在,我聽說過這個詞視覺樹踢一下,但是在幾乎沒有任何的視覺效果這個簡單的項目。只是一個主窗口上的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> 
+2

更好地使用TranslateTransform而不是在平移時重新渲染所有內容。您也可以繪製折線而不是2000行。 – Clemens

+0

我在這個問題/答案中找到了更相關的解決方案https://stackoverflow.com/questions/25450979/terrible-performance-of-custom-drawn-control(使用Translate Transform)。乾杯 – pm101

回答

2

不要叫InvalidateVisual()這一點。它會觸發您的用戶界面的完整重新佈局,這非常緩慢。

WPF良好性能的關鍵是理解它是一個保留繪圖系統。 OnRender()的確應該被命名爲AccumulateDrawingObjects()。它只在佈局過程結束時使用,並且它正在積累的對象實際上是活動對象,您可以在完成後更新


要做你正在做的事情的有效方法是爲你的圖表創建一個DrawingGroup「backingStore」。 OnRender()唯一需要做的就是將backingStore添加到DrawingContext中。然後,您可以隨時通過使用backingStore.Open()更新它,然後只需繪製它。 WPF會自動更新你的用戶界面。

您會發現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變換進行平移和縮放。