在 AvaloniaUI 中使用动态数据作为 WPF 的 CompositeCollection 替代品

Use Dynamic Data as WPF's CompositeCollection alternative in AvaloniaUI

我想在 AvaloniaUI 项目中使用动态数据作为 WPF 的 CompositeCollection 替代方案。

下面是一些暴露问题的代码:

public class MainWindowViewModel : ViewModelBase
{
    private readonly ReadOnlyObservableCollection<ViewModelBase> _testBind;
    public ReadOnlyObservableCollection<ViewModelBase> TestBind => _testBind;
    
    public IObservable<IChangeSet<ViewModelBase>> SeveralListTypes { get; set; }
    
    public IObservable<IChangeSet<ViewModelBase>> SeveralListTypes2 { get; set; }
    public IObservable<IChangeSet<ViewModelBase>> SeveralListTypes3 { get; set; }

    public ObservableCollection<ViewModelBase> ListTypesObject1 { get; set; } 
    public ObservableCollection<ViewModelBase> ListTypesObject2 { get; set; }
    public ObservableCollection<ViewModelBase> ListTypesObject3 { get; set; }
    public IObservable<IChangeSet<ViewModelBase>> InBoth { get; set; }
    
    
    private readonly ReadOnlyObservableCollection<ViewModelBase> _testBindTypes;
    public ReadOnlyObservableCollection<ViewModelBase> TestBindTypes => _testBindTypes;
    public MainWindowViewModel()
    {

        // TODO : those object collections should be of the real type and not from ancestor
        // ListTypesObject1 = new ObservableCollection<Object1>()

        ListTypesObject1 = new ObservableCollection<ViewModelBase>()
        {
            new Object1(),
        };
        
        ListTypesObject2 = new ObservableCollection<ViewModelBase>()
        {
            new Object2(),
        };
        
        ListTypesObject3 = new ObservableCollection<ViewModelBase>()
        {
            new Object3(),
        };


        // Change observableCollection to IObservable to be running with engine ReactiveUI
        SeveralListTypes = ListTypesObject1.ToObservableChangeSet();
        SeveralListTypes2 = ListTypesObject2.ToObservableChangeSet();
        SeveralListTypes3 = ListTypesObject3.ToObservableChangeSet();
        
        //Group All Observable into one with Or operator 
        InBoth = SeveralListTypes.Or(SeveralListTypes2).Or(SeveralListTypes3);

        // Bind => output to Binded Property for xaml
        // Subscribe => to be notified when changes
        var t = InBoth.Bind(out _testBindTypes)
            .DisposeMany()
            .Subscribe();
    }
    
    public void AddObject1()
    {
        var obj1 = new Object1("Added Object 1");
        ListTypesObject1.Add(obj1);
    }
    public void AddObject2()
    {
        var obj2 = new Object2("Added Object 2");
        ListTypesObject2.Add(obj2);
    }
    public void AddObject3()
    {
        if (ListTypesObject3 == null)
            return;
        var obj3 = new Object3("Added Object 3");
        ListTypesObject3.Add(obj3);
    }

    public void DeleteObject1()
    {
        if(ListTypesObject1 != null && ListTypesObject1.Count > 0)
            ListTypesObject1.RemoveAt(0);
    }
    public void DeleteObject2()
    {
        if (ListTypesObject2 != null && ListTypesObject2.Count > 0)
            ListTypesObject2.RemoveAt(0);
    }
    public void DeleteObject3()
    {
        if (ListTypesObject3 != null && ListTypesObject3.Count > 0)
            ListTypesObject3.RemoveAt(0);
    }
    
    public void DeleteObjectClear()
    {
        if (ListTypesObject3 == null)
            return;
        ListTypesObject3.Clear();
        ListTypesObject3 = null;
        ListTypesObject3 = new ObservableCollection<ViewModelBase>()
        {
            new Object3("Added object 3 from new list 3"),
        };
        SeveralListTypes3 = ListTypesObject3.ToObservableChangeSet();
        InBoth = InBoth.Or(SeveralListTypes3);
        // TODO : the collection we want to remove is still binded, the new one is not

    }

