What's an idiomatic XAML TreeView with CollectionViewGroup groupings? - c#

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

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

Related

Combobox not showing groupings

I'm trying to organize the items in a combobox into groups. To do this I've created an object that has project and group name strings. I then set the GroupStyle and ItemTemplate to display these values. However, Currently, only the project string is displayed in the combobox (and the box has a red border, indicating some kind of error).
Here's the xaml for my combobox:
<ComboBox x:Name="comboBoxProjects" Margin="165,90,28,0" Grid.Column="0" VerticalAlignment="Top" Height="25"
IsSynchronizedWithCurrentItem="True" SelectedIndex="0" Style="{StaticResource ComboBoxDefault}"
ItemsSource="{Binding Path=ProjectClientSelections.ProjectGroupItems,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=ProjectClientSelections.SelectedProject, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding GroupName}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Project}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Does anyone see where I'm going wrong?
In GroupStyle, the DataContext is not your item (the type contained in your ItemsSource), but a CollectionViewGroup object, which is formed based on the collection of items that you have grouped. Because of this you have to declare a binding path to one of the properties in CollectionViewGroup, for example, based on your code you probably want to use Name property. See MSDN CollectionViewGroup Class
Change your GroupStyle.HeaderTemplate to this:
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
You don't show how you have formed your GroupDescriptions. If you have not grouped the items already, you can do it in following way (assuming the XAML you have provided is contained inside Window and Window's and GroupBox's DataContext is the same):
<Window.Resources>
<CollectionViewSource
Source="{Binding ProjectClientSelections.ProjectGroupItems}"
x:Key="GroupedProjectItems">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription
PropertyName="GroupName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
After this change GroupBox ItemSource binding to the following (directly to CollectionViewSource resource):
ItemsSource="{Binding Source={StaticResource GroupedProjectItems}}"

ComboBox with grouping and custom group headers

I would like to create a grouped ComboBox with a group header which is bound to not only the group's name, but other properties of the values by which the 'ItemsSource' of the ComboBox is grouped as well.
This is the CollectionViewSource which is used as the ComboBoxes ItemsSource:
<CollectionViewSource x:Key="Tools" Source="{Binding AvailableTools}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Toolbox" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
The property by which I group, is not a string property, but an object of a custom type. The CollectionViewSource obviously creates groups and gives each group the name of [object by which is group].ToString(), yielding the type name of the property Toolbox in my example.
Here's the combo:
<ComboBox
SelectedItem="{Binding SelectedTool, Mode=TwoWay}"
Margin="10,0,0,0"
Width="40"
ItemsSource="{Binding Source={StaticResource Tools}}">
<ComboBox .GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate DataType="CollectionViewGroup">
<Grid Background="{StaticResource LighterBackgroundBrush}">
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox .GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate DataType="viewModels1:ToolSelectionItem">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Tool.ToolIcon}" Width="16" />
<TextBlock Text="{Binding ToolName}" Margin="10,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
In the header template, I can bind to the groups name but nothing else. What I would like to achieve, is a binding to the object by which a group was created and not just it's ToString() result. I.e. the values of the Toolbox property by which I group contain an Icon property of type BitmapImage which I would like to bind to an Image inside the header template to display this icon in the group header. Can this be done at all and if so, how?
The issue is a bit difficult to explain, please ask, if I am not being clear enough...

Databinding to a Dynamic property

In my application I have the following which works fine.
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,27.5">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
But now I want to bind a different sort of Items which have no Name property, but a Title property.
I've tried the following:
<TextBlock Text="{Binding {Binding MyDynamicProperty}}" />
So that I could set MyDynamicProperty in code to either Name or Title, but it crashes pretty soon.
Now I'm not really sure how I should go about this so that I can bind several different Items collections with a different property for the text to display.

WPF Binding to specific property

