如何将 WPF 树视图项的扩展事件绑定到视图模型

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

我正在尝试将展开的事件绑定到视图模型(不是 *.xaml.cs 文件)以仅在展开节点后扩展树视图。

我的做法是:

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

我收到以下错误消息:

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

谁能帮我解决这个错误,或者有其他方法可以将事件绑定到视图模型中的命令吗?

要创建到 TreeView 的绑定,您必须使用 RelativeSource Self,而不是使用 x:Reference 的 Source。 例如,我将 TreeView 的宽度绑定到高度:

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

</TreeView>

或者您也可以使用 ElementName,如下所示:

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

</TreeView>

据我了解,您尝试将命令附加到 TreeViewItem.Expanded 事件。 我对这个问题做了一个小的研究,这是我最终得到的结果。

  1. 树视图项和此事件存在一个小问题。
  2. 有几种解决问题的方法:

首先是 TreeViewItem 样式中的 AttachedProperties,这里是 link: Invoke Command when TreeViewItem is Expanded

其次是Behavior涉及的解决方案:

  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. 视图模型:

        public MainDataContext()
    {
        OnExpandedActionInViewModel = new Action<object, RoutedEventArgs>(OnExpanded);
    }
    
    public Action<object, RoutedEventArgs> OnExpandedActionInViewModel
    {
        get { return _onExpandedActionInViewModel; }
        private set
        {
            _onExpandedActionInViewModel = value;
            OnPropertyChanged();
        }
    }
    
  3. 行为命令属性:

    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. 行为代码:

        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:EventTrigger 时,您可以指定对象来监听事件,如下所示,我使用 RelativeSource 绑定在可视化树中向上移动并找到 TreeViewItem.

此外,您可以使用 ElementName 按名称绑定到元素,这不会导致循环依赖,因为当控件已经创建和初始化时绑定在调度程序上工作(不像 x:Reference ,必须在 XAML 反序列化期间解决)。

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