    public void DeleteObject3List()
    {
        if (ListTypesObject3 == null)
            return;
        ListTypesObject3.Clear();
        ListTypesObject3 = null;
        // TODO : remove the Object3List from DynamicData
    }

    public void CreateObject3List()
    {
        if (ListTypesObject3 != null)
            return;
        ListTypesObject3 = new ObservableCollection<ViewModelBase>()
        {
            new Object3("Added object 3 from new list 3"),
        };
        SeveralListTypes3 = ListTypesObject3.ToObservableChangeSet();
        InBoth = InBoth.Or(SeveralListTypes3);
        // TODO : the collection we want to remove is still binded, the new one is not

    }

}

Object1、Object2 和 Object3 是 ViewModelBase 的继承。

DeleteObjectClear 方法从绑定中删除所有 Object3,但随后不显示新列表。

如何添加或删除 ObservableCollection 并刷新绑定对象 (TestBind)?

作为第二个问题,是否可以在 ObservableCollection 中使用真实类型的对象(具有共同的祖先)而不是 ViewModelBase 并且仍然使用动态数据来聚合所有集合?

这是突出问题的完整 github 存储库 POC:https://github.com/Whiletru3/pocbindingdmo

谢谢

我真的不能只用 DynamicData 来做,所以我想出了这个解决方案。 不是很优雅,但它有效...

我创建了一个 ObservableCollectionAggregate class。我可以分配和取消分配不同的(类型化的)ObservableCollections。

public class ObservableCollectionAggregate : ObservableCollection<ViewModelBase>
{
    private ObservableCollection<Object1> _subCollection1;
    private ObservableCollection<Object2> _subCollection2;
    private ObservableCollection<Object3> _subCollection3;


    public ObservableCollectionAggregate()
    {
        _subCollection1 = null;
        _subCollection2 = null;
        _subCollection3 = null;
    }

    public void UnassignCollectionObject1()
    {
        if (_subCollection1 != null)
        {
            RemoveItems(_subCollection1);
            _subCollection1.CollectionChanged -= OnSubCollectionChanged;
            _subCollection1 = null;
        }
    }

    public void AssignCollectionObject1(ObservableCollection<Object1> collection)
    {
        if (_subCollection1 != null)
        {
            UnassignCollectionObject1();
        }

        _subCollection1 = collection;
        AddItems(_subCollection1);
        _subCollection1.CollectionChanged += OnSubCollectionChanged;

    }

    public void UnassignCollectionObject2()
    {
        if (_subCollection2 != null)
        {
            RemoveItems(_subCollection2);
            _subCollection2.CollectionChanged -= OnSubCollectionChanged;
            _subCollection2 = null;
        }
    }

    public void AssignCollectionObject2(ObservableCollection<Object2> collection)
    {
        if (_subCollection2 != null)
        {
            UnassignCollectionObject2();
        }

        _subCollection2 = collection;
        AddItems(_subCollection2);
        _subCollection2.CollectionChanged += OnSubCollectionChanged;

    }

    public void UnassignCollectionObject3()
    {
        if (_subCollection3 != null)
        {
            RemoveItems(_subCollection3);
            _subCollection3.CollectionChanged -= OnSubCollectionChanged;
            _subCollection3 = null;
        }
    }

    public void AssignCollectionObject3(ObservableCollection<Object3> collection)
    {
        if (_subCollection3 != null)
        {
            UnassignCollectionObject3();
        }

        _subCollection3 = collection;
        AddItems(_subCollection3);
        _subCollection3.CollectionChanged += OnSubCollectionChanged;

    }

    private void AddItems(IEnumerable<ViewModelBase> items)
    {
        foreach (ViewModelBase me in items)
            Add(me);
    }

    private void RemoveItems(IEnumerable<ViewModelBase> items)
    {
        foreach (ViewModelBase me in items)
            Remove(me);
    }

