TreeView 项目更改事件

Posted

技术标签:

【中文标题】TreeView 项目更改事件【英文标题】:TreeView Items changed event 【发布时间】:2017-02-09 04:45:47 【问题描述】:

要向本机 WPF 树视图添加多选支持,我必须添加一个自定义依赖属性来存储多选项目。在树的项目开始改变之前,这很有效。

例如,在初始树中有一个项目 A。我选择了它,它存储在 MultiSelectedItems 列表中。然后我删除了项目 A 并添加了项目 B。(通过 ViewModel ObservableCollection 绑定)

当这种情况发生时,我需要找到一种方法从 MultiSelectedItems 列表中删除项目 A。

我无法为此找到活动。我得到的最接近的是ItemContainerGenerator.ItemsChanged 事件,但此事件仅针对根级节点触发(不会针对其层次结构子节点触发)。

【问题讨论】:

【参考方案1】:

不幸的是,这是一个复杂的问题,只是因为在 WPF 中有多种实现 TreeView 的方法而变得更加复杂。如果您使用 MVVM、虚拟化和HierarchicalDataTemplates,那么所选项目甚至可能在任何给定时间都不是视觉或逻辑树的一部分 - 更不用说即使尝试观察单个项目的删除也是不够的,因为任何它的祖先可能会被删除。

我的建议是在控件级别实现一个朴素的 MultiSelection,并实现一个智能的 ViewModel 层次结构:

允许跨 ViewModel 层次结构访问“父”和“根”节点,并允许项目在其子集合更改时从 Root.SelecteItems 集合中删除其后代。

在我的 MVVM 框架中,我有一个 HierarchicalRootViewModelBase 和一个 HierarchicalViewModelBase 用于所有层次结构 VM。这样,所有的树功能(如选择和集合更改事件)都实现一次并自动处理。每个基类都是通过对其父节点和 Root 节点的引用来构造的(或者它使用递归来查找 Root)。

通过这种方式,在任何层次结构深度删除项目都可以轻松触发根级操作,例如检查/更新 SelectedItems 集合。

【讨论】:

【参考方案2】:

解决这个问题的关键思想是在每个节点而不是在树级别检测项目更改事件。

我继承了TreeViewItem

public class MultiSelectTreeViewItem : TreeViewItem

    object _originalHeader;
    protected override void OnHeaderChanged(object oldHeader, object newHeader)
    
        base.OnHeaderChanged(oldHeader, newHeader);
        //.NET 4.5 use BindingOperations.DisconnectedSource
        if (newHeader.ToString() != "DisconnectedItem")
            _originalHeader = newHeader;
    

    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    
        base.OnItemsChanged(e);

        if (Header.ToString() == "DisconnectedItem" && _originalHeader != null && e.Action == NotifyCollectionChangedAction.Reset)
        
            //Find the parent Tree View and remove this from MultiSelectedList
        
    


    protected override DependencyObject GetContainerForItemOverride()
    
        return new MultiSelectTreeViewItem();
    

    protected override bool IsItemItsOwnContainerOverride(object item)
    
        return item is MultiSelectTreeViewItem;
    

在继承的TreeView中

    protected override DependencyObject GetContainerForItemOverride()
    
        return new MultiSelectTreeViewItem();
    

    protected override bool IsItemItsOwnContainerOverride(object item)
    
        return item is MultiSelectTreeViewItem;
    

当节点被移除时,它的头部将被设置为一个名为 DisconnectedItem 的标记对象,并且 Items Changed 事件将是NotifyCollectionChangedAction.Reset

请注意,如果您执行了List.Clear(),则不会触发NotifyCollectionChangedAction.Remove 事件,只有NotifyCollectionChangedAction.Reset 会触发。所以我发现它是检测节点移除的最可靠方法。

一个问题是如果节点没有被渲染(父节点从未被扩展)那么这个事件将不会触发。

【讨论】:

正如你所提到的,这在任何意义上都不是一个通用的解决方案,并且有许多非平凡的失败案例。我强烈建议您遵循我之前的回答。 @AndrewHanlon 你介意举个例子让我更好地理解它吗?

以上是关于TreeView 项目更改事件的主要内容,如果未能解决你的问题,请参考以下文章

怎么响应TreeView中的OnTreeNodeCheckChanged事件

UWP - Flyout中的WinRT xaml工具包TreeView,在弹出窗口关闭后更改选择

访问 VBA - TreeView 控件 - AfterLabelEdit 事件

WPF Treeview,如何更改缩进

python tkinter treeview右键单击(Button-3)事件以选择树视图中的项目

单击 Tkinter Treeview 小部件的项目的命令?