我有一个普通的ObservableCollection,它带有我的BillDetailsViewModel。 我需要公开此集合以使其可绑定到View,但不能从VM外部进行更改。 这里是ReadOnlyObservableCollection:非常方便,非常简单。 现在我需要能够过滤显示的结果。 我正在做的是每次过滤器列表更改时动态创建一个新的ReadOnlyObsColl,并以这种方式更新绑定到ListView ItemsSource:
this.FilteredBills =
new ReadOnlyObservableCollection<BillDetailsViewModel>(
new ObservableCollection<BillDetailsViewModel>(
this.Bills.Where(b => this.Filter(b))));
问题是,当然,每次我对集合或集合的项目进行编辑时,我都必须手动刷新ReadOnlyObsColl上的绑定。
有没有更好的方法来做到这一点。 或者,如果我使用扩展列表控件将所有过滤和排序逻辑移动到UI层,会更好吗?
提前谢谢大家!
答案 0 :(得分:1)
当您处理过滤时,最好有2个集合或2个来源。
第一个是原始来源(它可能根本不是一个集合,只是一个例子)
2nd 是一个通过UI
在VM
上完成的集合。该集合可以更改,因此UI
看起来像过滤了。要重置过滤器,只需从 1st 重新加载 2nd 集合。
根据ReadOnlyObservableCollection
的文件如果对基础集合进行了更改,则 ReadOnlyObservableCollection反映了这些变化。
因此,您可以使用真实的 ObservableCollection
进行操作。
通过这种方式,您可以避免每次都创建新对象,这很好:
1)因为你弄乱了绑定
2)因为你抽了程序的记忆。
答案 1 :(得分:0)
我认为您可能通过将viewmodels用作集合类型来破坏MVVM设计模式。我的意思是,我不了解许多坚持MVVM严格设计原则的人,但遵循该设计(如果我理解你的问题)可能会帮助你解决问题。
所以我会创建一个名为&#34; BillDetailsModel&#34;的模型:
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的模型名为&#34; BillDetailsModels&#34;:
public class BillDetailsModels: ObservableCollection<BillDetailsModel>, INotifyPropertyChanged
{
/// <summary>
/// DEFAULT CONSTRUCTOR
/// </summary>
public BillDetailsModels()
{
}
}
复数本质上只是一个空的&#34;扩展Observable Collection和BillDetailsModel的模型。
这样,您的viewmodel现在可以正确地操作&#34; BillDetailsModels&#34;的具体实例化。并过滤/推断数据或您需要做的任何事情。
干杯!
答案 2 :(得分:0)
解决了创建自定义集合的问题。该实现基本上是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
}