2017-06-02 3 views
1

Je n'ai rien trouvé d'utile sur Google ou Stack Overflow ou simplement pas de réponse (ou peut-être que je ne sais pas quoi chercher) - la question la plus proche que je peux obtenir Est-ce que c'est un: The reason behind slow performance in WPFWPF MouseMove InvalidateVisual OnRender mise à jour très lente

Mais je veux aller au fond de ce décalage dans ce programme simple, peut-être que je ne fais pas juste quelque chose de bien.

Je rends environ 2000 points avec des lignes entre eux dans le OnRender() d'un élément d'interface utilisateur, créant essentiellement un graphique linéaire. C'est bon, mais je veux faire un panoramique sur le graphique avec MouseMove. Cela fonctionne bien, mais c'est le LAG qui pose problème. Chaque fois que je glisse avec la souris, je m'attendrais à une mise à jour en douceur, je pense que redessiner 2000 points avec des lignes entre eux serait une promenade dans le parc pour un processeur i5. Mais c'est incroyablement lent, même à basse résolution sur mon ordinateur portable à la maison. J'ai donc vérifié le Performance Profiler. La fonction OnRender() utilise à peine n'importe quel CPU.

MouseMove and OnRender hardly use much CPU

Il se trouve que c'est la mise en page qui change et en utilisant tant CPU.

The Layout is using most CPU

« Mise en page » prend le plus de temps pour compléter

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

Maintenant, je l'ai entendu le terme Arbre visuel piétinant, mais il n'y a guère de supports visuels ce projet simple. Juste un élément d'interface utilisateur sur une fenêtre principale. Et en utilisant un contexte de dessin, j'aurais pensé que le contexte du dessin dessinait comme un bitmap, ou est-ce qu'il dessine des éléments de l'interface utilisateur avec leurs propres événements/boîtes à boutons, etc.? Parce que tout ce que je veux, c'est que l'UIElement agisse comme une image mais aussi manipule les événements de la souris pour que je puisse faire glisser le tout (ou zoomer avec la molette de la souris).

Alors Questions:

  1. Si la mise en page est à l'origine de la lenteur/lag, comment puis-je empêcher cela?
  2. Je remarque également beaucoup de garbage collection qui est logique, mais je ne veux pas que cela se produise pendant le rendu. Je préfère faire ça pendant que c'est inactif. mais comment?

Voici la source:

.cs fichier

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)); 

     } 
    } 
} 

fichier 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

Mieux utiliser un TranslateTransform au lieu de tout re-rendre tout en panoramique. Vous pouvez également dessiner une polyligne au lieu de 2000 lignes. – Clemens

+0

J'ai trouvé une solution plus pertinente dans cette question/réponse https://stackoverflow.com/questions/25450979/terrible-performance-of-custom-drawn-control (utilise Translate Transform). à votre santé – pm101

Répondre

2

Ne pas appeler InvalidateVisual() pour cela. Il déclenche un relais complet de votre interface utilisateur, ce qui est très lent.

La clé de bonnes performances dans WPF est de comprendre qu'il s'agit d'un système de dessin retenu. OnRender() devrait vraiment être nommé AccumulateDrawingObjects(). Il n'est utilisé qu'à la fin du processus de mise en page et les objets qu'il accumule sont en réalité des objets vivants que vous pouvez mettre à jour après c'est terminé.


La façon efficace de faire ce que vous essayez de faire, est de créer un DrawingGroup « BackingStore » pour votre graphique. La seule chose que votre OnRender() doit faire est d'ajouter le backingStore au DrawingContext. Ensuite, vous pouvez le mettre à jour quand vous le voulez en utilisant backingStore.Open() et en y dessinant simplement. WPF mettra automatiquement à jour votre interface utilisateur.

Vous trouverez que StreamGeometry est le moyen le plus rapide de dessiner un DrawingContext, car il optimise la géométrie non-animée.

Vous pouvez également obtenir des performances supplémentaires en utilisant .Freeze() sur votre Pen, car il n'est pas animé. Bien que je doute que vous remarquerez en tirant seulement 2000 points.

Il ressemble à ceci:

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 
} 

Si vous souhaitez voir plus de code d'exemple, jetez un oeil ici:

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


Cependant, si le visuel est relativement statique, et tout ce que vous voulez faire est de faire un panoramique et de zoomer autour, il y a d'autres options. Vous pouvez créer un Canvas, et instancier des formes dans celui-ci, puis pendant le déplacement de la souris, vous modifiez la transformation de la toile pour effectuer un panoramique et un zoom.