I should start selling these WPF samples, not giving them for free. = P

- A virtualized user interface (using
VirtualizingStackPanel ) that delivers incredibly good performance (even with 200,000+ elements) - Fully compatible with MVVM.
DataTemplate for each type of type LogEntry . This gives you the opportunity you want. I implemented only 2 types of LogEntries (basic and nested), but you got the idea. You can subclass LogEntry as much as you need. You can even support rich text or images.- Expandable (nested) elements.
- Word Wrap
- You can implement filtering, etc. using
CollectionView . WPF Rocks, just copy and paste my code into File -> New -> WPF Application and see the results for yourself.
<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: (Note that most of them are just boileplate to support the example (generating random entries)
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() }; } }
Data Elements:
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)); })); } }
Federico Berasategui May 24 '13 at 11:48 p.m. 2013-05-24 23:48
source share