2012-02-22 8 views
11

मैं संग्रह में INotifyPropertyChanged ऑब्जेक्ट्स पर किसी ईवेंट में हुक करने की कोशिश कर रहा हूं।संपत्ति का निरीक्षण करें संग्रह में वस्तुओं पर चेंज किया गया

हर जवाब है कि मैं कभी इस सवाल का देखा है इसे संभाल करने के लिए इस प्रकार कहा है:

void NotifyingItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
{ 
    if(e.NewItems != null) 
    { 
     foreach(INotifyPropertyChanged item in e.NewItems) 
     { 
      item.PropertyChanged += new PropertyChangedEventHandler(CollectionItemChanged); 
     } 
    } 
    if(e.OldItems != null) 
    { 
     foreach(ValidationMessageCollection item in e.OldItems) 
     { 
      item.PropertyChanged -= CollectionItemChanged; 
     } 
    } 
} 

मेरे समस्या यह है कि यह पूरी तरह से विफल रहता है जब भी एक डेवलपर कॉल NotifyingItems संग्रह पर Clear() है। जब ऐसा होता है, तो इस ईवेंट हैंडलर को e.Action == Reset और e.NewItems और e.OldItemsnull के बराबर कहा जाता है (मैं उम्मीद करता हूं कि बाद वाले सभी आइटम शामिल हों)।

समस्या यह है कि वे आइटम नहीं चले जाते हैं, और वे नष्ट नहीं होते हैं, अब उन्हें वर्तमान वर्ग द्वारा निगरानी नहीं की जानी चाहिए - लेकिन जब से मुझे अपने PropertyChangedEventHandler को अनैप करने का मौका कभी नहीं मिला - वे रहते हैं मेरी CollectionItemChanged हैंडलर को कॉल करने के बाद भी मुझे नोटिफ़ाइंगइटम सूची से साफ़ कर दिया गया है। इस 'अच्छी तरह से स्थापित' पैटर्न के साथ ऐसी स्थिति को कैसे संभाला जाना चाहिए?

+1

संभावित डुप्लिकेट [जब एक पर्यवेक्षण चयन समाशोधन, ई.ओल्डइटम में कोई आइटम नहीं है] (http://stackoverflow.com/questions/224155/when-clearing-an-observablecollection-here-are-no-items- इन-ए-olditems) – Rachel

उत्तर

2

अंतिम समाधान की खोज की

मैं एक समाधान दोनों के लिए अनुमति देता है कि मिल गया है, जबकि जोड़ने या केवल एक ईवेंट फायरिंग एक समय में कई आइटम को हटाने की क्षमता को भुनाने - UIElements की जरूरतों और संतुष्ट एक्शन प्राप्त करने के लिए। रीसेट ईवेंट तर्क देता है जबकि अन्य सभी उपयोगकर्ता जोड़े गए और हटाए गए तत्वों की एक सूची चाहते हैं।

इस समाधान में संग्रहChanged घटना को ओवरराइड करना शामिल है। जब हम इस घटना को आग में जाते हैं, तो हम वास्तव में प्रत्येक पंजीकृत हैंडलर के लक्ष्य को देख सकते हैं और अपना प्रकार निर्धारित कर सकते हैं। चूंकि केवल ICollectionView कक्षाओं को NotifyCollectionChangedAction.Reset तर्क देता है जब एक से अधिक आइटम बदलते हैं, हम उन्हें एकल कर सकते हैं, और सभी को उचित ईवेंट तर्क देते हैं जिसमें हटाए गए या जोड़े गए आइटम की पूरी सूची होती है। नीचे कार्यान्वयन है।

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 
} 

उनके सुझावों और लिंक के लिए सभी को धन्यवाद। मैं इस बिंदु पर कभी भी अन्य लोगों के साथ आने वाले सभी बढ़ते बेहतर समाधानों को देखे बिना कभी नहीं मिला होता।

+0

आपके समाधान Alain के लिए धन्यवाद। हालांकि मुझे एक छोटी सी बग मिली। "जोड़ें" और "निकालें" विधियों में, आप पैरामीटर में IENumerable से दोगुना दोहराते हैं। तो उदाहरण के लिए यदि वह IENumerable ऑब्जेक्ट्स बनायेगा, तो उन्हें दो बार बनाया जाएगा। इस चाल को करने से पहले इसे कैश करने के लिए, इस तरह: var toAddList = IList के रूप में जोड़ें ?? toAdd.ToList(); और वैसे भी, आप अंत में गणना के बाहर एक सूची बना रहे हैं। – FrankyB