I'm new to DataBinding but would like to accomplish the following:
I have a ViewModel which has a collection of objects: CollectionOfStuff
Each object Stuff has some properties: Name, Value.
In my View, I have a StackPanel with some TextBlocks. I would like to bind the TextBlock's Text property to a specific property. ( I would like to bind to Value when Name of Stuff is "some name" )
On my StackPanel I have set the DataContext to the collection.
However for the textblocks, when I try ... Text="{Binding Path=Value"} ... I only get the first object in the CollectionOfStuff. How do I selectively bind to the Value of an object when the Name is "some name" ?
I THINK, from your description, you want to use an ItemsControl, which is backed by a StackPanel by default. It will show all of your items with the given template (I included a very simple one). It would look like this:
<ItemsControl ItemsSource="{Binding CollectionOfStuff}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock HorizontalAlignment="Left" Text="{Binding Name}" />
<TextBlock HorizontalAlignment="Right" Text="{Binding Value}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Is that what you want to do? If so, ignore the rest.
If, however, you are looking to bind to a specific item in the list, you will need to make sure that CollectionOfStuff implents a this[string index] indexer (very important step). Then, you can call it out by name:
<StackPanel DataContext="{Binding CollectionOfStuff['theName']}"></StackPanel>
<TextBlock HorizontalAlignment="Left" Text="{Binding Name}" />
<TextBlock HorizontalAlignment="Right" Text="{Binding Value}" />
</StackPanel>
If you are going that route, but you don't have control of the type of collection that CollectionOfStuff is, then you can always create your own indexer on your ViewModel:
public object this[string indexer]
{
get
{
return CollectionOfStuff.FirstOrDefault(s => s.Name == indexer);
}
}
Then, your DataContext on your StackPanel would look like this: DataContext="{Binding ['theName']}"
I suppose it all depends on what, exactly, you are trying to do. Your solution is in this answer some place :)

C1HierarchicalDataTemplate / C1TreeView Problem

I've a problem building up a ComponentOne TreeView in Silverlight (C1TreeView) with a C1HierarchicalDataTemplate. In detail the Tree only shows 2 levels (H1 and H2), although 3 levels are defined through HierarchicalDataTemplates like:
<c1:C1HierarchicalDataTemplate x:Key="H3Template">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
</c1:C1HierarchicalDataTemplate>
<c1:C1HierarchicalDataTemplate x:Key="H2Template" ItemsSource="{Binding Path=H3Items}" ItemTemplate="{StaticResource H3Template}">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
</c1:C1HierarchicalDataTemplate>
<c1:C1HierarchicalDataTemplate x:Key="H1Template" ItemsSource="{Binding Path=H2Items}" ItemTemplate="{StaticResource H2Template}">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
</c1:C1HierarchicalDataTemplate>");
I'm using this Templates in a Custom TreeView (derived from C1TreeView):
<c1:C1TreeView ... ItemTemplate="{StaticResource H1Template}">
</c1:C1TreeView>
The constructor of this TreeView looks like this:
public MyTreeView(ObservableCollection<H1> h1Items)
{
InitializeComponent();
ItemsSource = h1Items;
}
Can anybody see the error in these code snippets??
thx, Dom
While I'm unfamiliar with the ComponentOne TreeView that you're using, and despite the fact that you are using Silverlight, normally in WPF when you are using HierarchicalDataTemplates, you tell the template what type it's for. Sub-item templates are similarly told what type they apply to. You don't specifically tell the data template what template to use for it's ItemTemplate. That is automatically figured out by the system based on the type of object. This also applies when you bind an item collection to the TreeView--you don't have to specify the ItemTemplate.
So in your case (local: is a namespace defined at the top of your xaml):
<c1:C1HierarchicalDataTemplate DataType="{x:Type local:H1}"
ItemsSource="{Binding Path=H2Items}">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
</c1:C1HierarchicalDataTemplate>
<c1:C1HierarchicalDataTemplate DataType="{x:Type local:H2}"
ItemsSource="{Binding Path=H3Items}">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
</c1:C1HierarchicalDataTemplate>
<c1:C1HierarchicalDataTemplate DataType="{x:Type local:H3}">
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
</c1:C1HierarchicalDataTemplate>
And the TreeView:
<c1:C1TreeView ItemsSource="{Binding SomeH1List}"/>
Of course, as I said, this applies to WPF, so it might not apply in your case.

Categories

Resources