عند مسح ObservableCollection، لا توجد عناصر في e.OldItems

StackOverflow https://stackoverflow.com/questions/224155

  •  03-07-2019
  •  | 
  •  

سؤال

لدي شيء هنا يفاجئني حقًا.

لدي مجموعة ObservableCollection من T مليئة بالعناصر.لدي أيضًا معالج حدث مرفق بحدث CollectionChanged.

عندما انت واضح المجموعة تتسبب في حدث CollectionChanged مع تعيين e.Action على NotifyCollectionChangedAction.Reset.حسنًا، هذا طبيعي.ولكن الأمر الغريب هو أنه لا يوجد أي شيء في e.OldItems أو e.NewItems. أتوقع أن يتم ملء e.OldItems بجميع العناصر التي تمت إزالتها من المجموعة.

هل راى احد اخر هذا؟وإذا كان الأمر كذلك، فكيف تمكنوا من الالتفاف حوله؟

بعض الخلفية:أنا أستخدم الحدث CollectionChanged للإرفاق والفصل عن حدث آخر وبالتالي إذا لم أحصل على أي عناصر في e.OldItems ...لن أكون قادرًا على الانفصال عن هذا الحدث.


إيضاح:أعلم أن الوثائق لا تفعل ذلك صريح الدولة التي يجب أن تتصرف بهذه الطريقة.ولكن بالنسبة لكل إجراء آخر، فإنه يخطرني بما قام به.لذا فإن ظني هو أنه سيخبرني ...في حالة المسح/إعادة الضبط أيضًا.


يوجد أدناه نموذج التعليمات البرمجية إذا كنت ترغب في إعادة إنتاجه بنفسك.أولا من xaml:

<Window
    x:Class="ObservableCollection.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1"
    Height="300"
    Width="300"
>
    <StackPanel>
        <Button x:Name="addButton" Content="Add" Width="100" Height="25" Margin="10" Click="addButton_Click"/>
        <Button x:Name="moveButton" Content="Move" Width="100" Height="25" Margin="10" Click="moveButton_Click"/>
        <Button x:Name="removeButton" Content="Remove" Width="100" Height="25" Margin="10" Click="removeButton_Click"/>
        <Button x:Name="replaceButton" Content="Replace" Width="100" Height="25" Margin="10" Click="replaceButton_Click"/>
        <Button x:Name="resetButton" Content="Reset" Width="100" Height="25" Margin="10" Click="resetButton_Click"/>
    </StackPanel>
</Window>

بعد ذلك، الكود الموجود خلف:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace ObservableCollection
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            _integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged);
        }

        private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    break;
            }
        }

        private void addButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Add(25);
        }

        private void moveButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Move(0, 19);
        }

        private void removeButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.RemoveAt(0);
        }

        private void replaceButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection[0] = 50;
        }

        private void resetButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Clear();
        }

        private ObservableCollection<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
    }
}
هل كانت مفيدة؟

المحلول 6

حسنًا، على الرغم من أنني ما زلت أتمنى أن يتصرف ObservableCollection كما أردت ...الكود أدناه هو ما انتهى بي الأمر بفعله.في الأساس، قمت بإنشاء مجموعة جديدة من T تسمى TrulyObservableCollection وتجاوزت طريقة ClearItems التي استخدمتها بعد ذلك لرفع حدث المقاصة.

في الكود الذي يستخدم TrulyObservableCollection، أستخدم حدث Clearing هذا للتنقل عبر العناصر التي لا تزال في المجموعة في ذلك الوقت للقيام بالفصل في الحدث الذي كنت أرغب في الانفصال عنه.

نأمل أن يساعد هذا النهج شخصًا آخر أيضًا.

public class TrulyObservableCollection<T> : ObservableCollection<T>
{
    public event EventHandler<EventArgs> Clearing;
    protected virtual void OnClearing(EventArgs e)
    {
        if (Clearing != null)
            Clearing(this, e);
    }

