You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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));
    }
}

关键特性说明

  1. 递归监听:子文件夹的SelectedItems会被自动纳入父文件夹的选中项集合,无需手动维护递归逻辑
  2. 动态适配:支持懒加载子项、用户新增文件/文件夹,DynamicData会自动监听新项的状态变化
  3. 内存安全:使用DisposeMany()确保子项的订阅在被移除时自动释放
  4. 去重保障:通过Distinct(item => item.Path)避免因递归导致的重复项

内容的提问来源于stack exchange,提问作者SuperJMN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 03:58:41