Action Heinz Action Heinz - 10 days ago 5
C# Question

How to bind expanded event of WPF treeview item to the viewmodel

I'm trying to bind the expanded event to the viewmodel (not the *.xaml.cs file) to extend the treeview only after expanding a node.

My approach is:

<TreeView x:Name="TreeViewTest" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneTime}">
<StackPanel>
<Label Content="{Binding Title, Mode=OneTime}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="TreeViewItem.Expanded">
<i:InvokeCommandAction Command="{Binding DataContext.ExpandedCommand, Source={x:Reference TreeViewTest}}"> </i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>


I get following error message:


Cannot call MarkupExtension.ProvideValue because of a cyclical dependency. Properties inside a MarkupExtension cannot reference objects that reference the result of the MarkupExtension. The affected MarkupExtensions are:
System.Windows.Data.Binding


Can someone help me how to solve this error or is there another way to bind the event to a command in the viewmodel?

Answer

As I can understand you try to attach a command to TreeViewItem.Expanded event. I've done a small research concerning this issue and here is the result I've end up with.

  1. There is a small problem with the tree view items and this event.
  2. There are several approaches to solve the problem:

First is AttachedProperties in style of the TreeViewItem, here is the link: Invoke Command when TreeViewItem is Expanded

Second is Behavior involved solution:

  1. XAML:

    <Grid>
    <TreeView x:Name="TreeViewTest" ItemsSource="{Binding Items}" TreeViewItem.Expanded="TreeViewTest_OnExpanded">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type soTreeViewHelpAttempt:TreeObject}" 
                                      ItemsSource="{Binding Children, Mode=OneTime}">
                <HierarchicalDataTemplate.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <Label Content="{Binding }" />
                        </StackPanel>
                    </DataTemplate>
                </HierarchicalDataTemplate.ItemTemplate>
                <StackPanel>
                    <Label Content="{Binding Title, Mode=OneTime}" />
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
        <i:Interaction.Behaviors>
            <soTreeViewHelpAttempt:TreeViewItemExpandBehavior OnExpandedAction="{Binding OnExpandedActionInViewModel}"/>
        </i:Interaction.Behaviors>
    </TreeView></Grid>
    
  2. ViewModel:

        public MainDataContext()
    {
        OnExpandedActionInViewModel = new Action<object, RoutedEventArgs>(OnExpanded);
    }
    
    public Action<object, RoutedEventArgs> OnExpandedActionInViewModel
    {
        get { return _onExpandedActionInViewModel; }
        private set
        {
            _onExpandedActionInViewModel = value;
            OnPropertyChanged();
        }
    }
    
  3. Behavior Command property:

    public static readonly DependencyProperty OnExpandedActionProperty = DependencyProperty.Register(
        "OnExpandedAction", typeof (Action<object,RoutedEventArgs>), typeof (TreeViewItemExpandBehavior), new PropertyMetadata(default(Action<object,RoutedEventArgs>)));
    
    public Action<object,RoutedEventArgs> OnExpandedAction
    {
        get { return (Action<object,RoutedEventArgs>) GetValue(OnExpandedActionProperty); }
        set { SetValue(OnExpandedActionProperty, value); }
    }
    
  4. Behavior code:

        protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += AssociatedObjectOnLoaded;
    }
    
    private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        var treeView = sender as TreeView;
        if(treeView == null) return;
        treeView.ItemsSource.Cast<object>().ToList().ForEach(o =>
        {
            var treeViewItem = treeView.ItemContainerGenerator.ContainerFromItem(o) as TreeViewItem;
            if(treeViewItem == null) return;
            treeViewItem.Expanded += TreeViewItemOnExpanded;
            _list.Add(treeViewItem);
        });
    }
    
    private void TreeViewItemOnExpanded(object sender, RoutedEventArgs routedEventArgs)
    {
        if(OnExpandedAction == null)
            return;
        OnExpandedAction(sender, routedEventArgs);
    }
    
    
    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.Loaded -= AssociatedObjectOnLoaded;
        _list.ForEach(item => item.Expanded -= TreeViewItemOnExpanded);
        _list.Clear();
    }
    

I hope It will help you. Regards,

Edit: fixed formatting