2012-11-09 6 views
5

Ich zeichne eine Kopfzeile für eine Zeitleistensteuerung. Es sieht wie folgt aus: enter image description here10000 + UI-Elemente, binden oder zeichnen?

ich pro Zeile auf 0,01 Millisekunden gehen, also für 10 Minuten Timeline ich bei Zeichnung 60000 Linien + 6000 Etiketten suchen. Das dauert eine Weile, ~ 10 Sekunden. Ich möchte dies vom UI-Thread herunterladen. Mein Code ist zur Zeit:

private void drawHeader() 
{ 
    Header.Children.Clear(); 
    switch (viewLevel) 
    { 
    case ViewLevel.MilliSeconds100: 
     double hWidth = Header.Width; 
     this.drawHeaderLines(new TimeSpan(0, 0, 0, 0, 10), 100, 5, hWidth); 

     //Was looking into background worker to off load UI 

     //backgroundWorker = new BackgroundWorker(); 

     //backgroundWorker.DoWork += delegate(object sender, DoWorkEventArgs args) 
     //        { 
     //         this.drawHeaderLines(new TimeSpan(0, 0, 0, 0, 10), 100, 5, hWidth); 
     //        }; 
     //backgroundWorker.RunWorkerAsync(); 
     break; 
    } 
} 

private void drawHeaderLines(TimeSpan timeStep, int majorEveryXLine, int distanceBetweenLines, double headerWidth) 
{ 
var currentTime = new TimeSpan(0, 0, 0, 0, 0); 
const int everyXLine100 = 10; 
double currentX = 0; 
var currentLine = 0; 
while (currentX < headerWidth) 
{ 
    var l = new Line 
       { 
        ToolTip = currentTime.ToString(@"hh\:mm\:ss\.fff"), 
        StrokeThickness = 1, 
        X1 = 0, 
        X2 = 0, 
        Y1 = 30, 
        Y2 = 25 
       }; 
    if (((currentLine % majorEveryXLine) == 0) && currentLine != 0) 
    { 
     l.StrokeThickness = 2; 
     l.Y2 = 15; 
     var textBlock = new TextBlock 
          { 
           Text = l.ToolTip.ToString(), 
           FontSize = 8, 
           FontFamily = new FontFamily("Tahoma"), 
           Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255)) 
          }; 

     Canvas.SetLeft(textBlock, (currentX - 22)); 
     Canvas.SetTop(textBlock, 0); 
     Header.Children.Add(textBlock); 
    } 

    if ((((currentLine % everyXLine100) == 0) && currentLine != 0) 
     && (currentLine % majorEveryXLine) != 0) 
    { 
     l.Y2 = 20; 
     var textBlock = new TextBlock 
          { 
           Text = string.Format(".{0}", TimeSpan.Parse(l.ToolTip.ToString()).Milliseconds), 
                  FontSize = 8, 
                  FontFamily = new FontFamily("Tahoma"), 
                  Foreground = new SolidColorBrush(Color.FromRgb(192, 192, 192)) 
          }; 

     Canvas.SetLeft(textBlock, (currentX - 8)); 
     Canvas.SetTop(textBlock, 8); 
     Header.Children.Add(textBlock); 
    } 
    l.Stroke = new SolidColorBrush(Color.FromRgb(255, 255, 255)); 
    Header.Children.Add(l); 
    Canvas.SetLeft(l, currentX); 

    currentX += distanceBetweenLines; 
    currentLine++; 
    currentTime += timeStep; 
} 
} 

ich in Backgroundausgesehen hatte, außer dass Sie nicht UI-Elemente auf einem Nicht-UI-Thread erstellen können.

Ist es überhaupt möglich, drawHeaderLines in einem Nicht-UI-Thread zu tun?

Kann ich Datenbindung zum Zeichnen der Linien verwenden? Würde dies bei der Reaktionsfähigkeit der Benutzeroberfläche helfen?

Ich könnte mir vorstellen, dass ich Datenbindung verwenden kann, aber das Styling ist wahrscheinlich jenseits meiner aktuellen WPF-Fähigkeit (von Winforms kommend und versuche zu erfahren, was all diese Stilobjekte sind und sie binden).

Wäre jemand in der Lage, einen Ausgangspunkt für die Verlockung dieses liefern? Oder Google ein Tutorial, mit dem ich anfangen könnte?

+0

Welche Version von .NET verwenden Sie? Mit 4.5 kann das etwas einfacher gestaltet werden. – Servy

+1

