筛选并更新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层更好?

提前感谢大家!

筛选并更新ReadOnlyObservableCollection

在处理筛选时,最好有两个集合或两个源。

第一个用于原始源(它可能根本不是一个集合,只是一个示例)

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
}
相关文章:
  • 没有找到相关文章