使用 DynamicData 递归地观察子级的变化

问题描述 投票:0回答:1

我正在为 AvaloniaUI 创建一个完全通用的文件浏览器。它有一个显示文件夹树的视图,您可以通过展开每个目录来导航。

我的目标是让这个视图足够灵活,以支持从多个节点选择多个项目。

问题?树是动态的。不仅子项会延迟加载,而且用户还可以创建新项目(例如文件夹)并选择它们。

  • 我目前有一个
    IFileItem
    接口来表示文件结构中的项目。我目前有
    Folder
    File
  • 当然,文件夹可以包含其他条目(文件夹+文件)。
  • 文件和文件夹都是ViewModel(尽管我没有添加ViewModel后缀😁)
interface IFileItem
{
    string Path { get; }
}

class Folder : IFileItem
{
     ReadOnlyObservableCollection<IFileEntry> { get; }
     ...
}

class File : IFileItem
{
     ...
}

为了支持选择功能,我的计划是添加一个属性

ReadOnlyObservableCollection<IFileItem> SelectedItems

class Folder : IFileEntry
{
     ReadOnlyObservableCollection<IFileItem> Children { get; }
     bool IsSelected { get; set; } 
     ReadOnlyObservableCollection<IFileItem> SelectedItems { get; }
     
     ...
}

SelectedItems
属性应该保存在给定分支中选择的任何内容的列表,即给定节点+子节点。 这种方法可能很有用,并且可能是我所需要的,但我不确定该解决方案对于支持单个文件夹选取等场景的效果如何。

现在的问题是:我们如何定义

SelectedItems
属性???

它应该递归地观察 this.IsSelected 和 Children.IsSelected 的变化。现在这很难了。 我不熟悉 DynamicData 中的任何此类用例,但我认为应该处理它。现在,我完全陷入困境。

我希望有人能以纯粹的DD方式提出一个很好的解决方案:)

c# .net system.reactive dynamic-data
1个回答
0
投票

这是部分实现的解决方案。这很痛苦,而且需要做相当多的工作才能完全发挥作用,但这可以演示如何在所有级别维护

SelectedItems
集合。

从基本接口和抽象类开始:

public interface IFileItem
{
    string Path { get; }
    bool IsSelected { get; set; }
    event Action<bool> Selected;
}

public abstract class FileItemBase : IFileItem
{
    public FileItemBase(string path) => this.Path = path;
    public virtual string Path { get; }

    private bool _isSelected = false;

    public virtual bool IsSelected
    {
        get => _isSelected;
        set
        {
            _isSelected = value;
            this?.Selected(value);
        }
    }
    
    public event Action<bool> Selected;
}

然后我实现了

File
:

public class File : FileItemBase
{
    public File(string path) : base(path) { }
}

然后

Folder
:

public class Folder : FileItemBase
{
    public Folder(string path) : base(path)
    {
        _children = new ObservableCollection<IFileItem>();
        this.Children = new ReadOnlyObservableCollection<IFileItem>(_children);
        _selectedItems = new ObservableCollection<IFileItem>();
        this.SelectedItems = new ReadOnlyObservableCollection<IFileItem>(_selectedItems);
    }

    private ObservableCollection<IFileItem> _children;
    public ReadOnlyObservableCollection<IFileItem> Children { get; }

    private ObservableCollection<IFileItem> _selectedItems = new ObservableCollection<IFileItem>();
    public ReadOnlyObservableCollection<IFileItem> SelectedItems { get; }

    private IEnumerable<IFileItem> Recurse()
    {
        foreach (var _child in _children)
        {
            yield return _child;
            if (_child is Folder folder)
            {
                foreach (var x in folder.Recurse())
                {
                    yield return x;
                }
            }
        }
    }

    public void Add(IFileItem fileItem)
    {
        _children.Add(fileItem);
        if (fileItem.IsSelected)
        {
            _selectedItems.Add(fileItem);
        }
        fileItem.Selected += isSelected =>
        {
            if (isSelected)
            {
                _selectedItems.Add(fileItem);
            }
            else
            {
                _selectedItems.Remove(fileItem);
            }
        };
        if (fileItem is Folder folder)
        {
            (folder.SelectedItems as System.Collections.Specialized.INotifyCollectionChanged).CollectionChanged += (s, e) =>
            {
                switch (e.Action)
                {
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                        foreach (var item in e.NewItems.OfType<IFileItem>())
                            if (item.IsSelected)
                                _selectedItems.Add(item);
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                        foreach (var item in e.OldItems.OfType<IFileItem>())
                                _selectedItems.Remove(item);
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                        break;
                    case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                        var removes = this.SelectedItems.Except(this.Recurse().Where(x => x.IsSelected)).ToList();
                        foreach (var remove in removes)
                            _selectedItems.Remove(remove);
                        break;
                }
            };
        }
    }
}

现在我的测试是在 LINQPad 中完成的:

void Main()
{
    var f1 = new Folder("X");
    var f2 = new Folder("XY");
    var f3 = new Folder("XZ");
    var f4 = new File("Xa");
    var f5 = new File("XYa");
    var f6 = new File("XYb");
    var f7 = new File("XZa");
    
    f1.Add(f2);
    f1.Add(f3);
    
    f1.Add(f4);
    f2.Add(f5);
    f2.Add(f6);
    f3.Add(f7);

    f1.SelectedItems.Dump();

    f7.IsSelected = true;

    f1.SelectedItems.Dump();

    f7.IsSelected = false;

    f1.SelectedItems.Dump();
}

因此,即使

f7
嵌套在
f3
下面并且
f3
f1
下面,它仍然会在
f1
的集合中出现和消失。

© www.soinside.com 2019 - 2024. All rights reserved.