    protected override void ClearItems()
    {
        OnClearing(EventArgs.Empty);
        base.ClearItems();
    }
}

نصائح أخرى

ولا يدعي تضمين العناصر القديمة، لأن إعادة الضبط لا تعني أنه تم مسح القائمة

هذا يعني أن شيئًا مثيرًا قد حدث، ومن المرجح أن تتجاوز تكلفة العمل على الإضافة/الإزالة تكلفة إعادة فحص القائمة من البداية...هذا ما يجب عليك فعله.

تقترح MSDN مثالاً على إعادة تصنيف المجموعة بأكملها كمجموعة مرشحة لإعادة التعيين.

أن أكرر. إعادة الضبط لا تعني الوضوح, ، هذا يعني افتراضاتك حول القائمة أصبحت الآن غير صالحة.تعامل معها كما لو كانت قائمة جديدة تمامًا.ومن الواضح أن هذا هو أحد الأمثلة على ذلك، ولكن من الممكن أن يكون هناك حالات أخرى.

بعض الأمثلة:
لقد كانت لدي قائمة مثل هذه تحتوي على الكثير من العناصر، وقد تم ربطها بالبيانات إلى WPF ListView لعرضها على الشاشة.
إذا قمت بمسح القائمة ورفع .Reset الحدث، يكون الأداء فوريًا إلى حد كبير، ولكن إذا قمت بدلاً من ذلك برفع العديد من الأفراد .Remove الأحداث، كان الأداء سيئًا، حيث يقوم WPF بإزالة العناصر واحدًا تلو الآخر.لقد استخدمت أيضا .Reset في الكود الخاص بي للإشارة إلى أن القائمة قد تم إعادة فرزها، بدلاً من إصدار آلاف الأفراد Move عمليات.كما هو الحال مع Clear، هناك أداء كبير عند رفع العديد من الأحداث الفردية.

كان لدينا نفس المشكلة هنا.لا يتضمن إجراء إعادة التعيين في CollectionChanged العناصر القديمة.كان لدينا حل بديل:استخدمنا بدلاً من ذلك طريقة التمديد التالية:

public static void RemoveAll(this IList list)
{
   while (list.Count > 0)
   {
      list.RemoveAt(list.Count - 1);
   }
}

انتهى بنا الأمر إلى عدم دعم وظيفة Clear()، وإلقاء حدث NotSupportedException في حدث CollectionChanged لإجراءات إعادة التعيين.سيؤدي RemoveAll إلى تشغيل إجراء إزالة في حدث CollectionChanged، باستخدام العناصر القديمة المناسبة.

هناك خيار آخر وهو استبدال حدث Reset بحدث Remove واحد يحتوي على كافة العناصر التي تم مسحها في خاصية OldItems الخاصة به كما يلي:

public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
    protected override void ClearItems()
    {
        List<T> removed = new List<T>(this);
        base.ClearItems();
        base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (e.Action != NotifyCollectionChangedAction.Reset)
            base.OnCollectionChanged(e);
    }
    // Constructors omitted
    ...
}

مزايا:

  1. لا حاجة للاشتراك في حدث إضافي (كما هو مطلوب في الإجابة المقبولة)

  2. لا يُنشئ حدثًا لكل كائن تتم إزالته (بعض الحلول المقترحة الأخرى تؤدي إلى عدة أحداث تمت إزالتها).

  3. يحتاج المشترك فقط إلى التحقق من العناصر الجديدة والعناصر القديمة في أي حدث لإضافة/إزالة معالجات الأحداث كما هو مطلوب.

سلبيات:

  1. لا يوجد حدث إعادة تعيين

  2. النفقات العامة الصغيرة (؟) لإنشاء نسخة من القائمة.

  3. ???

تحرير 2012-02-23