+0

@ फ्रैंकबी आप सही हैं। ReSharper ने मुझे अपने तरीकों की त्रुटि दिखाए जाने से पहले यह शुरुआती दिनों में था :) – Alain

5

शायद this answer

पर एक नज़र यह .Clear() का उपयोग नहीं करने का सुझाव और एक .RemoveAll() विस्तार विधि है कि आइटम एक-एक करके

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

है कि आप के लिए काम नहीं करता है निकाल देंगे लागू करने ले , लिंक में भी अन्य अच्छे समाधान पोस्ट किए गए हैं।

+0

धन्यवाद, यह वास्तव में इस प्रश्न के एक सटीक डुप्लिकेट की तरह दिखता है, बस बेहतर phrased। – Alain

+0

मुझे लगता है कि आपको वास्तव में इस समस्या का सामना करना पड़ा है। [लिंक] (http://stackoverflow.com/questions/7449196/how-can-i-raise-a-collectionchanged-event-on-an-observablecollection-and-pass-i) क्या आपको कभी सौदा करने का कोई तरीका मिला सैकड़ों संपत्तियों को आग लगने के बिना इस थोक स्पष्ट के साथ घटनाओं को बदल दिया? (आईई, UIElements के लिए "साफ़ करें" ईवेंट बढ़ाएं, और बाकी सब कुछ के लिए निकालें ईवेंट?) – Alain

+0

@ एलन मैंने कभी नहीं किया। इसके बजाए, जब 'अddRange() 'या 'RemoveRange()' का प्रदर्शन बहुत लंबा हुआ, तो मैंने अभी संग्रह को फिर से बनाया है। आमतौर पर संग्रह के 'सेट' विधि में कुछ था जो पुराने संग्रह के सभी ईवेंट हैंडलर को अनदेखा करने के साथ-साथ नए को जोड़ने से पहले था। यह निश्चित रूप से एक आदर्श समाधान नहीं है, लेकिन यह काम किया। – Rachel

0

रीसेट बदले गए आइटम प्रदान नहीं करता है। यदि आप साफ़ का उपयोग करना जारी रखते हैं तो आपको घटनाओं को साफ़ करने के लिए एक अलग संग्रह बनाए रखना होगा।

एक आसान और अधिक मेमोरी कुशल समाधान आपके स्वयं के स्पष्ट कार्य को बनाने और संग्रह की स्पष्ट कॉल करने के बजाय प्रत्येक आइटम को हटाने के लिए होगा।

void ClearCollection() 
    { 
     while(collection.Count > 0) 
     { 
      // Could handle the event here... 
      // collection[0].PropertyChanged -= CollectionItemChanged; 
      collection.RemoveAt(collection.Count -1); 
     } 
    } 
+0

इस समाधान के साथ मेरी एकमात्र समस्या है, जैसा कि प्रश्न में संकेत दिया गया है, इस वर्ग और संग्रह का उपयोग अन्य डेवलपर्स द्वारा किया जाता है, और इस कोड के बारे में कुछ भी नहीं है जो मुझे अन्य डेवलपर्स को संग्रह पर "साफ़()" का उपयोग न करने की अनुमति देता है - विधि वहां है और वे इसे प्यार करते हैं। अगर किसी ने कभी किया है, तो यह रनटाइम बग का निदान करने के लिए खुद को बहुत मुश्किल के रूप में प्रकट करेगा। – Alain

+0

एक नई विरासत कक्षा बनाना और कार्यों को ओवरराइड करना वास्तव में आपका एकमात्र समाधान है। लेकिन आप पहले से ही निष्कर्ष निकाला है कि, बहुत अच्छी किस्मत। – JeremyK

1

मैं जो ClearItems विधि ओवरराइड करता है ObservableCollection<T> के अपने खुद के उपवर्ग बनाकर इस समस्या का समाधान। आधार कार्यान्वयन को कॉल करने से पहले, यह CollectionChanging ईवेंट उठाता है जिसे मैंने अपनी कक्षा में परिभाषित किया था।

CollectionChanging संग्रह वास्तव में साफ़ होने से पहले आग लगती है, और इस प्रकार आपको घटना की सदस्यता लेने और घटनाओं से सदस्यता समाप्त करने का अवसर मिलता है।

उदाहरण:

public event NotifyCollectionChangedEventHandler CollectionChanging; 

protected override void ClearItems() 
{ 
    if (this.Items.Count > 0) 
    { 
     this.OnCollectionChanging(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
    } 

    base.ClearItems(); 
} 

protected virtual void OnCollectionChanging(NotifyCollectionChangedEventArgs eventArgs) 
{ 
    if (this.CollectionChanging != null) 
    { 
     this.CollectionChanging(this, eventArgs); 
    } 
} 
+0

यह एक वैध समाधान है, हालांकि मैं ऐसे किसी के लिए प्रयास कर रहा हूं जिसके लिए अन्य डेवलपर्स की आवश्यकता नहीं है "हमेशा इस नए कार्यक्रम को संभालने के लिए याद रखें या यह काम नहीं करेगा।" इस तरह के नियम जिन्हें संकलन समय पर लागू नहीं किया जा सकता है, एक से अधिक डेवलपर वाली परियोजनाओं में अभ्यास में कभी भी काम नहीं करते हैं। – Alain

+0

ठीक है, आप हमेशा ऊपर दिए गए कार्यों के आधार पर अपना स्वयं का संग्रह प्रकार बना सकते हैं, जो आंतरिक रूप से किसी तत्व को हटाए जाने पर सदस्यता रद्द करने का ख्याल रखता है या संग्रह को – RobSiklos

1

संपादित करें:

अगर मैं अपने NotifyingItems बदल देते हैं: यह समाधान सवाल राहेल से जुड़ा हुआ से

This solution काम नहीं करता शानदार प्रतीत होता है एक उत्तराधिकारी वर्ग के साथ अवलोकन योग्य चयन जो ओवरराइडेबल संग्रह को ओवरराइड करता है।ClearItems() विधि, तो मैं NotifyCollectionChangedEventArgs को रोक सकता है और एक रीसेट आपरेशन के बजाय एक निकालें साथ बदलें, और हटा दिया आइटमों की सूची पारित:

//Makes sure on a clear, the list of removed items is actually included. 
protected override void ClearItems() 
{ 
    if(this.Count == 0) return; 

    List<T> removed = new List<T>(this); 
    base.ClearItems(); 
    base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); 
} 

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
{ 
    //If the action is a reset (from calling base.Clear()) our overriding Clear() will call OnCollectionChanged, but properly. 
    if(e.Action != NotifyCollectionChangedAction.Reset) 
     base.OnCollectionChanged(e); 
} 

शानदार है, और कुछ भी नहीं है को छोड़कर कहीं भी बदलने की आवश्यकता मेरी अपनी कक्षा


* संपादित करें *

मैं इस समाधान प्यार करता था, लेकिन यह काम नहीं करता है ... आप एक NotifyCollectionChangedEventArgs एक से अधिक आइटम है कि बदल बढ़ाने के लिए अनुमति नहीं है जब तक कि कार्रवाई "रीसेट" न हो। आपको निम्न रनटाइम अपवाद मिलता है: Range actions are not supported। मुझे नहीं पता कि इसे इस बारे में इतना चुनौतीपूर्ण क्यों होना चाहिए, लेकिन अब यह एक ही समय में प्रत्येक आइटम को हटाने के लिए कोई विकल्प नहीं छोड़ता है ... प्रत्येक के लिए एक नया संग्रह बदल दिया गया आयोजन। क्या एक परेशानी परेशानी है।

+0

साफ़ कर दिया जाता है, तो मेरा जवाब? : पी – JeremyK

+0

मैं उपर्युक्त रनटाइम अपवाद के लिए एक समाधान के लिए आया था, जिसे कलेक्शन व्यू क्लास (जिसे सभी आइटम लिस्टिंग UIElements उपयोग करते हैं) द्वारा फेंक दिया जा रहा था। समाधान नीचे पोस्ट किया गया है: http://stackoverflow.com/a/9416568/529618 – Alain

संबंधित मुद्दे

 संबंधित मुद्दे