Search code examples
c#wpfmvvmtreeviewcaliburn.micro

WPF TreeView auto expand tree items on condition once


In my View I have a Search Box for filtering and a tree that should be automatically expanded on specific conditions and when the search string is changed.

So the user should be able to see all nodes that are found.

I'm using a TreeListView which is a simple TreeView with columns, it also behaves like one.
Also I'm using a ControlTemplate.
Because the control template is ignoring any ItemsSource set in the original TreeView I'm using a hack:
ControlTemplate:

<ControlTemplate x:Key="TreeControlTemplate">
    <Border>
        <treeList:TreeListView ItemsSource="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type treeList:TreeListView}}}"
[...]

TreeView:

<treeList:TreeListView Template="{StaticResource TreeControlTemplate}"
                       DataContext="{Binding RootItem.FilteredChildren}" />

This works so far so good.

When the Filter is changing there is a NotifyOfPropertyChange(() => FilteredChildren);
To expand the tree I'm using this code in code-behind:

private ViewModel _viewModel;

public View()
{
    InitializeComponent();
    DataContextChanged += OnDataContextChanged;
    Tree.DataContextChanged += Tree_DataContextChanged;
}

private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (DataContext is ViewModel viewModel)
    {
        DataContextChanged -= OnDataContextChanged;
        _viewModel = viewModel;
    }
}

private async void Tree_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    await Task.Delay(500);
    if (_viewModel.ShouldExpandTreesAfterUpdate())
        ExpandTree(Tree);
}

private void ExpandTree(DependencyObject parent)
{
    for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
        var child = VisualTreeHelper.GetChild(parent, i);

        if (child is TreeListViewItem treeListViewItem)
            treeListViewItem.ExpandSubtree();
        else
            ExpandTree(child);
    }
}

This works, but only when I use the Task.Delay
There are a few downsides I am not able to overcome tho:

  • The Task.Delay seems to be required otherwise the UI is probably not ready. How can I do without?
  • It would be nice to do this without accessing the ViewModel from View code-behind (optional)
  • Since I'm binding my ItemsSource the Items property seems to be empty and I'm basicially crawling the entire visual tree just to find the actual tree items. (optional)

Solution

  • How can I do without?

    You need to wait until the tree has been refreshed based on the new DataContext one way or another. Using the Dispatcher to execute a delegate at a specified priority is one option, e.g.:

    private async void Tree_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        _ = Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, () =>
        {
            if (_viewModel.ShouldExpandTreesAfterUpdate())
                ExpandTree(Tree);
        });
    }