لسوء الحظ، عند الارتباط بعناصر التحكم القائمة على قائمة WPF، سيؤدي مسح مجموعة ObservableCollectionNoReset بعناصر متعددة إلى حدوث استثناء "إجراءات النطاق غير مدعومة".لاستخدامها مع عناصر التحكم مع هذا القيد، قمت بتغيير فئة ObservableCollectionNoReset إلى:

public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
    // Some CollectionChanged listeners don't support range actions.
    public Boolean RangeActionsSupported { get; set; }

    protected override void ClearItems()
    {
        if (RangeActionsSupported)
        {
            List<T> removed = new List<T>(this);
            base.ClearItems();
            base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
        }
        else
        {
            while (Count > 0 )
                base.RemoveAt(Count - 1);
        }                
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (e.Action != NotifyCollectionChangedAction.Reset)
            base.OnCollectionChanged(e);
    }

    public ObservableCollectionNoReset(Boolean rangeActionsSupported = false) 
    {
        RangeActionsSupported = rangeActionsSupported;
    }

    // Additional constructors omitted.
 }

لا يكون هذا فعالاً عندما يكون RangeActionsSupported خطأ (الافتراضي) لأنه يتم إنشاء إشعار إزالة واحد لكل كائن في المجموعة

لقد وجدت حلاً يسمح للمستخدم بالاستفادة من كفاءة إضافة أو إزالة العديد من العناصر في وقت واحد أثناء إطلاق حدث واحد فقط - وتلبية احتياجات UIElements للحصول على وسيطات الحدث Action.Reset بينما يفعل جميع المستخدمين الآخرين ذلك مثل قائمة العناصر المضافة والمحذوفة.

يتضمن هذا الحل تجاوز الحدث CollectionChanged.عندما نبدأ بإطلاق هذا الحدث، يمكننا في الواقع إلقاء نظرة على هدف كل معالج مسجل وتحديد نوعه.نظرًا لأن فئات ICollectionView فقط هي التي تتطلب ذلك NotifyCollectionChangedAction.Reset args عندما يتغير أكثر من عنصر واحد، يمكننا تمييزها ومنح الجميع وسيطات الأحداث المناسبة التي تحتوي على القائمة الكاملة للعناصر التي تمت إزالتها أو إضافتها.أدناه هو التنفيذ.

public class BaseObservableCollection<T> : ObservableCollection<T>
{
    //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
    private bool _SuppressCollectionChanged = false;

    /// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    public BaseObservableCollection() : base(){}
    public BaseObservableCollection(IEnumerable<T> data) : base(data){}

    #region Event Handlers
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if( !_SuppressCollectionChanged )
        {
            base.OnCollectionChanged(e);
            if( CollectionChanged != null )
                CollectionChanged.Invoke(this, e);
        }
    }

    //CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
    //one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
    //for applications in code, so we actually check the type we're notifying on and pass a customized event args.
    protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
        if( handlers != null )
            foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
                handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    #endregion

    #region Extended Collection Methods
    protected override void ClearItems()
    {
        if( this.Count == 0 ) return;

        List<T> removed = new List<T>(this);
        _SuppressCollectionChanged = true;
        base.ClearItems();
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    public void Add(IEnumerable<T> toAdd)
    {
        if( this == toAdd )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toAdd )
            Add(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
    }

    public void Remove(IEnumerable<T> toRemove)
    {
        if( this == toRemove )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toRemove )
            Remove(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
    }
    #endregion
}

حسنًا، أعلم أن هذا سؤال قديم جدًا ولكني توصلت إلى حل جيد لهذه المشكلة واعتقدت أنني سأشاركه.هذا الحل مستوحى من الكثير من الإجابات الرائعة هنا ولكنه يتمتع بالمزايا التالية:

  • لا حاجة لإنشاء فئة جديدة وتجاوز الأساليب من ObservableCollection
  • لا يعبث بأعمال NotifyCollectionChanged (لذا لا داعي للعبث بإعادة التعيين)
  • لا يستفيد من التأمل

