2012-11-09 5 views
5

Sto disegnando un'intestazione per un controllo della timeline. Ecco come si presenta: enter image description here10000 + Elementi dell'interfaccia utente, bind o pareggio?

vado a 0,01 millisecondi per linea, quindi per una timeline 10 minuti Sto cercando di disegnare linee 60000 + 6000 etichette. Questo richiede un po ', ~ 10 secondi. Mi piacerebbe scaricare questo dal thread dell'interfaccia utente. Il mio codice è attualmente:

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

Avevo guardato in BackgroundWorker, tranne che non è possibile creare elementi dell'interfaccia utente su un thread non-UI.

È possibile eseguire drawHeaderLines in un thread non dell'interfaccia utente?

Posso utilizzare l'associazione dati per disegnare le linee? Questo potrebbe aiutare con la reattività dell'interfaccia utente?

Immagino di poter usare il databinding, ma lo stile è probabilmente al di là della mia attuale abilità WPF (proveniente da winforms e sto cercando di imparare cosa sono tutti questi oggetti di stile e li legano).

Qualcuno sarebbe in grado di fornire un punto di partenza per tentare questo fuori? O Google un tutorial che potrebbe iniziare?

+0

Quale versione di .NET stai usando? Usando 4.5 questo può essere fatto per essere un po 'più semplice. – Servy

+1

Sono in grado di utilizzare .NET 4.5 – jpiccolo

+1

Ho modificato il titolo. Per favore vedi, "[Le domande dovrebbero includere" tag "nei loro titoli?] (Http://meta.stackexchange.com/questions/19190/)", dove il consenso è "no, non dovrebbero". –

risposta

0

Beh, avevo messo questo sul retro del bruciatore per un po 'ma.

Ma penso di aver trovato una soluzione.

Per utilizzare DrawingVisual e DrawingContext.

Questo post del blog mi ha aiutato un po ': Simple WPF 2D Graphics: DrawingVisual

In primo luogo abbiamo bisogno di ottenere la nostra classe Visuale (notare che questo codice è stato schiaffeggiato insieme, la pulizia è una buona idea):

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]; 
} 
} 

Successivo abbiamo bisogno della nostra associazione:

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> 

Poi basta impostare in codice:

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

mio test ha mostrato, con una larghezza di 50000, con questo nuovo metodo, ho visto un grande aumento!

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 

Il tempo è inferiore la prima volta perché gli elementi devono essere cancellati durante la ricostruzione.

2

Quindi, come hai detto, tutto questo lavoro deve essere fatto nel thread dell'interfaccia utente; non puoi farlo solo in un thread in background.

Tuttavia, l'esecuzione di un ciclo molto lungo che esegue molte modifiche all'interfaccia utente nel thread dell'interfaccia utente blocca il thread dell'interfaccia utente, quindi chiaramente non possiamo farlo.

La chiave qui è che è necessario suddividere ciò che si sta facendo in molte unità di lavoro più piccole, e quindi fare tutte quelle piccole unità di lavoro nel thread dell'interfaccia utente. Il thread dell'interfaccia utente sta facendo lo stesso lavoro di prima (forse anche un po 'di più, a causa del sovraccarico di gestione di tutto questo) ma consente altre cose (come gli eventi di spostamento/clic del mouse, la pressione dei tasti, ecc.) tra queste attività.

Ecco un semplice esempio:

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

In primo luogo abbiamo afferrare il contesto interfaccia utente, mentre nel thread UI, quindi iniziamo un nuovo thread in background che sarà responsabile per la messa in tutti i nostri piccoli compiti . Qui avresti l'inizio del tuo ciclo. (Potresti voler estrapolarlo con un altro metodo, dato che il tuo non è così semplice.) Quindi, immediatamente all'interno del ciclo, avvia una nuova attività e l'attività viene avviata nel contesto dell'interfaccia utente. All'interno di tale attività puoi quindi posizionare l'intero corpo di ciò che era nel tuo ciclo. Con await su quell'attività si assicura che ciascuno sia programmato come una continuazione del precedente, in modo che vengano eseguiti tutti in ordine. Se l'ordine non ha importanza (il che è improbabile, ma possibile), non è necessario attendere affatto.

Se è necessaria una versione C# 4.0 di questo, è possibile mantenere un ciclo Task fuori dal ciclo e in ogni iterazione collegare un nuovo task come una continuazione del precedente e quindi impostare "stesso" come tale attività. Sarebbe comunque peggio.

0

Questa è solo una dimostrazione del concetto ma penso che si possa utilizzare la virtualizzazione di ListView. Invece del testo, è sufficiente disporre di una raccolta di immagini (un'immagine per ogni 10 righe). Con la virtualizzazione rende solo ciò che è in mostra.

<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; 
    } 
} 
Problemi correlati