So here is my data structure:
MyViewModel
Docs (ObservableCollection<Doc>)
Specs (ObservableCollection<Spec>)
i.e. the ViewModel has an ObservableCollection
named Docs
that is a collection of Doc
objects, and in turn each Doc
object has a collection of Spec
objects. A property named position
(available in both Doc and Spec classes) stores the logical position of each doc/spec.
I now need to bind this structure to a TreeView
. I need to keep both both Docs and Specs sorted at all times (TreeView supports drag-n-drop rearrangement of nodes), so a direct binding cannot work here.
Therefore I use a CollectionViewSource
to perform sorting at runtime.
<CollectionViewSource x:Key="DocumentsCVS" Source="{Binding Docs}">
<CollectionViewSource.SortDescriptions>
<componentmodel:SortDescription PropertyName="position" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
and use it in my TreeView:
<TreeView ItemsSource="{Binding Source={StaticResource DocumentsCVS}}">
So far so good. The TreeView shows my Docs sorted.
But from here onward things become confusing. How/where do I create CollectionViewSource
for my specs? I tried doing this in my HierarchicalDataTemplate
:
<HierarchicalDataTemplate>
<HierarchicalDataTemplate.ItemsSource>
<Binding>
<Binding.Source>
<CollectionViewSource Source="{Binding Specs}">
<CollectionViewSource.SortDescriptions>
<componentmodel:SortDescription PropertyName="position" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Binding.Source>
</Binding>
</HierarchicalDataTemplate.ItemsSource>
</HierarchicalDataTemplate>
But this doesn't work. Only Docs are listed in the TreeView
, with no children inside. My gut feeling is that CollectionViewSource
probably doesn't live in the same DataContext
as the parent TreeViewItem
.
Or is this something else?
Here is the full XAML of my TreeView:
<TreeView ItemsSource="{Binding Source={StaticResource DocumentsCVS}}"
PresentationTraceSources.TraceLevel="High">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:DocumentVM}">
<HierarchicalDataTemplate.ItemsSource>
<Binding>
<Binding.Source>
<CollectionViewSource Source="{Binding Specs}">
<CollectionViewSource.SortDescriptions>
<componentmodel:SortDescription PropertyName="position" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Binding.Source>
</Binding>
</HierarchicalDataTemplate.ItemsSource>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type vm:SpecVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<fa:ImageAwesome Grid.Column="0" Icon="PuzzlePiece" Width="16" Margin="3,3,6,3" Foreground="Orange" />
<Label Grid.Column="1" Content="{Binding name}" />
</Grid>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<fa:ImageAwesome Icon="FileWordOutline" Height="16" Margin="3,3,6,3" Foreground="Crimson" />
<Label Grid.Column="1" Content="{Binding name}" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<CollectionViewSource Source="{Binding Specs}">
The Binding on Source there doesn't know where to go look for a DataContext (no "framework mentor"). I've kicked this around a bit. I can't find a place to define the CollectionViewSource where it inherits the DataContext from the template.
I did find a solution.
C#
public class SortConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var view = CollectionViewSource.GetDefaultView(value);
view.SortDescriptions.Add(new SortDescription((string)parameter, ListSortDirection.Ascending));
return view;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
<HierarchicalDataTemplate
DataType="{x:Type vm:DocumentVM}"
ItemsSource="{Binding Specs, Converter={StaticResource SortConverter}, ConverterParameter=position}"
>
This could be made more useful by giving the converter multiple PropertyName/SortDirection properties, or a collection of SortDescriptions. You could make it a MarkupExtension. You could also just create the collection view in a viewmodel property.