هنا هو الرمز:

 public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction)
 {
     unhookAction.Invoke(collection);
     collection.Clear();
 }

تأخذ طريقة التمديد هذه ببساطة Action والتي سيتم استدعاؤها قبل مسح المجموعة.

لقد تعاملت مع هذا بطريقة مختلفة قليلاً حيث أردت التسجيل في حدث واحد والتعامل مع جميع الإضافات والإزالة في معالج الحدث.لقد بدأت في تجاوز حدث تغيير المجموعة وإعادة توجيه إجراءات إعادة التعيين إلى إجراءات الإزالة باستخدام قائمة العناصر.حدث كل هذا بشكل خاطئ لأنني كنت أستخدم المجموعة التي يمكن ملاحظتها كمصدر عناصر لعرض المجموعة وحصلت على "إجراءات النطاق غير مدعومة".

لقد قمت أخيرًا بإنشاء حدث جديد يسمى CollectionChangedRange والذي يعمل بالطريقة التي توقعت أن تعمل بها النسخة المدمجة.

لا أستطيع أن أتخيل سبب السماح بهذا القيد وآمل أن يمنع هذا المنشور الآخرين على الأقل من السير في طريق مسدود كما فعلت.

/// <summary>
/// An observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ObservableCollectionRange<T> : ObservableCollection<T>
{
    private bool _addingRange;

    [field: NonSerialized]
    public event NotifyCollectionChangedEventHandler CollectionChangedRange;

    protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs e)
    {
        if ((CollectionChangedRange == null) || _addingRange) return;
        using (BlockReentrancy())
        {
            CollectionChangedRange(this, e);
        }
    }

    public void AddRange(IEnumerable<T> collection)
    {
        CheckReentrancy();
        var newItems = new List<T>();
        if ((collection == null) || (Items == null)) return;
        using (var enumerator = collection.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                _addingRange = true;
                Add(enumerator.Current);
                _addingRange = false;
                newItems.Add(enumerator.Current);
            }
        }
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems));
    }

    protected override void ClearItems()
    {
        CheckReentrancy();
        var oldItems = new List<T>(this);
        base.ClearItems();
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems));
    }

    protected override void InsertItem(int index, T item)
    {
        CheckReentrancy();
        base.InsertItem(index, item);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
    }

    protected override void MoveItem(int oldIndex, int newIndex)
    {
        CheckReentrancy();
        var item = base[oldIndex];
        base.MoveItem(oldIndex, newIndex);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
    }

    protected override void RemoveItem(int index)
    {
        CheckReentrancy();
        var item = base[index];
        base.RemoveItem(index);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
    }

    protected override void SetItem(int index, T item)
    {
        CheckReentrancy();
        var oldItem = base[index];
        base.SetItem(index, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldItem, item, index));
    }
}

/// <summary>
/// A read only observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ReadOnlyObservableCollectionRange<T> : ReadOnlyObservableCollection<T>
{
    [field: NonSerialized]
    public event NotifyCollectionChangedEventHandler CollectionChangedRange;

    public ReadOnlyObservableCollectionRange(ObservableCollectionRange<T> list) : base(list)
    {
        list.CollectionChangedRange += HandleCollectionChangedRange;
    }

    private void HandleCollectionChangedRange(object sender, NotifyCollectionChangedEventArgs e)
    {
        OnCollectionChangedRange(e);
    }

    protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs args)
    {
        if (CollectionChangedRange != null)
        {
            CollectionChangedRange(this, args);
        }
    }

}

هذه هي الطريقة التي يعمل بها ObservableCollection، يمكنك حل هذه المشكلة عن طريق الاحتفاظ بقائمتك الخاصة خارج ObservableCollection (الإضافة إلى القائمة عندما يكون الإجراء إضافة، والإزالة عندما يكون الإجراء إزالة وما إلى ذلك) ثم يمكنك الحصول على جميع العناصر المحذوفة (أو العناصر المضافة ) عند إعادة تعيين الإجراء من خلال مقارنة قائمتك مع ObservableCollection.

