Implementación de un visor de registro con WPF

Busco asesoramiento sobre el mejor enfoque para implementar un visor de registro de consola con WPF.

Debe coincidir con los siguientes criterios:

  • desplazamiento rápido con más de 100.000 líneas
  • Algunas entradas (como stacktraces) deberían ser plegables
  • artículos largos envuelven
  • la lista se puede filtrar por diferentes criterios (búsqueda, tags, etc.)
  • cuando al final, debe seguir desplazándose cuando se agregan nuevos elementos
  • Los elementos de línea pueden contener algún tipo de formato de adición como hipervínculos y contador de ocurrencias

En general, tengo algo en mente como la ventana de la consola de FireBug y Chrome.

Jugué con esto pero no hice mucho progreso, porque … – la cuadrícula de datos no puede manejar diferentes alturas de elementos – la posición de desplazamiento solo se actualiza después de soltar la barra de desplazamiento (lo cual es completamente inaceptable).

Estoy bastante seguro, necesito alguna forma de virtualización y me encantaría seguir el patrón MVVM.

Cualquier ayuda o punteros son bienvenidos.

Debería comenzar a vender estas muestras de WPF en lugar de darlas gratis. = P

enter image description here

  • Interfaz de usuario virtualizada (usando VirtualizingStackPanel ) que proporciona un rendimiento increíblemente bueno (incluso con más de 200 000 elementos)
  • Totalmente compatible con MVVM.
  • DataTemplate s para cada clase de tipo LogEntry . Estos te dan la posibilidad de personalizar todo lo que quieras. Solo implementé 2 tipos de LogEntries (básico y nested), pero se entiende la idea. Puede subclase LogEntry tanto como lo necesite. Incluso puede admitir texto o imágenes enriquecidas.
  • Artículos ampliables (nesteds).
  • Ajuste de línea.
  • Puede implementar el filtrado, etc. mediante el uso de un CollectionView .
  • WPF Rocks, solo copie y pegue mi código en un File -> New -> WPF Application y vea los resultados usted mismo.

                                                               

Código detrás: (Tenga en cuenta que la mayor parte es solo una placa de punta para apoyar el ejemplo (generar entradas aleatorias)

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

Elementos de datos:

 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 respuesta de HighCore es perfecta, pero supongo que falta este requisito: “cuando al final, debe seguir desplazándose cuando se agregan nuevos elementos”.

De acuerdo con esta respuesta, puedes hacer esto:

En el ScrollViewer principal (dentro del DockPanel), agregue el evento:

  

Emitir el origen del evento para hacer el desplazamiento automático:

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