.Net 不可变集合与 WPF DataGrid

本文关键字:WPF DataGrid 集合 不可变 Net | 更新日期: 2023-09-27 18:31:39

有没有人找到将ImmutableCollections(来自MS BCL)绑定到WPF DataGrid的良好模式?为了表示不可变结构的可变性,我会用 ISubject 包装它以跟踪更改并为数据网格提供新版本。

ISubject<ImmutableList<T>> <--(binding)--> DataGrid

DataGrid显然需要一个可变集合,例如直接在其下方的ObservableCollection,因此问题可以简化为

ISubject<ImmutableList<T>> <-----> ObservableCollection<T>

有什么建议吗?

可观察集合

.Net 不可变集合与 WPF DataGrid

这是一个部分解决方案。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Immutable;
using System.Collections.Specialized;
using ReactiveUI;
namespace ReactiveUI.Ext
{
    public class ImmutableListToReactive<T> : ReactiveObject, ICollection<T>, INotifyCollectionChanged, IDisposable, IList<T>
    where T : class, Immutable
    {
        private  ISubject<ImmutableList<T>> _Source;
        ImmutableList<T> _Current;
        public ImmutableList<T> Current
        {
            get { return _Current; }
            set { this.RaiseAndSetIfChanged(ref _Current, value); }
        }
        public void Dispose()
        {
            _Subscription.Dispose();
        }

        public ImmutableListToReactive( ISubject<ImmutableList<T>> source )
        {
            _Source = source;
            _Subscription = source.Subscribe(newVersion =>
            {
                if ( !rebound )
                {
                    _Current = newVersion;
                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                }
            });
        }
        private void OnNext( ImmutableList<T> list )
        {
            rebound = true;
            _Current = list;
            try
            {
                _Source.OnNext(list);
            }
            finally
            {
                rebound = false;
            }
        }
        public void Add( T item )
        {
            OnNext(_Current.Add(item));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(){item}, Current.Count - 1));
        }
        public void Clear()
        {
            OnNext(_Current.Clear());
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
        public bool Contains( T item )
        {
            return _Current.Contains(item);
        }
        public void CopyTo( T[] array, int arrayIndex )
        {
            _Current.CopyTo(array, arrayIndex);
        }
        public int Count
        {
            get { return _Current.Count; }
        }
        public bool IsReadOnly
        {
            get { return false; }
        }
        public bool Remove( T item )
        {
            var idx = _Current.IndexOf(item);
            var next = _Current.Remove(item);
            if ( next == _Current )
            {
                return false;
            }
            else
            {
                OnNext(next);
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, idx));
                return true;
            }
        }
        public IEnumerator<T> GetEnumerator()
        {
            return _Current.GetEnumerator();
        }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _Current.GetEnumerator();
        }
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        private  IDisposable _Subscription;

        bool rebound = false;
        protected virtual void OnCollectionChanged( NotifyCollectionChangedEventArgs e )
        {
            if ( !rebound )
            {
                rebound = true;
                if ( CollectionChanged != null )
                {
                    CollectionChanged(this, e);
                }
                rebound = false;
            }
        }
        public int IndexOf( T item )
        {
            return _Current.IndexOf(item);
        }
        public void Insert( int index, T item )
        {
            OnNext(_Current.Insert(index, item));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
        }
        public void RemoveAt( int index )
        {
            var itemToBeRemoved = _Current[index];
            OnNext(_Current.RemoveAt(index));
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, itemToBeRemoved, index));
        }
        public T this[int index]
        {
            get
            {
                return _Current[index];
            }
            set
            {
                OnNext(_Current.SetItem(index, value));
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value, index));
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value, index));
            }
        }
    }
}

和一个测试用例