هناك خيار آخر وهو إنشاء فصلك الخاص الذي يطبق IList وINotifyCollectionChanged، ثم يمكنك إرفاق الأحداث وفصلها من داخل تلك الفئة (أو تعيين OldItems على Clear إذا أردت) - إنه ليس بالأمر الصعب حقًا، ولكنه يتطلب الكثير من الكتابة.

بالنسبة لسيناريو إرفاق معالجات الأحداث وفصلها عن عناصر ObservableCollection، يوجد أيضًا حل "من جانب العميل".في كود التعامل مع الحدث، يمكنك التحقق مما إذا كان المرسل موجودًا في ObservableCollection باستخدام الطريقة التي تحتوي على.طليعة:يمكنك العمل مع أي ObservableCollection موجود.سلبيات:يتم تشغيل الطريقة التي تحتوي على O(n) حيث n هو عدد العناصر الموجودة في ObservableCollection.لذلك هذا هو الحل لمجموعات ObservableCollections الصغيرة.

الحل الآخر "من جانب العميل" هو استخدام معالج الأحداث في المنتصف.ما عليك سوى تسجيل جميع الأحداث في معالج الأحداث في المنتصف.يقوم معالج الحدث بدوره بإعلام معالج الحدث الحقيقي من خلال رد اتصال أو حدث.في حالة حدوث إجراء إعادة تعيين، قم بإزالة رد الاتصال أو الحدث، وقم بإنشاء معالج حدث جديد في المنتصف ونسيان المعالج القديم.يعمل هذا الأسلوب أيضًا مع مجموعات ObservableCollections الكبيرة.لقد استخدمت هذا لحدث PropertyChanged (انظر الكود أدناه).

    /// <summary>
    /// Helper class that allows to "detach" all current Eventhandlers by setting
    /// DelegateHandler to null.
    /// </summary>
    public class PropertyChangedDelegator
    {
        /// <summary>
        /// Callback to the real event handling code.
        /// </summary>
        public PropertyChangedEventHandler DelegateHandler;
        /// <summary>
        /// Eventhandler that is registered by the elements.
        /// </summary>
        /// <param name="sender">the element that has been changed.</param>
        /// <param name="e">the event arguments</param>
        public void PropertyChangedHandler(Object sender, PropertyChangedEventArgs e)
        {
            if (DelegateHandler != null)
            {
                DelegateHandler(sender, e);
            }
            else
            {
                INotifyPropertyChanged s = sender as INotifyPropertyChanged;
                if (s != null)
                    s.PropertyChanged -= PropertyChangedHandler;
            }   
        }
    }

أنظر إلى NotifyCollectionChangedEventArgs, ، يبدو أن العناصر القديمة تحتوي فقط على العناصر التي تم تغييرها نتيجة لإجراء الاستبدال أو الإزالة أو النقل.لا يشير ذلك إلى أنه سيحتوي على أي شيء في Clear.أظن أن Clear يقوم بتشغيل الحدث، لكنه لا يسجل العناصر التي تمت إزالتها ولا يستدعي رمز الإزالة على الإطلاق.

حسنًا، قررت أن أتسخ معه بنفسي.

بذلت Microsoft الكثير من العمل للتأكد دائمًا من عدم احتواء NotifyCollectionChangedEventArgs على أي بيانات عند الاتصال بإعادة التعيين.أفترض أن هذا كان قرارًا للأداء/الذاكرة.إذا كنت تقوم بإعادة تعيين مجموعة تحتوي على 100000 عنصر، أفترض أنهم لا يريدون تكرار كل هذه العناصر.