    private void OnSubCollectionChanged(object source, NotifyCollectionChangedEventArgs args)
    {
        switch (args.Action)
        {
            case NotifyCollectionChangedAction.Add:
                AddItems(args.NewItems.Cast<ViewModelBase>());
                break;

            case NotifyCollectionChangedAction.Remove:
                RemoveItems(args.OldItems.Cast<ViewModelBase>());
                break;
            case NotifyCollectionChangedAction.Replace:
                RemoveItems(args.OldItems.Cast<ViewModelBase>());
                AddItems(args.NewItems.Cast<ViewModelBase>());
                break;
            case NotifyCollectionChangedAction.Move:
                throw new NotImplementedException();
            case NotifyCollectionChangedAction.Reset:
                if (source is ObservableCollection<Object1>)
                {
                    RemoveItems(this.Where(c => c is Object3).ToList());
                }
                if (source is ObservableCollection<Object2>)
                {
                    RemoveItems(this.Where(c => c is Object3).ToList());
                }
                if (source is ObservableCollection<Object3>)
                {
                    RemoveItems(this.Where(c => c is Object3).ToList());
                }
                break;
        }
    }
}

然后我可以在我的视图模型中使用它,使用 DynamicData 进行绑定:

public class MainWindowViewModel : ViewModelBase
{
    private readonly ReadOnlyObservableCollection<ViewModelBase> _testBind;
    public ReadOnlyObservableCollection<ViewModelBase> TestBind => _testBind;
    
    public ObservableCollectionAggregate AggregatedCollection { get; set; }
    public IObservable<IChangeSet<ViewModelBase>> AggregatedChangeSetFull { get; set; }

    public ObservableCollection<Object1> ListTypesObject1 { get; set; } 
    public ObservableCollection<Object2> ListTypesObject2 { get; set; }
    public ObservableCollection<Object3> ListTypesObject3 { get; set; }


    private readonly ReadOnlyObservableCollection<ViewModelBase> _testBindTypes;
    public ReadOnlyObservableCollection<ViewModelBase> TestBindTypes => _testBindTypes;
    public MainWindowViewModel()
    {

        ListTypesObject1 = new ObservableCollection<Object1>()
        {
            new Object1(),
        };
        
        ListTypesObject2 = new ObservableCollection<Object2>()
        {
            new Object2(),
        };
        

        AggregatedCollection = new ObservableCollectionAggregate();

        AggregatedCollection.AssignCollectionObject1(ListTypesObject1);
        AggregatedCollection.AssignCollectionObject2(ListTypesObject2);


        AggregatedChangeSetFull = AggregatedCollection.ToObservableChangeSet();


        // Bind => output to Binded Property for xaml
        // Subscribe => to be notified when changes
        var t = AggregatedChangeSetFull
            .DisposeMany()
            .ObserveOn(RxApp.MainThreadScheduler)
            .Bind(out _testBindTypes)
            .Subscribe();

    }

    public void AddObject1()
    {
        if (ListTypesObject1 == null)
            return;
        var obj1 = new Object1("Added Object 1");
        ListTypesObject1.Add(obj1);
    }
    public void AddObject2()
    {
        if (ListTypesObject2 == null)
            return;
        var obj2 = new Object2("Added Object 2");
        ListTypesObject2.Add(obj2);
    }
    public void AddObject3()
    {
        if (ListTypesObject3 == null)
            return;
        var obj3 = new Object3("Added Object 3");
        ListTypesObject3.Add(obj3);
    }

    public void DeleteObject1()
    {
        if(ListTypesObject1 != null && ListTypesObject1.Count > 0)
            ListTypesObject1.RemoveAt(0);
    }
    public void DeleteObject2()
    {
        if (ListTypesObject2 != null && ListTypesObject2.Count > 0)
            ListTypesObject2.RemoveAt(0);
    }
    public void DeleteObject3()
    {
        if (ListTypesObject3 != null && ListTypesObject3.Count > 0)
            ListTypesObject3.RemoveAt(0);
    }

    public void DeleteObject3List()
    {
        if (ListTypesObject3 == null)
            return;
        ListTypesObject3.Clear();
        ListTypesObject3 = null;
        AggregatedCollection.UnassignCollectionObject3();
    }
    
    public void CreateObject3List()
    {
        if (ListTypesObject3 != null)
            return;
        ListTypesObject3 = new ObservableCollection<Object3>()
        {
            new Object3("Added object 3 from new list 3"),
        };
        AggregatedCollection.AssignCollectionObject3(ListTypesObject3);
    }


}

存储库此分支中的完整工作示例: https://github.com/Whiletru3/pocbindingdmo/tree/ObservableCollectionAggregate