基于DynamicData实现AvaloniaUI文件树递归选中项监听方案咨询
基于DynamicData实现递归选中项的解决方案
核心思路
要实现Folder的SelectedItems递归包含自身及所有子项的选中项,需利用DynamicData的响应式集合能力,监听自身IsSelected变化、子项集合的动态变更,以及子项(文件/子文件夹)的IsSelected状态变化,最终合并所有选中项并自动更新。
前置修正与接口定义
首先统一接口命名(修正原代码中的IFileEntry笔误),并让IFileItem继承INotifyPropertyChanged以支持属性变更通知:
using System.ComponentModel; using System.Runtime.CompilerServices; using DynamicData; interface IFileItem : INotifyPropertyChanged { string Path { get; } bool IsSelected { get; set; } }
File类实现
class File : IFileItem { private bool _isSelected; public string Path { get; } public bool IsSelected { get => _isSelected; set { if (_isSelected != value) { _isSelected = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler? PropertyChanged; public File(string path) { Path = path; } private void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Folder类的DynamicData实现
class Folder : IFileItem { private readonly SourceList<IFileItem> _childrenSource = new(); private readonly ReadOnlyObservableCollection<IFileItem> _children; private readonly ReadOnlyObservableCollection<IFileItem> _selectedItems; private bool _isSelected; public string Path { get; } public ReadOnlyObservableCollection<IFileItem> Children => _children; public ReadOnlyObservableCollection<IFileItem> SelectedItems => _selectedItems; public bool IsSelected { get => _isSelected; set { if (_isSelected != value) { _isSelected = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler? PropertyChanged; public Folder(string path) { Path = path; // 初始化子项集合支持(可直接替换为懒加载逻辑) _childrenSource.Connect() .Bind(out _children) .Subscribe(); // 1. 监听自身选中状态,生成包含自身或空的变更集合 var selfSelectedStream = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => PropertyChanged += h, h => PropertyChanged -= h) .StartWith(new EventPattern<PropertyChangedEventArgs>(this, new PropertyChangedEventArgs(nameof(IsSelected)))) .Select(_ => IsSelected ? new[] { this as IFileItem } : Array.Empty<IFileItem>()) .ToObservableChangeSet<IFileItem>(); // 2. 处理子项的选中状态:递归获取子文件夹的选中项,监听文件的选中状态 var childrenSelectedStream = _childrenSource.Connect() .TransformMany(item => { if (item is Folder subFolder) { // 递归订阅子文件夹的SelectedItems变更 return subFolder.SelectedItems.ToObservableChangeSet(); } else { // 监听文件的IsSelected变更 return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => item.PropertyChanged += h, h => item.PropertyChanged -= h) .StartWith(new EventPattern<PropertyChangedEventArgs>(item, new PropertyChangedEventArgs(nameof(IsSelected)))) .Select(_ => item.IsSelected ? new[] { item } : Array.Empty<IFileItem>()) .ToObservableChangeSet<IFileItem>(); } }) .DisposeMany(); // 自动释放子项的订阅,避免内存泄漏 // 3. 合并自身与子项的选中项,按Path去重后绑定到SelectedItems selfSelectedStream .Merge(childrenSelectedStream) .Distinct(item => item.Path) .Bind(out _selectedItems) .Subscribe(); } // 用于添加子项(适配懒加载/用户创建文件夹场景) public void AddChild(IFileItem child) { _childrenSource.Add(child); } private void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
关键特性说明
- 递归监听:子文件夹的
SelectedItems会被自动纳入父文件夹的选中项集合,无需手动维护递归逻辑 - 动态适配:支持懒加载子项、用户新增文件/文件夹,DynamicData会自动监听新项的状态变化
- 内存安全:使用
DisposeMany()确保子项的订阅在被移除时自动释放 - 去重保障:通过
Distinct(item => item.Path)避免因递归导致的重复项
内容的提问来源于stack exchange,提问作者SuperJMN
相关产品推荐
相关产品推荐

