Implementazione di un visualizzatore di registri con WPF

Cerco consigli per l’approccio migliore per implementare un visualizzatore di registri di console con WPF.

Dovrebbe corrispondere ai seguenti criteri:

  • scorrimento veloce con oltre 100.000 righe
  • Alcune voci (come stacktraces) dovrebbero essere pieghevoli
  • articoli lunghi avvolgere
  • l’elenco può essere filtrato in base a criteri diversi (ricerca, tag, ecc.)
  • quando alla fine, dovrebbe continuare a scorrere quando vengono aggiunti nuovi elementi
  • Gli elementi linea possono contenere una sorta di formattazione aggiuntiva come collegamenti ipertestuali e contatori di eventi

In generale, ho in mente qualcosa come la finestra della console di FireBug e Chrome.

Ho giocato con questo, ma non ho fatto molti progressi, perché … – il datagrid non può gestire altezze di oggetti diversi – la posizione di scorrimento viene aggiornata solo dopo aver rilasciato la barra di scorrimento (che è completamente inaccettabile).

Sono abbastanza sicuro, ho bisogno di qualche forma di virtualizzazione e mi piacerebbe seguire lo schema MVVM.

Qualsiasi aiuto o indicazioni sono ben accetti.

Dovrei iniziare a vendere questi campioni WPF invece di darli gratuitamente. = P

inserisci la descrizione dell'immagine qui

  • Interfaccia utente virtualizzata (utilizzando VirtualizingStackPanel ) che offre prestazioni incredibilmente buone (anche con oltre 200000 voci)
  • Completamente MVVM-friendly.
  • DataTemplate per ogni tipo di tipo LogEntry . Questi ti danno la possibilità di personalizzare quanto vuoi. Ho implementato solo 2 tipi di LogEntries (base e nidificati), ma tu hai capito l’idea. È ansible LogEntry sottoclass di LogEntry quanto necessario. Potresti anche supportare il rich text o le immagini.
  • Elementi espandibili (nidificati).
  • Word Wrap.
  • È ansible implementare il filtro, ecc. Utilizzando un CollectionView .
  • Rocce WPF, basta copiare e incollare il mio codice in un File -> New -> WPF Application e vedere i risultati per te.

                                                               

Codice dietro: (Si noti che la maggior parte di esso è solo boileplate per supportare l’esempio (genera voci casuali)

  public partial class LogViewer : Window { private string TestData = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"; private List words; private int maxword; private int index; public ObservableCollection LogEntries { get; set; } public LogViewer() { InitializeComponent(); random = new Random(); words = TestData.Split(' ').ToList(); maxword = words.Count - 1; DataContext = LogEntries = new ObservableCollection(); Enumerable.Range(0, 200000) .ToList() .ForEach(x => LogEntries.Add(GetRandomEntry())); Timer = new Timer(x => AddRandomEntry(), null, 1000, 10); } private System.Threading.Timer Timer; private System.Random random; private void AddRandomEntry() { Dispatcher.BeginInvoke((Action) (() => LogEntries.Add(GetRandomEntry()))); } private LogEntry GetRandomEntry() { if (random.Next(1,10) > 1) { return new LogEntry() { Index = index++, DateTime = DateTime.Now, Message = string.Join(" ", Enumerable.Range(5, random.Next(10, 50)) .Select(x => words[random.Next(0, maxword)])), }; } return new CollapsibleLogEntry() { Index = index++, DateTime = DateTime.Now, Message = string.Join(" ", Enumerable.Range(5, random.Next(10, 50)) .Select(x => words[random.Next(0, maxword)])), Contents = Enumerable.Range(5, random.Next(5, 10)) .Select(i => GetRandomEntry()) .ToList() }; } } 

Dati:

 public class LogEntry: PropertyChangedBase { public DateTime DateTime { get; set; } public int Index { get; set; } public string Message { get; set; } } public class CollapsibleLogEntry: LogEntry { public List Contents { get; set; } } 

PropertyChangedBase:

  public class PropertyChangedBase:INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { Application.Current.Dispatcher.BeginInvoke((Action) (() => { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); })); } } 

La risposta HighCore è perfetta, ma suppongo che manchi questo requisito: “quando alla fine, dovrebbe continuare a scorrere quando vengono aggiunti nuovi elementi”.

Secondo questa risposta, puoi farlo:

Nel Main ScrollViewer (all’interno del DockPanel), aggiungi l’evento:

  

Trasmetti la fonte dell’evento per fare lo scroll automatico:

  private bool AutoScroll = true; private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { // User scroll event : set or unset autoscroll mode if (e.ExtentHeightChange == 0) { // Content unchanged : user scroll event if ((e.Source as ScrollViewer).VerticalOffset == (e.Source as ScrollViewer).ScrollableHeight) { // Scroll bar is in bottom // Set autoscroll mode AutoScroll = true; } else { // Scroll bar isn't in bottom // Unset autoscroll mode AutoScroll = false; } } // Content scroll event : autoscroll eventually if (AutoScroll && e.ExtentHeightChange != 0) { // Content changed and autoscroll mode set // Autoscroll (e.Source as ScrollViewer).ScrollToVerticalOffset((e.Source as ScrollViewer).ExtentHeight); } } }