用WPF实现日志查看器

我寻求build议,以实现与WPF的控制台日志查看器的最佳方法。

它应该符合以下标准:

  • 快速滚动100.000+行
  • 一些条目(如堆栈跟踪)应该是可折叠的
  • 长项目包装
  • 该列表可以按不同的标准过滤(search,标签等)
  • 最后在添加新项目时应该保持滚动
  • 线元素可以包含某种添加格式,如超链接和计数器

总的来说,我有一些想法,比如FireBug和Chrome的控制台窗口。

我玩了这个,但我没有取得太多的进展,因为… – 数据网格无法处理不同的项目高度 – 滚动位置只是释放滚动条后更新(这是完全不可接受的)。

我很确定,我需要某种forms的虚拟化,并愿意遵循MVVM模式。

任何帮助或指针是受欢迎的。

我应该开始销售这些WPF样品,而不是免费赠送。 = P

在这里输入图像描述

  • 虚拟化UI(使用VirtualizingStackPanel ),提供令人难以置信的良好性能(即使有200000+项目)
  • 完全MVVM友好。
  • 每种LogEntrytypes的DataTemplate 。 这些给你能够尽可能多的自定义。 我只实现了两种LogEntry(基本和嵌套),但你明白了。 您可以根据需要对LogEntry子类化。 你甚至可以支持丰富的文字或图像。
  • 可扩展(嵌套)项目。
  • 单词包装。
  • 您可以通过使用CollectionView来实现过滤等。
  • WPF岩石,只需复制并粘贴我的代码在File -> New -> WPF Application ,看看你自己的结果。

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

代码后面:( 注意,它大部分只是boileplate来支持这个例子(生成随机条目)

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

数据项目:

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

HighCore的答案是完美的,但我猜测它缺less这个要求:“最后,当新的项目被添加时,它应该保持滚动”。

根据这个答案,你可以这样做:

在主要的ScrollViewer(在DockPanel内),添加事件:

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

投掷事件源做自动滚动:

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