0

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?

Edit

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>
dotNET
  • 33,414
  • 24
  • 162
  • 251
  • Try `Source="{Binding Specs, PresentationTraceSources.TraceLevel=High}"` and see what you see in the VS Output window at runtime. There ought to be a mass of trace information when the binding is attempted to be resolved. – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 14:09
  • @EdPlunkett: No luck. It doesn't print out anything in the Output window. And yes, I'm in debug mode. – dotNET May 15 '19 at 15:48
  • That sounds like the Binding never gets resolved. Can I see the whole XAML for the treeview, including the HierarchicalDataTemplates? – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 15:49
  • @EdPlunkett: See my edit please. – dotNET May 15 '19 at 15:55
  • Is that what you did with `PresentationTraceSources.TraceLevel="High"`, put it on the TreeView rather than on the Binding to `Specs`, as suggested? – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 15:59
  • Yes, that seems to be what happened. You'd have found that there was no "framework mentor" -- the Binding didn't know to go look for a DataContext. 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 think [this is your answer](https://stackoverflow.com/a/20899469/424129) (option 2, ICollectionView). – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 17:13

1 Answers1

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

  • 1
    Thank you very much. That got me headed in the right direction. I didn't succeed on the `MarkupExtension` path, so I reverted back to using converter. After spending a few hours with it, I finally got it working. Thing is that you have to cast the view to `ICollectionViewLiveShaping` and enable live sorting on it before returning it from the converter; else it won't reorder tree nodes automatically. – dotNET May 15 '19 at 20:40
  • Thanks, I’ll add that to the answer when I get home (or feel free to edit it if you’re incline) – 15ee8f99-57ff-4f92-890c-b56153 May 15 '19 at 20:51