Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's an idiomatic XAML TreeView with CollectionViewGroup groupings?

I have a TreeView that looks like this:

<TreeView Grid.Row="1" x:Name="InspectionResultsTreeView"
          ItemsSource="{Binding Source={StaticResource InspectionTypeGroupViewSource}, Path=Groups}"
          ItemTemplate="{StaticResource InspectionTypeGroupsTemplate}">
</TreeView>

The ItemsSource is a keyed resource that goes by the name of InspectionTypeGroupViewSource:

<CollectionViewSource x:Key="InspectionTypeGroupViewSource" Source="{Binding Results}">
    <CollectionViewSource.GroupDescriptions>
        <PropertyGroupDescription PropertyName="Inspection.InspectionType" />
        <PropertyGroupDescription PropertyName="Inspection" />
    </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

The role of this little thing is to take the ViewModel's Results property:

private ObservableCollection<ICodeInspectionResult> _results;

public ObservableCollection<ICodeInspectionResult> Results
{
    get { return _results; } 
    set { _results = value; OnPropertyChanged(); }
}

...and group it on two levels - first by InspectionType, then by Inspection - the result is a 3-level hierarchy with inspection types, inspections, and then individual inspection results. At this point a screenshot might help visualizing I guess:

Rubberduck 2.0 Code Inspections toolwindow

So, the ItemTemplate of the InspectionResultsTreeView is another keyed resource, by the name of InspectionTypeGroupsTemplate - that's the bold "inspection type" items:

<HierarchicalDataTemplate x:Key="InspectionTypeGroupsTemplate" 
                            DataType="{x:Type CollectionViewGroup}"
                            ItemsSource="{Binding Items}"
                            ItemTemplate="{StaticResource InspectionGroupsTemplate}">

    <StackPanel Orientation="Horizontal">
        <TextBlock VerticalAlignment="Center" 
                   Text="{Binding Name}"
                   FontWeight="Bold"
                   TextWrapping="NoWrap"/>
        <TextBlock Margin="4,0,4,0" 
                   VerticalAlignment="Center" 
                   Text="{Binding ItemCount, StringFormat=({0})}" 
                   TextWrapping="NoWrap"/>
    </StackPanel>
</HierarchicalDataTemplate>

And the ItemTemplate of that template is an InspectionGroupsTemplate - that's the individual inspections, with the "severity" icons:

<HierarchicalDataTemplate x:Key="InspectionGroupsTemplate"
                          DataType="{x:Type CollectionViewGroup}"
                          ItemsSource="{Binding Items}"
                          ItemTemplate="{StaticResource InspectionResultTemplate}">

    <StackPanel Orientation="Horizontal">
        <Image Style="{StaticResource IconStyle}" 
               Source="{Binding Name, Converter={StaticResource InspectionIconConverter}}"
               VerticalAlignment="Center" />
        <TextBlock Margin="4" 
                   VerticalAlignment="Center" 
                   Text="{Binding Name, Converter={StaticResource InspectionDescriptionConverter}}"
                   TextWrapping="NoWrap"/>
        <TextBlock Margin="0,4,0,4" 
                   VerticalAlignment="Center" 
                   Text="{Binding ItemCount, StringFormat=({0})}" 
                   TextWrapping="NoWrap"/>
    </StackPanel>
</HierarchicalDataTemplate>

Lastly, the ItemTemplate of this grouping is an InspectionResultTemplate, which is for each individual inspection result:

<DataTemplate x:Key="InspectionResultTemplate" 
              DataType="{x:Type inspections:ICodeInspectionResult}">
    <StackPanel Orientation="Horizontal">
        <TextBlock VerticalAlignment="Center" 
                   Margin="4"
                   Text="{Binding Name}" 
                   TextWrapping="NoWrap"/>
    </StackPanel>
</DataTemplate>

The ICodeInspectionResult interface has a string Name property that I'm using here; this Name is different from the Name that's used in the grouping levels, where it's an object CollectionViewGroup.Name - the underlying type of that Name is that of the grouping, so level 1 is an InspectionType, and level 2 is an Inspection.

The problem is that I'm using more converters than I believe I'd need to, to convert this object Name and access the members I need to access and display... but then, I need to display the number of items in each grouping so the DataType ought to be a CollectionViewGroup... right?

How can I do this without resorting to a converter for everything that needs to be displayed? How is this supposed to be done? Every TreeView / CollectionViewGroup tutorial I could find was a trivial implementation.

like image 727
Mathieu Guindon Avatar asked Sep 16 '15 00:09

Mathieu Guindon


Video Answer


1 Answers

You've encountered the iconic problem with XAML: it's almost too structured.

The most idiomatic solution is writing a custom WPF User Control. (How and what you include in it is up you.) The goal of the WPF User Control is to eliminate the duplicate XAML markup and logic. You can include your Converter in the User Control, and eliminate the converters from your main control.

There are plenty of tutorials on creating UserControl objects in WPF, so I'll not go into detail here.


As far as the Converter issue: this is almost the most idiomatic way. Each converter is reusable, and focuses only on one source type. There's not much else you can do about it, except consider merging converters that support the same source type together. (There's a reason the converter has a Type targetType parameter, and an object parameter.)

like image 58
Der Kommissar Avatar answered Nov 04 '22 07:11

Der Kommissar