0

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?

Action Heinz
  • 722
  • 10
  • 23

3 Answers3

3

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,

Community
  • 1
  • 1
Ilan
  • 2,762
  • 1
  • 13
  • 24
1

To create Binding to TreeView you must use RelativeSource Self, but not Source with x:Reference. For example here i'm binding Width of TreeView to Height:

<TreeView Name="tv" Width="{Binding Height, RelativeSource={RelativeSource Self}}">

</TreeView>

Or also you can use ElementName, like here:

<TreeView Name="tv" Width="{Binding Height, ElementName=tv}">

</TreeView>
Spawn
  • 935
  • 1
  • 13
  • 32
1

I know this question is old, but there's an obvious solution that does not require any custom code.

When using i:EventTrigger, you can specify the object to listen the event from as below, where I'm using RelativeSource binding to walk up the visual tree and find a TreeViewItem.

Also, you can bind to an element by name using ElementName, which doesn't cause circular dependencies because bindings work on the dispatcher when the controls are already created and initialized (unlike x:Reference, which has to be resolved during XAML deserialization).

<i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}" EventName="Expanded">
        <i:InvokeCommandAction Command="{Binding ElementName=TreeView, Path=DataContext.ExpandedCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>
Yarik
  • 1,423
  • 1
  • 14
  • 16