2013-05-24 11 views
56

Ich suche Rat für die beste Methode zum Implementieren eines Konsolenprotokoll-Viewers mit WPF.Implementieren eines Protokoll-Viewers mit WPF

Es sollte die folgenden Kriterien entsprechen:

  • schnelles Scrollen mit 100.000+ Linien
  • Einige Einträge (wie stacktraces) sollten
  • Langgut wickeln
  • die Liste von faltbar sein gefiltert werden können verschiedene Kriterien (Suche, Tags, etc.)
  • wenn es am Ende weiter scrollen sollte, wenn neue Elemente hinzugefügt werden
  • Zeilenelemente können eine Art von Zusatzformatierung wie Hyperlinks und Vorkommenszähler enthalten.

Im Allgemeinen habe ich etwas im Sinn wie das Konsolenfenster von FireBug und Chrome.

spielte ich mit this herum, aber ich habe nicht viele Fortschritte machen, weil ... - das Datenraster nicht verschiedene Artikel Höhen handhaben kann - die Scroll-Position erst nach Freigabe der Scrollbar aktualisiert wird (was völlig inakzeptabel ist).

Ich bin ziemlich sicher, ich brauche eine Form der Virtualisierung und würde gerne dem MVVM-Muster folgen.

Jede Hilfe oder Hinweise sind willkommen.

+0

Sind Sie sicher, dass Sie Ihren eigenen Log Viewer implementieren müssen? Dadurch wird das Rad neu erfunden ... Können Sie Tools von Drittanbietern verwenden, um Ihre Protokolle anzuzeigen? Sie können beispielsweise [DbgView] (http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx) öffnen und Protokolle erfassen, die über die Windows-API gesendet werden. Sie können dann Protokolle senden, die in dem Tool erfasst werden, zum einfachen Durchsuchen und Filtern – Liel

+0

Ausgezeichnete Frage. Ich benötige diese Komponente als Teil einer bestehenden WPF-Anwendung. Wir haben bereits eine "Konsole", die als frustrierend langsame TextBox implementiert ist. Aber jetzt brauchen wir die zusätzlichen Funktionen, die ich beschrieben habe. Ich bin sehr froh, bestehende kommerzielle oder freie Nicht-GPL-Komponenten wiederzuverwenden. – pixtur

Antwort

145

Ich sollte anfangen, diese WPF-Proben zu verkaufen, anstatt sie kostenlos zu verteilen. = P