لكن نظرًا لأن مجموعاتي لا تحتوي أبدًا على أكثر من 100 عنصر، فلا أرى مشكلة في ذلك.

على أي حال قمت بإنشاء فئة موروثة بالطريقة التالية:

protected override void ClearItems()
{
    CheckReentrancy();
    List<TItem> oldItems = new List<TItem>(Items);

    Items.Clear();

    OnPropertyChanged(new PropertyChangedEventArgs("Count"));
    OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));

    NotifyCollectionChangedEventArgs e =
        new NotifyCollectionChangedEventArgs
        (
            NotifyCollectionChangedAction.Reset
        );

        FieldInfo field =
            e.GetType().GetField
            (
                "_oldItems",
                BindingFlags.Instance | BindingFlags.NonPublic
            );
        field.SetValue(e, oldItems);

        OnCollectionChanged(e);
    }

تتم كتابة ObservableCollection بالإضافة إلى واجهة INotifyCollectionChanged بشكل واضح مع وضع استخدام محدد في الاعتبار:بناء واجهة المستخدم وخصائص أدائها المحددة.

عندما تريد إشعارات بتغييرات المجموعة، فأنت عمومًا مهتم فقط بإضافة وإزالة الأحداث.

أستخدم الواجهة التالية:

using System;
using System.Collections.Generic;

/// <summary>
/// Notifies listeners of the following situations:
/// <list type="bullet">
/// <item>Elements have been added.</item>
/// <item>Elements are about to be removed.</item>
/// </list>
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
interface INotifyCollection<T>
{
    /// <summary>
    /// Occurs when elements have been added.
    /// </summary>
    event EventHandler<NotifyCollectionEventArgs<T>> Added;

    /// <summary>
    /// Occurs when elements are about to be removed.
    /// </summary>
    event EventHandler<NotifyCollectionEventArgs<T>> Removing;
}

/// <summary>
/// Provides data for the NotifyCollection event.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public class NotifyCollectionEventArgs<T> : EventArgs
{
    /// <summary>
    /// Gets or sets the elements.
    /// </summary>
    /// <value>The elements.</value>
    public IEnumerable<T> Items
    {
        get;
        set;
    }
}

لقد كتبت أيضًا الحمل الزائد للمجموعة حيث:

  • ClearItems يثير عملية الإزالة
  • إدراج عنصر يثير المضافة
  • يرفع RemoveItem عملية الإزالة
  • يرفع SetItem الإزالة والإضافة

وبطبيعة الحال، يمكن إضافة AddRange كذلك.

لقد كنت أتصفح للتو بعضًا من أكواد الرسوم البيانية في مجموعات أدوات Silverlight وWPF ولاحظت أنهم قاموا أيضًا بحل هذه المشكلة (بطريقة مماثلة) ...واعتقدت أنني سأمضي قدمًا وأنشر الحل.

في الأساس، قاموا أيضًا بإنشاء ObservableCollection مشتق وتجاوزوا ClearItems، واستدعوا إزالة على كل عنصر يتم مسحه.

هنا هو الرمز:

/// <summary>
/// An observable collection that cannot be reset.  When clear is called
/// items are removed individually, giving listeners the chance to detect
/// each remove event and perform operations such as unhooking event 
/// handlers.
/// </summary>
/// <typeparam name="T">The type of item in the collection.</typeparam>
public class NoResetObservableCollection<T> : ObservableCollection<T>
{
    public NoResetObservableCollection()
    {
    }

    /// <summary>
    /// Clears all items in the collection by removing them individually.
    /// </summary>
    protected override void ClearItems()
    {
        IList<T> items = new List<T>(this);
        foreach (T item in items)
        {
            Remove(item);
        }
    }
}

هذا موضوع ساخن...لأنه في رأيي أن مايكروسوفت لم تقم بعملها بالشكل الصحيح..مرة أخرى.لا تسيئوا فهمي، أنا أحب Microsoft، لكنها ليست مثالية!