using FluentAssertions;
using ReactiveUI.Subjects;
using System;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Xunit;
namespace ReactiveUI.Ext.Spec
{
    public class Data : Immutable
    {
        public int A { get; private set; }
        public int B { get; private set; }
        public  bool Equals(Data other){
            return A == other.A && B == other.B;
        }
        public  bool Equals(object o){
            Data other = o as Data;
            if ( other == null )
            {
                return false;
            }
            return this.Equals(other);
        }
        public static bool operator ==( Data a, Data b )
        {
            return a.Equals(b);
        }
        public static bool operator !=( Data a, Data b )
        {
            return !a.Equals(b);
        }
        public Data( int a, int b )
        {
            A = a;
            B = b;
        }
    }
    public class DataMutable : ReactiveObject, IDisposable
    {
        int _A;
        public int A
        {
            get { return _A; }
            set { this.RaiseAndSetIfChanged(ref _A, value); }
        }
        int _B;
        public int B
        {
            get { return _B; }
            set { this.RaiseAndSetIfChanged(ref _B, value); }
        }
        IDisposable _Subscriptions;
        public DataMutable( ILens<Data> dataLens )
        {
            _Subscriptions = new CompositeDisposable();
            var d0 = dataLens.Focus(p => p.A).TwoWayBindTo(this, p => p.A);
            var d1 = dataLens.Focus(p => p.B).TwoWayBindTo(this, p => p.B);
            _Subscriptions = new CompositeDisposable(d0, d1);
        }
        public void Dispose()
        {
            _Subscriptions.Dispose();
        }
    }

    public class ImmutableListToReactiveSpec : ReactiveObject
    {
        ImmutableList<Data> _Fixture;
        public ImmutableList<Data> Fixture
        {
            get { return _Fixture; }
            set { this.RaiseAndSetIfChanged(ref _Fixture, value); }
        }
        [Fact]
        public void ReactiveListSux()
        {
            var a = new ReactiveList<int>();
            var b = a.CreateDerivedCollection(x => x);
            a.Add(10);
            b.Count.Should().Be(1);
        }

        [Fact]
        public void ShouldWork()
        {
            Fixture = ImmutableList<Data>.Empty;
            // Convert an INPC property to a subject
            ISubject<ImmutableList<Data>> s = this.PropertySubject(p => p.Fixture);
            var MutableList = new ImmutableListToReactive<Data>(s);
            var DerivedList = MutableList.CreateDerivedCollection(x => x);
            Fixture = Fixture.Add(new Data(10, 20));
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            Fixture = Fixture.Add(new Data(11, 21));
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            Fixture = Fixture.Add(new Data(12, 22));
            MutableList.Count.Should().Be(3);
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.Add(new Data(33, 88));
            MutableList.Count.Should().Be(4);
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.ShouldAllBeEquivalentTo(Fixture);
            MutableList[1] = new Data(99, 21);
            MutableList.Count.Should().Be(4);
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.ShouldAllBeEquivalentTo(Fixture);
            var itemAtOne = MutableList[1];
            MutableList.RemoveAt(1);
            MutableList.Should().NotContain(itemAtOne);
            MutableList.Count.Should().Be(3);
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.ShouldAllBeEquivalentTo(Fixture);
            var i = new Data(78, 32);
            MutableList.Insert(0, i);
            DerivedList[0].Should().Be(i);
            MutableList.Count.Should().Be(4);
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.ShouldAllBeEquivalentTo(Fixture);
            var j = new Data(18, 22);
            MutableList.Insert(3, j);
            DerivedList[3].Should().Be(j);
            MutableList.Count.Should().Be(5);
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.ShouldAllBeEquivalentTo(Fixture);
            var k = new Data(18, 22);
            MutableList.Add(k);
            DerivedList[DerivedList.Count-1].Should().Be(k);
            MutableList.Count.Should().Be(6);
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.Remove(i);
            DerivedList[DerivedList.Count-1].Should().Be(k);
            MutableList.Count.Should().Be(5);
            DerivedList.ShouldAllBeEquivalentTo(Fixture);
            MutableList.ShouldAllBeEquivalentTo(Fixture);
        }
    }
}