Yael
Yael

Reputation: 31

Grouping a tree view with a different source

I am new in the WPF world, and I am trying to implement an application according to the MVVM design pattern. I am having some problems when it comes to using treeviews.

My Model is a list of log messages (from different applications) with the following properties: message, severity, application. I have one collection with 6 items that belong to two different applications. I want my tree view to look as follows:

App A
    Error
        First Error message (aaa)
        Second Error message (bbb)
    Warning
        First Warning message (ccc)
App B
    Warning
        First Warning message (ddd)
    Info
        First Info message (eee)
        Second Info message (fff)

My current understanding is that the expects to have an item with a list of children, so in order to create the view I want I will need to create a list that contains a string (application name) and a list of children (different severities) that will contain list of children( display messages).

That doesn't make sense to me because I am creating a dependency between my View and my Model, let's say in the future I will need to add another hierarchical layer, I will need to change my data structure in order to support it.

Is there a way to use only one list with different groupings?

Thanks.

Upvotes: 3

Views: 2101

Answers (2)

brunnerh
brunnerh

Reputation: 185057

You can easily group your data with a CollectionView, even in a nested way:

ObservableCollection<LogEntry> data = new ObservableCollection<LogEntry>(new LogEntry[]
{
    new LogEntry("App1", "Warning", "Msg1"),
    new LogEntry("App1", "Error", "Msg2"),
    new LogEntry("App1", "Warning", "Msg3"),
    new LogEntry("App2", "Error", "Msg4"),
    new LogEntry("App2", "Info", "Msg5"),
    new LogEntry("App2", "Info", "Msg6"),
});
ListCollectionView view = (ListCollectionView)CollectionViewSource.GetDefaultView(data);
view.GroupDescriptions.Add(new PropertyGroupDescription("Application"));
view.GroupDescriptions.Add(new PropertyGroupDescription("Severity"));
Data = view;

(You could also create the CollectionView view in XAML, this is just an example)

Now you can display this using DataTemlates:

<TreeView ItemsSource="{Binding Data.Groups}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type CollectionViewGroup}" ItemsSource="{Binding Items}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type local:LogEntry}">
            <TextBlock Text="{Binding Message}"/>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

Looks like this:

Upvotes: 4

user7116
user7116

Reputation: 64098

You are going to have to create a view, in the logical sense, which will create the nesting required for the hierarchical data templates. If I understand you correctly, you have a collection which represents the timeline of log events received by your application.

You could utilize some nested CollectionViewSources with a nested TreeView if you did not feel like altering the basic collection (adapted from this post). However, I do not know if this will respect online updates to the underlying collection I've decided is a property Messages on the data context:

<!-- First level grouping on the application name -->
<CollectionViewSource x:Key="ApplicationGroups"
                      Source="{Binding Messages}">
    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="Application" />
    </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

Then you set up the template for the application and severity levels:

<HierarchicalDataTemplate x:Key="ApplicationTemplate">
    <!-- Nested tree view for the severity -->
    <TreeView>
        <TreeView.Resources>
            <!-- Since this TreeView will receive a Group as its DataContext
                 we will bind to its Items property, containing the objects
                 which are a member of its group
             -->
            <CollectionViewSource x:Key="SeverityGroups"
                                  Source="{Binding Items}">
                <CollectionViewSource.GroupDescriptions>
                    <PropertyGroupDescription PropertyName="Severity" />
                </CollectionViewSource.GroupDescriptions>
            </CollectionViewSource>

            <!-- Message Template -->
            <DataTemplate x:Key="MessageTemplate">
                <TextBlock Text="{Binding Message}" />
            </DataTemplate>

            <!-- Severity Hierarchy Template -->
            <HierarchicalDataTemplate x:Key="SeverityTemplate"
                                      ItemsSource="{Binding Items}"
                                      ItemsTemplate="{StaticResource MessageTemplate}">
                <TextBlock Text="{Binding Name}" />
            </HierarchicalDataTemplate>
        </TreeView.Resources>

        <!-- Application sub-Tree View -->
        <TreeViewItem Header="{Binding Name}"
                      ItemsSource="{Binding Groups, Source={StaticResource SeverityGroups}}"
                      ItemTemplate="{StaticResource SeverityTemplate}" />
    </TreeView>
</HierarchicalDataTemplate>

You then have your TreeView set its ItemsSource to the Groups property of the collection view, like so:

<TreeView ItemsSource="{Binding Groups, Source={StaticResource ApplicationGroups}}"
          ItemTemplate="{StaticResource ApplicationTemplate}" />

Usually, rather than go through this hassle, I will create a translation layer which converts to a natural ViewModel hierarchy, however, if your severity levels may change this may be a better option.

Some commercial data grid solutions provide decent flexibility for nested grouping, however, I do not have enough experience with these to recommend one over the other.

Upvotes: 1

Related Questions