筛选并更新ReadOnlyObservableCollection
本文关键字:ReadOnlyObservableCollection 更新 筛选 | 更新日期: 2023-09-27 18:29:37
我有一个普通的ObservableCollection,它承载了我的BillDetailsViewModel。我需要公开这个集合,使它可以绑定到视图,但不能从VM外部更改。ReadOnlyObservableCollection来了:非常方便,非常简单。现在我需要过滤显示的结果的能力。我正在做的是,每次过滤器列表更改时,通常都会创建一个新的ReadOnlyObsColl,并更新到ListView ItemsSource的绑定,方法如下:
this.FilteredBills =
new ReadOnlyObservableCollection<BillDetailsViewModel>(
new ObservableCollection<BillDetailsViewModel>(
this.Bills.Where(b => this.Filter(b))));
问题是,当然,每次编辑集合或集合的某个项时,我都必须手动刷新ReadOnlyObsColl上的绑定。
有更好的方法吗。还是使用扩展列表控件将所有过滤和排序逻辑移动到UI层更好?
提前感谢大家!
在处理筛选时,最好有两个集合或两个源。
第一个用于原始源(它可能根本不是一个集合,只是一个示例)
2nd是在UI
上通过VM
完成的集合。该集合可以更改,因此UI
看起来像已筛选的。要重置过滤器,只需从1st重新加载2nd集合。
根据ReadOnlyObservableCollection 的文档
如果对基础集合进行了更改ReadOnlyObservableCollection反映了这些变化。
因此,在引擎盖下,您可以对真实ObservableCollection
进行操作。
这样可以避免每次都创建新对象,这很好:
1) 因为你把绑定搞砸了
2) 因为你抽运了程序的内存。
我认为使用视图模型作为集合类型可能会打破MVVM的设计模式。我的意思是,我不知道有多少人坚持MVVM严格的设计原则,但遵循这种设计(如果我正确理解你的问题)可能会帮助你找到解决方案。
所以我会创建一个名为"BillDetailsModel"的模型:
public class BillDetailsModel : INotifyPropertyChanged
{
// INSERT YOUR MODEL PROPERTIES
/// <summary>
/// DEFAULT CONSTRUCTOR
/// </summary>
public BillDetailsModel()
{
}
#region Property Changed
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
以及另一个扩展Observable Collection的模型,称为"BillDetailsModels":
public class BillDetailsModels: ObservableCollection<BillDetailsModel>, INotifyPropertyChanged
{
/// <summary>
/// DEFAULT CONSTRUCTOR
/// </summary>
public BillDetailsModels()
{
}
}
复数基本上只是一个"空"模型,它扩展了Observable Collection和BillDetailsModel。
这样,您的视图模型现在可以正确地操作"BillDetailsModels"的具体实例化,并过滤/推断数据或您需要做的任何事情。
干杯!
已解决创建自定义集合的问题。该实现基本上是ReadOnlyObservableCollection类的重新实现,其好处是能够访问私有列表字段。这使我能够拦截来自源ObservableCollection的通知,应用过滤逻辑,创建自定义的NotifyCollectionChangedEventArgs,并传播事件。
这就是实现:
[Serializable]
[DebuggerDisplay("Count = {Count}")]
public class ReadOnlyObservableCollectionEx<T> : IList<T>, IList, IReadOnlyList<T>, INotifyCollectionChanged, INotifyPropertyChanged
where T : class
{
#region fields
private readonly ObservableCollection<T> source;
private IList<T> filteredSource;
[NonSerialized]
private Object _syncRoot;
#endregion
#region ctor
public ReadOnlyObservableCollectionEx(ObservableCollection<T> source, IEnumerable<Predicate<T>> filters)
{
if (source == null)
throw new ArgumentNullException();
this.source = source;
this.filters = filters;
this.UpdateFiltering();
((INotifyCollectionChanged)this.source).CollectionChanged += new NotifyCollectionChangedEventHandler(HandleCollectionChanged);
((INotifyPropertyChanged)this.source).PropertyChanged += new PropertyChangedEventHandler(HandlePropertyChanged);
}
public ReadOnlyObservableCollectionEx(ObservableCollection<T> source)
: this(source, null)
{
}
#endregion ctor
#region properties
private IEnumerable<Predicate<T>> filters;
public IEnumerable<Predicate<T>> Filters
{
get { return this.filters; }
set
{
if (this.filters == value & value == null) return;
this.filters = value;
this.NotifyOfPropertyChange();
this.UpdateFiltering();
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
#endregion properties
#region methods
private void UpdateFiltering()
{
if (this.Filters != null)
this.filteredSource = this.source.Where(i => this.Filters.All(f => f(i))).ToList();
else
this.filteredSource = this.source.ToList();
this.NotifyOfPropertyChange("Item[]");
this.NotifyOfPropertyChange("Count");
}
#endregion methods
#region implementations
#region IList<T>, IList, IReadOnlyList<T>
public int Count
{
get { return this.filteredSource.Count; }
}
public T this[int index]
{
get { return this.filteredSource[index]; }
}
public bool Contains(T value)
{
return this.filteredSource.Contains(value);
}
public void CopyTo(T[] array, int index)
{
this.filteredSource.CopyTo(array, index);
}
public IEnumerator<T> GetEnumerator()
{
return this.filteredSource.GetEnumerator();
}
public int IndexOf(T value)
{
return this.filteredSource.IndexOf(value);
}
protected IList<T> Items
{
get
{
return this.filteredSource;
}
}
bool ICollection<T>.IsReadOnly
{
get { return true; }
}
T IList<T>.this[int index]
{
get { return this.filteredSource[index]; }
set
{
throw new NotSupportedException();
}
}
void ICollection<T>.Add(T value)
{
throw new NotSupportedException();
}
void ICollection<T>.Clear()
{
throw new NotSupportedException();
}
void IList<T>.Insert(int index, T value)
{
throw new NotSupportedException();
}
bool ICollection<T>.Remove(T value)
{
throw new NotSupportedException();
}
void IList<T>.RemoveAt(int index)
{
throw new NotSupportedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)this.filteredSource).GetEnumerator();
}
bool ICollection.IsSynchronized
{
get { return false; }
}
object ICollection.SyncRoot
{
get
{
if (_syncRoot == null)
{
ICollection c = this.filteredSource as ICollection;
if (c != null)
{
_syncRoot = c.SyncRoot;
}
else
{
System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);
}
}
return _syncRoot;
}
}
void ICollection.CopyTo(Array array, int index)
{
if (array == null)
{
throw new ArgumentNullException();
}
if (array.Rank != 1)
{
throw new ArgumentException();
}
if (array.GetLowerBound(0) != 0)
{
throw new ArgumentException();
}
if (index < 0)
{
throw new ArgumentException();
}
if (array.Length - index < Count)
{
throw new ArgumentException();
}
T[] items = array as T[];
if (items != null)
{
this.filteredSource.CopyTo(items, index);
}
else
{
//
// Catch the obvious case assignment will fail.
// We can found all possible problems by doing the check though.
// For example, if the element type of the Array is derived from T,
// we can't figure out if we can successfully copy the element beforehand.
//
Type targetType = array.GetType().GetElementType();
Type sourceType = typeof(T);
if (!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType)))
{
throw new ArgumentException();
}
//
// We can't cast array of value type to object[], so we don't support
// widening of primitive types here.
//
object[] objects = array as object[];
if (objects == null)
{
throw new ArgumentException();
}
int count = this.filteredSource.Count;
try
{
for (int i = 0; i < count; i++)
{
objects[index++] = this.filteredSource[i];
}
}
catch (ArrayTypeMismatchException)
{
throw new ArgumentException();
}
}
}
bool IList.IsFixedSize
{
get { return true; }
}
bool IList.IsReadOnly
{
get { return true; }
}
object IList.this[int index]
{
get { return this.filteredSource[index]; }
set
{
throw new NotSupportedException();
}
}
int IList.Add(object value)
{
throw new NotSupportedException();
}
void IList.Clear()
{
throw new NotSupportedException();
}
private static bool IsCompatibleObject(object value)
{
// Non-null values are fine. Only accept nulls if T is a class or Nullable<U>.
// Note that default(T) is not equal to null for value types except when T is Nullable<U>.
return ((value is T) || (value == null && default(T) == null));
}
bool IList.Contains(object value)
{
if (IsCompatibleObject(value))
{
return this.Contains((T)value);
}
return false;
}
int IList.IndexOf(object value)
{
if (IsCompatibleObject(value))
{
return this.IndexOf((T)value);
}
return -1;
}
void IList.Insert(int index, object value)
{
throw new NotSupportedException();
}
void IList.Remove(object value)
{
throw new NotSupportedException();
}
void IList.RemoveAt(int index)
{
throw new NotSupportedException();
}
#endregion IList<T>, IList, IReadOnlyList<T>
#region INotifyCollectionChanged
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
{
add { this.CollectionChanged += value; }
remove { this.CollectionChanged -= value; }
}
[field: NonSerialized]
protected virtual event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
if (this.CollectionChanged != null)
{
this.CollectionChanged(this, args);
}
}
void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
if (this.Filters != null)
{
// if there are no filter OR if the added item passes the filter
if (this.Filters.All(f => f(e.NewItems[0] as T)))
{
// add it
this.UpdateFiltering(); // TODO: check if there's a way to just add the item
var addIndex = this.IndexOf(e.NewItems[0] as T);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.NewItems, addIndex));
}
}
else
{
this.filteredSource.Insert(e.NewStartingIndex, e.NewItems[0] as T);
this.OnCollectionChanged(e);
}
break;
}
case NotifyCollectionChangedAction.Move:
{
if (this.Filters != null)
{
// if there are no filter OR if the moved item passes the filter
if (this.Filters.All(f => f(e.OldItems[0] as T)))
{
// if it was already in the filtered list
var wasAlreadyContained = this.Contains(e.OldItems[0] as T);
int oldIndex = -1;
if (wasAlreadyContained)
oldIndex = this.IndexOf(e.OldItems[0] as T);
this.UpdateFiltering(); // TODO: check if there's a way to just add the item
var newIndex = this.IndexOf(e.OldItems[0] as T);
if (wasAlreadyContained)
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, e.OldItems, newIndex, oldIndex));
else
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, e.OldItems, newIndex));
}
// if the moved item doesn't pass the filter but it's contained
else if (this.Contains(e.OldItems[0] as T))
{
// remove it
var removeIndex = this.IndexOf(e.OldItems[0] as T);
this.filteredSource.RemoveAt(removeIndex);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, e.OldItems, removeIndex));
}
}
else
{
this.filteredSource.RemoveAt(e.OldStartingIndex);
this.filteredSource.Insert(e.NewStartingIndex, e.NewItems[0] as T);
this.OnCollectionChanged(e);
}
break;
}
case NotifyCollectionChangedAction.Remove:
{
if (this.Filters != null)
{
// if the item is contained (passes the filter)
if (this.Filters.All(f => f(e.OldItems[0] as T)))
{
// remove it
var removeIndex = this.IndexOf(e.OldItems[0] as T);
this.filteredSource.RemoveAt(removeIndex);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, e.OldItems, removeIndex));
}
}
else
{
this.filteredSource.RemoveAt(e.OldStartingIndex);
this.OnCollectionChanged(e);
}
break;
}
case NotifyCollectionChangedAction.Replace:
{
if (this.Filters != null)
{
// if the item that has been replaced is contained (passes the filter)
if (this.Filters.All(f => f(e.OldItems[0] as T)))
{
// remove it
var replaceIndex = this.IndexOf(e.OldItems[0] as T);
this.filteredSource.RemoveAt(replaceIndex);
// if the new one is allowed
if (this.Filters.All(f => f(e.NewItems[0] as T)))
{
// replace it
this.filteredSource.Insert(replaceIndex, e.NewItems[0] as T);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, e.NewItems, e.OldItems, replaceIndex));
}
else // if the new one it's not allowed
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, e.OldItems, replaceIndex));
}
else // if the replaced item is not contained
{
// but the new one is allowed
if (this.Filters.All(f => f(e.NewItems[0] as T)))
{
// add it
this.UpdateFiltering(); // TODO: check if there's a way to just add the item
var addIndex = this.IndexOf(e.NewItems[0] as T);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, e.NewItems, addIndex));
}
}
}
else
{
this.filteredSource.RemoveAt(e.OldStartingIndex);
this.filteredSource.Insert(e.NewStartingIndex, e.NewItems[0] as T);
this.OnCollectionChanged(e);
}
break;
}
case NotifyCollectionChangedAction.Reset:
{
this.UpdateFiltering();
this.OnCollectionChanged(e);
break;
}
default:
throw new InvalidEnumArgumentException(@"Unknown collection action: " + e.Action);
}
}
#endregion INotifyCollectionChanged
#region INotifyPropertyChanged
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { this.PropertyChanged += value; }
remove { this.PropertyChanged -= value; }
}
[field: NonSerialized]
protected virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, args);
}
}
void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.OnPropertyChanged(e);
}
public virtual void NotifyOfPropertyChange([CallerMemberName] string propertyName = null)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged
#endregion implementations
}