Ich kann .NET 4.5 verwenden – jpiccolo

+1

Ich habe Ihren Titel bearbeitet. Bitte lesen Sie "[Sollten die Fragen" Tags "in ihren Titeln enthalten?] (Http://meta.stackexchange.com/questions/19190/)", wobei der Konsens "nein, sie sollten nicht" lautet. –

Antwort

0

Nun, ich hatte dies auf die Back-Brenner für ein wenig, aber.

Aber ich denke, ich habe eine Lösung gefunden.

Verwenden von DrawingVisual und DrawingContext.

Dieser Blog-Eintrag half mir ziemlich viel: Simple WPF 2D Graphics: DrawingVisual

Zuerst müssen wir unsere Sicht Klasse erhalten (diesen Code beachten Sie hat zusammen geschlagen worden, Reinigung ist eine gute Idee):

public class MyVisualHost : FrameworkElement 
{ 
private readonly VisualCollection children; 

public MyVisualHost(int width) 
{ 
    children = new VisualCollection(this); 

    var visual = new DrawingVisual(); 
    children.Add(visual); 

    var currentTime = new TimeSpan(0, 0, 0, 0, 0); 
    const int everyXLine100 = 10; 
    double currentX = 0; 
    var currentLine = 0; 
    double distanceBetweenLines = 5; 
    TimeSpan timeStep = new TimeSpan(0, 0, 0, 0, 10); 
    int majorEveryXLine = 100; 

    var grayBrush = new SolidColorBrush(Color.FromRgb(192, 192, 192)); 
    grayBrush.Freeze(); 
    var grayPen = new Pen(grayBrush, 2); 
    var whitePen = new Pen(Brushes.White, 2); 
    grayPen.Freeze(); 
    whitePen.Freeze(); 

    using (var dc = visual.RenderOpen()) 
    { 
     while (currentX < width) 
     { 
      if (((currentLine % majorEveryXLine) == 0) && currentLine != 0) 
      { 
       dc.DrawLine(whitePen, new Point(currentX, 30), new Point(currentX, 15)); 

       var text = new FormattedText(
        currentTime.ToString(@"hh\:mm\:ss\.fff"), 
        CultureInfo.CurrentCulture, 
        FlowDirection.LeftToRight, 
        new Typeface("Tahoma"), 
        8, 
        grayBrush); 

       dc.DrawText(text, new Point((currentX - 22), 0)); 
      } 
      else if ((((currentLine % everyXLine100) == 0) && currentLine != 0) 
         && (currentLine % majorEveryXLine) != 0) 
      { 
       dc.DrawLine(grayPen, new Point(currentX, 30), new Point(currentX, 20)); 

       var text = new FormattedText(
        string.Format(".{0}", currentTime.Milliseconds), 
        CultureInfo.CurrentCulture, 
        FlowDirection.LeftToRight, 
        new Typeface("Tahoma"), 
        8, 
        grayBrush); 
       dc.DrawText(text, new Point((currentX - 8), 8)); 
      } 
      else 
      { 
       dc.DrawLine(grayPen, new Point(currentX, 30), new Point(currentX, 25)); 
      } 

      currentX += distanceBetweenLines; 
      currentLine++; 
      currentTime += timeStep; 
     } 
    } 
} 

// Provide a required override for the VisualChildrenCount property. 
protected override int VisualChildrenCount { get { return children.Count; } } 

// Provide a required override for the GetVisualChild method. 
protected override Visual GetVisualChild(int index) 
{ 
    if (index < 0 || index >= children.Count) 
    { 
     throw new ArgumentOutOfRangeException(); 
    } 

    return children[index]; 
} 
} 

Weiter wir brauchen, um unsere Bindung:

public static readonly DependencyProperty HeaderDrawingVisualProperty = DependencyProperty.Register("HeaderDrawingVisual", typeof(MyVisualHost), typeof(MainWindow)); 

public MyVisualHost VisualHost 
{ 
    get { return (MyVisualHost)GetValue(HeaderDrawingVisualProperty); } 
    set { SetValue(HeaderDrawingVisualProperty, value); } 
} 

XAML:

<Canvas x:Name="Header" Background="#FF2D2D30" Grid.Row="0"> 
    <ContentPresenter Content="{Binding HeaderDrawingVisual}" /> 
</Canvas> 

Dann setzen Sie einfach in Code:

Header.Width = 50000; 
VisualHost = new MyVisualHost(50000); 

Meine Tests zeigten, eine Breite von 50000 mit dieser neuen Methode, ich sah einen großen Anstieg!