enter image description here

  • Virtualized UI (mit VirtualizingStackPanel), die unglaublich gute Leistung (auch mit 200000+ Artikel)
  • Fully MVVM freundlich zur Verfügung stellt.
  • DataTemplate s für jede Art von LogEntry Typ. Diese geben Ihnen die Möglichkeit, wie viel wie Sie möchten anpassen. Ich habe nur zwei Arten von LogEntries implementiert (Basic und Nested), aber Sie bekommen die Idee. Sie können die LogEntry so oft unterteilen, wie Sie benötigen. Sie können sogar Rich Text oder Bilder unterstützen.
  • Erweiterbare (verschachtelte) Elemente.
  • Wortumbruch.
  • Sie können die Filterung usw. mithilfe einer CollectionView implementieren.
  • WPF Rocks, kopieren Sie einfach und fügen Sie meinen Code in einem File -> New -> WPF Application und sehen Sie die Ergebnisse für sich.

    <Window x:Class="MiscSamples.LogViewer" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:MiscSamples" 
        Title="LogViewer" Height="500" Width="800"> 
    <Window.Resources> 
        <Style TargetType="ItemsControl" x:Key="LogViewerStyle"> 
         <Setter Property="Template"> 
          <Setter.Value> 
           <ControlTemplate> 
            <ScrollViewer CanContentScroll="True"> 
             <ItemsPresenter/> 
            </ScrollViewer> 
           </ControlTemplate> 
          </Setter.Value> 
         </Setter> 
    
         <Setter Property="ItemsPanel"> 
          <Setter.Value> 
           <ItemsPanelTemplate> 
            <VirtualizingStackPanel IsItemsHost="True"/> 
           </ItemsPanelTemplate> 
          </Setter.Value> 
         </Setter> 
        </Style> 
    
        <DataTemplate DataType="{x:Type local:LogEntry}"> 
         <Grid IsSharedSizeScope="True"> 
          <Grid.ColumnDefinitions> 
           <ColumnDefinition SharedSizeGroup="Index" Width="Auto"/> 
           <ColumnDefinition SharedSizeGroup="Date" Width="Auto"/> 
           <ColumnDefinition/> 
          </Grid.ColumnDefinitions> 
    
          <TextBlock Text="{Binding DateTime}" Grid.Column="0" 
             FontWeight="Bold" Margin="5,0,5,0"/> 
    
          <TextBlock Text="{Binding Index}" Grid.Column="1" 
             FontWeight="Bold" Margin="0,0,2,0" /> 
    
          <TextBlock Text="{Binding Message}" Grid.Column="2" 
             TextWrapping="Wrap"/> 
         </Grid> 
        </DataTemplate> 
    
        <DataTemplate DataType="{x:Type local:CollapsibleLogEntry}"> 
         <Grid IsSharedSizeScope="True"> 
          <Grid.ColumnDefinitions> 
           <ColumnDefinition SharedSizeGroup="Index" Width="Auto"/> 
           <ColumnDefinition SharedSizeGroup="Date" Width="Auto"/> 
           <ColumnDefinition/> 
          </Grid.ColumnDefinitions> 
    
          <Grid.RowDefinitions> 
           <RowDefinition Height="Auto"/> 
           <RowDefinition/> 
          </Grid.RowDefinitions> 
    
          <TextBlock Text="{Binding DateTime}" Grid.Column="0" 
             FontWeight="Bold" Margin="5,0,5,0"/> 
    
          <TextBlock Text="{Binding Index}" Grid.Column="1" 
             FontWeight="Bold" Margin="0,0,2,0" /> 
    
          <TextBlock Text="{Binding Message}" Grid.Column="2" 
             TextWrapping="Wrap"/> 
    
          <ToggleButton x:Name="Expander" Grid.Row="1" Grid.Column="0" 
              VerticalAlignment="Top" Content="+" HorizontalAlignment="Right"/> 
    
          <ItemsControl ItemsSource="{Binding Contents}" Style="{StaticResource LogViewerStyle}" 
              Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" 
              x:Name="Contents" Visibility="Collapsed"/> 
    
         </Grid> 
         <DataTemplate.Triggers> 
          <Trigger SourceName="Expander" Property="IsChecked" Value="True"> 
           <Setter TargetName="Contents" Property="Visibility" Value="Visible"/> 
           <Setter TargetName="Expander" Property="Content" Value="-"/> 
          </Trigger> 
         </DataTemplate.Triggers> 
        </DataTemplate> 
    </Window.Resources> 
    
    <DockPanel> 
        <TextBlock Text="{Binding Count, StringFormat='{}{0} Items'}" 
           DockPanel.Dock="Top"/> 
    
        <ItemsControl ItemsSource="{Binding}" Style="{StaticResource LogViewerStyle}"> 
         <ItemsControl.Template> 
          <ControlTemplate> 
           <ScrollViewer CanContentScroll="True"> 
            <ItemsPresenter/> 
           </ScrollViewer> 
          </ControlTemplate> 
         </ItemsControl.Template> 
         <ItemsControl.ItemsPanel> 
          <ItemsPanelTemplate> 
           <VirtualizingStackPanel IsItemsHost="True"/> 
          </ItemsPanelTemplate> 
         </ItemsControl.ItemsPanel> 
        </ItemsControl> 
    </DockPanel> 
    </Window> 
    

-Code Behind: (Beachten Sie, dass die meisten davon nur das Beispiel zu unterstützen ist boileplate (erzeugen zufällige Einträge)

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<string> words; 
     private int maxword; 
     private int index; 

     public ObservableCollection<LogEntry> LogEntries { get; set; } 

     public LogViewer() 
     { 
      InitializeComponent(); 

      random = new Random(); 
      words = TestData.Split(' ').ToList(); 
      maxword = words.Count - 1; 

      DataContext = LogEntries = new ObservableCollection<LogEntry>(); 
      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() 
         }; 

     } 
    } 

Daten Artikel:

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

Wow! Hast du das gerade geschrieben ?! Das ist wirklich erstaunlich. Ich habe es gerade getestet und es ist so ziemlich die perfekte Antwort auf meine Frage. Es sieht so aus, als würde man die Details präzisieren. Ich bin hin und weg. Danke vielmals! – pixtur

+0

Wenn Sie Fragen wie diese ab und zu beantworten würden, würde ich mich sehr freuen zu zahlen. :-) – pixtur

+0

Ein Problem, das ich gefunden habe, ist, dass, wenn ich das Mausrad auf einem verschachtelten Element scrollte, die Ansicht nicht scrollte. –

15

HighCore Antwort ist perfekt, aber ich denke, es ist diese Anforderung fehlt: „Wenn am Ende soll es eine Rolle halten, wenn neue Elemente hinzugefügt“.

Nach this Antwort, können Sie dies tun:

Im Hauptscroll (im DockPanel), fügen Sie die Veranstaltung:

<ScrollViewer CanContentScroll="True" ScrollChanged="ScrollViewer_ScrollChanged"> 

die Ereignisquelle Cast die Autoscroll zu tun:

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

AutoScroll-Variable ist eine Ausnahme – alerya

+1

Danke. Jetzt behoben. – drizin

+0

Dies funktioniert nicht und gibt mir gelegentlich eine Null-Ausnahme auf e.source – rolls

Verwandte Themen