有没有人找到将ImmutableCollections(来自MS BCL)绑定到WPF DataGrid的良好模式?为了表示不可变结构的可变性,我会用 ISubject 包装它以跟踪更改并为数据网格提供新版本。
ISubject<ImmutableList<T>> <--(binding)--> DataGrid
DataGrid显然需要一个可变集合,例如直接在其下方的ObservableCollection,因此问题可以简化为
。ISubject<ImmutableList<T>> <-----> ObservableCollection<T>
有什么建议吗?
可观察集合
这是一个部分解决方案。
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);
}
}
}