قرأت معظم التعليقات السابقة.أتفق مع كل من يعتقد أن Microsoft لم تقم ببرمجة Clear() بشكل صحيح.

في رأيي، على الأقل، يحتاج الأمر إلى حجة لجعل من الممكن فصل الأشياء عن الحدث ...لكنني أفهم أيضًا تأثير ذلك.ثم فكرت في هذا الحل المقترح.

أتمنى أن يسعد الجميع، أو على الأقل الجميع..

اريك

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;

namespace WpfUtil.Collections
{
    public static class ObservableCollectionExtension
    {
        public static void RemoveAllOneByOne<T>(this ObservableCollection<T> obsColl)
        {
            foreach (T item in obsColl)
            {
                while (obsColl.Count > 0)
                {
                    obsColl.RemoveAt(0);
                }
            }
        }

        public static void RemoveAll<T>(this ObservableCollection<T> obsColl)
        {
            if (obsColl.Count > 0)
            {
                List<T> removedItems = new List<T>(obsColl);
                obsColl.Clear();

                NotifyCollectionChangedEventArgs e =
                    new NotifyCollectionChangedEventArgs
                    (
                        NotifyCollectionChangedAction.Remove,
                        removedItems
                    );
                var eventInfo =
                    obsColl.GetType().GetField
                    (
                        "CollectionChanged",
                        BindingFlags.Instance | BindingFlags.NonPublic
                    );
                if (eventInfo != null)
                {
                    var eventMember = eventInfo.GetValue(obsColl);
                    // note: if eventMember is null
                    // nobody registered to the event, you can't call it.
                    if (eventMember != null)
                        eventMember.GetType().GetMethod("Invoke").
                            Invoke(eventMember, new object[] { obsColl, e });
                }
            }
        }
    }
}

لتبسيط الأمر، لماذا لا تتجاوز طريقة ClearItem وتفعل ما تريد هناك، أي قم بفصل العناصر من الحدث.

public class PeopleAttributeList : ObservableCollection<PeopleAttributeDto>,    {
{
  protected override void ClearItems()
  {
    Do what ever you want
    base.ClearItems();
  }

  rest of the code omitted
}

بسيطة ونظيفة، وتحتوي على رمز المجموعة.

كان لدي نفس المشكلة، وكان هذا هو الحل الخاص بي.يبدو أن العمل.هل يرى أي شخص أي مشاكل محتملة مع هذا النهج؟

// overriden so that we can call GetInvocationList
public override event NotifyCollectionChangedEventHandler CollectionChanged;

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
    if (collectionChanged != null)
    {
        lock (collectionChanged)
        {
            foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
            {
                try
                {
                    handler(this, e);
                }
                catch (NotSupportedException ex)
                {
                    // this will occur if this collection is used as an ItemsControl.ItemsSource
                    if (ex.Message == "Range actions are not supported.")
                    {
                        handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    }
                    else
                    {
                        throw ex;
                    }
                }
            }
        }
    }
}

فيما يلي بعض الطرق المفيدة الأخرى في صفي:

public void SetItems(IEnumerable<T> newItems)
{
    Items.Clear();
    foreach (T newItem in newItems)
    {
        Items.Add(newItem);
    }
    NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

public void AddRange(IEnumerable<T> newItems)
{
    int index = Count;
    foreach (T item in newItems)
    {
        Items.Add(item);
    }
    NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(newItems), index);
    NotifyCollectionChanged(e);
}

public void RemoveRange(int startingIndex, int count)
{
    IList<T> oldItems = new List<T>();
    for (int i = 0; i < count; i++)
    {
        oldItems.Add(Items[startingIndex]);
        Items.RemoveAt(startingIndex);
    }
    NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(oldItems), startingIndex);
    NotifyCollectionChanged(e);
}

