过滤并更新ReadOnlyObservableCollection

时间:2014-11-06 16:24:56

标签: c# .net sorting mvvm filtering

我有一个普通的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层,会更好吗?

提前谢谢大家!

3 个答案:

答案 0 :(得分:1)

当您处理过滤时,最好有2个集合或2个来源。

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

2nd 是一个通过UIVM上完成的集合。该集合可以更改,因此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
}