Old way Total Milliseconds: 277.061 
New way Total Milliseconds: 13.9982 
Old way Total Milliseconds: 518.4632 
New way Total Milliseconds: 12.9423 
Old way Total Milliseconds: 479.1846 
New way Total Milliseconds: 23.4987 
Old way Total Milliseconds: 477.1366 
New way Total Milliseconds: 12.6469 
Old way Total Milliseconds: 481.3118 
New way Total Milliseconds: 12.9678 

Zeit ist weniger das erste Mal, weil Gegenstände, wenn sie gelöscht haben wieder aufzubauen.

2

Also, wie Sie gesagt haben, all diese Arbeit muss im UIhread getan werden; Sie können es nicht einfach in einem Hintergrundthread tun.

Wenn Sie jedoch eine sehr lange Schleife ausführen, die viele UI-Änderungen im UI-Thread ausführt, wird der UI-Thread blockiert. Wir können das also nicht tun.

Der Schlüssel hier ist, dass Sie in viele kleinere Arbeitseinheiten aufteilen müssen, was Sie tun, und dann alle diese kleinen Arbeitseinheiten im UIhread ausführen. Der UI-Thread macht genau so viel Arbeit wie zuvor (möglicherweise sogar ein bisschen mehr, aufgrund des Overheads, all dies zu verwalten), aber er ermöglicht andere Dinge (wie Mausbewegung/Klickereignisse, Tastenbetätigungen usw.) zwischen diesen Aufgaben.

Hier ist ein einfaches Beispiel:

private void button1_Click(object sender, EventArgs e) 
{ 
    TaskScheduler uiContext = TaskScheduler.FromCurrentSynchronizationContext(); 
    Task.Run(async() => 
    { 
     for (int i = 0; i < 1000; i++) 
     { 
      await Task.Factory.StartNew(() => 
      { 
       Controls.Add(new Label() { Text = i.ToString() }); 
      }, CancellationToken.None, TaskCreationOptions.None, uiContext); 
     } 
    }); 
} 

Zuerst haben wir den Benutzeroberfläche Kontext greifen, während in dem UI-Thread, dann starten wir einen neuen Thread im Hintergrund, die für das Anfahren all unsere kleinen Aufgaben zuständig sein wird . Hier hätten Sie den Anfang Ihrer Schleife. (Sie können dies auf eine andere Methode anwenden, da Ihre nicht so einfach ist.) Starten Sie unmittelbar danach innerhalb der Schleife eine neue Aufgabe, und starten Sie diese Aufgabe im Kontext der Benutzeroberfläche. Innerhalb dieser Aufgabe können Sie dann den gesamten Körper dessen, was in Ihrer Schleife war, platzieren. Durch await In dieser Aufgabe stellen Sie sicher, dass jeder als eine Fortsetzung des vorherigen geplant ist, so dass alle in der Reihenfolge ausgeführt werden. Wenn die Reihenfolge keine Rolle spielt (was unwahrscheinlich, aber möglich ist), müssen Sie gar nicht warten.

Wenn Sie eine C# 4.0-Version von diesem benötigen, können Sie einen Task aus der Schleife heraushalten, und in jeder Iteration eine neue Aufgabe als eine Fortsetzung der vorherigen verknüpfen und dann "sich selbst" als diese Aufgabe festlegen. Es wäre jedoch unordentlicher.

0

Dies ist nur ein Beweis für das Konzept, aber ich denke, Sie könnten ListView-Virtualisierung verwenden. Anstatt Text haben Sie nur eine Sammlung von Bildern (ein Bild für jeweils 10 Zeilen). Mit der Virtualisierung rendert es nur, was in der Anzeige ist.

<Grid> 
    <Grid.ColumnDefinitions> 
     <ColumnDefinition Width="*"/> 
    </Grid.ColumnDefinitions> 
    <Grid.RowDefinitions> 
     <RowDefinition Height="*"/> 
    </Grid.RowDefinitions> 
    <ListView Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=Numbers}" Width="100" Height="500"> 
     <ListView.RenderTransform> 
      <RotateTransform Angle="90" CenterX="150" CenterY="150"/> 
     </ListView.RenderTransform> 
    </ListView> 
</Grid> 




public List<string> Numbers 
{ 
    get 
    { 
     List<string> numbers = new List<string>(); 
     for (UInt16 i=1; i < 60000; i++) 
     { 
      numbers.Add(i.ToString()); 
     } 
     return numbers; 
    } 
} 
Verwandte Themen