// this needs to be overridden to avoid raising a NotifyCollectionChangedEvent with NotifyCollectionChangedAction.Reset, which our other lists don't support
new public void Clear()
{
    RemoveRange(0, Count);
}

public void RemoveWhere(Func<T, bool> criterion)
{
    List<T> removedItems = null;
    int startingIndex = default(int);
    int contiguousCount = default(int);
    for (int i = 0; i < Count; i++)
    {
        T item = Items[i];
        if (criterion(item))
        {
            if (removedItems == null)
            {
                removedItems = new List<T>();
                startingIndex = i;
                contiguousCount = 0;
            }
            Items.RemoveAt(i);
            removedItems.Add(item);
            contiguousCount++;
        }
        else if (removedItems != null)
        {
            NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
            removedItems = null;
            i = startingIndex;
        }
    }
    if (removedItems != null)
    {
        NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
    }
}

private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    OnPropertyChanged(new PropertyChangedEventArgs("Count"));
    OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
    OnCollectionChanged(e);
}

لقد وجدت حلاً "بسيطًا" آخر مستمدًا من ObservableCollection، ولكنه ليس أنيقًا جدًا لأنه يستخدم Reflection...إذا أعجبك هذا هو الحل الخاص بي:

public class ObservableCollectionClearable<T> : ObservableCollection<T>
{
    private T[] ClearingItems = null;

    protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                if (this.ClearingItems != null)
                {
                    ReplaceOldItems(e, this.ClearingItems);
                    this.ClearingItems = null;
                }
                break;
        }
        base.OnCollectionChanged(e);
    }

    protected override void ClearItems()
    {
        this.ClearingItems = this.ToArray();
        base.ClearItems();
    }

    private static void ReplaceOldItems(System.Collections.Specialized.NotifyCollectionChangedEventArgs e, T[] olditems)
    {
        Type t = e.GetType();
        System.Reflection.FieldInfo foldItems = t.GetField("_oldItems", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        if (foldItems != null)
        {
            foldItems.SetValue(e, olditems);
        }
    }
}

أقوم هنا بحفظ العناصر الحالية في حقل مصفوفة بطريقة ClearItems، ثم أعترض استدعاء OnCollectionChanged وأقوم بالكتابة فوق الحقل الخاص e._oldItems (من خلال الانعكاسات) قبل تشغيل base.OnCollectionChanged

يمكنك تجاوز طريقة ClearItems ورفع الحدث باستخدام إجراء الإزالة وOldItems.

public class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>
{
    protected override void ClearItems()
    {
        CheckReentrancy();
        var items = Items.ToList();
        base.ClearItems();
        OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items, -1));
    }
}

جزء من System.Collections.ObjectModel.ObservableCollection<T> ادراك:

public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
    protected override void ClearItems()
    {
        CheckReentrancy();
        base.ClearItems();
        OnPropertyChanged(CountString);
        OnPropertyChanged(IndexerName);
        OnCollectionReset();
    }

    private void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    private void OnCollectionReset()
    {
        OnCollectionChanged(new   NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    private const string CountString = "Count";

    private const string IndexerName = "Item[]";
}

http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx

من فضلك اقرأ هذه الوثائق وأعينك مفتوحة وعقلك في حالة تشغيل.فعلت مايكروسوفت كل شيء بشكل صحيح.يجب عليك إعادة فحص مجموعتك عندما يرسل لك إشعار إعادة التعيين.ستتلقى إشعارًا بإعادة التعيين لأن إجراء إضافة/إزالة لكل عنصر (تتم إزالته من المجموعة وإضافته مرة أخرى) أمر مكلف للغاية.

أوريون إدواردز على حق تمامًا (احترام يا رجل).يرجى التفكير على نطاق أوسع عند قراءة الوثائق.

إذا كان لديك ObservableCollection لم يتم توضيح الأمر، فيمكنك تجربة هذا الكود أدناه.قد يساعدك:

private TestEntities context; // This is your context

context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top