เมื่อล้าง ObservableCollection ไม่มีรายการใน e.OldItems


92

ฉันมีบางอย่างที่นี่ที่ทำให้ฉันไม่ทันระวัง

ฉันมี ObservableCollection ของ T ที่เต็มไปด้วยไอเท็ม ฉันยังมีตัวจัดการเหตุการณ์ที่แนบมากับเหตุการณ์ CollectionChanged

เมื่อคุณล้างคอลเลกชันจะทำให้เกิดเหตุการณ์ CollectionChanged ด้วย e.Action ที่ตั้งค่าเป็น NotifyCollectionChangedAction.Reset โอเคเป็นเรื่องปกติ แต่สิ่งที่แปลกคือไม่มี e.OldItems หรือ e.NewItems มีอะไรอยู่ในนั้น ฉันคาดหวังว่า e.OldItems จะเต็มไปด้วยรายการทั้งหมดที่ถูกลบออกจากคอลเลกชัน

มีใครเห็นสิ่งนี้อีกบ้าง? แล้วถ้าเป็นเช่นนั้นพวกเขาไปถึงมันได้อย่างไร?

ความเป็นมาบางประการ: ฉันใช้เหตุการณ์ CollectionChanged เพื่อแนบและแยกออกจากเหตุการณ์อื่นดังนั้นหากฉันไม่ได้รับไอเท็มใด ๆ ใน e.OldItems ... ฉันจะไม่สามารถแยกออกจากเหตุการณ์นั้น


ชี้แจง: ฉันจะรู้ว่าเอกสารไม่ตรงไปตรงมาว่ารัฐจะมีการทำงานในลักษณะนี้ แต่สำหรับการกระทำอื่น ๆ มันเป็นการแจ้งให้ฉันทราบถึงสิ่งที่ทำลงไป ดังนั้นสมมติฐานของฉันก็คือมันจะบอกฉัน ... ในกรณีของ Clear / Reset เช่นกัน


ด้านล่างนี้คือตัวอย่างโค้ดหากคุณต้องการทำซ้ำด้วยตัวเอง ก่อนอื่นจาก 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 };
    }
}

ทำไมคุณต้องยกเลิกการสมัครเข้าร่วมกิจกรรม? คุณสมัครสมาชิกไปในทิศทางใด เหตุการณ์สร้างการอ้างอิงถึงผู้สมัครสมาชิกที่จัดขึ้นโดยผู้ระดมทุนไม่ใช่ในทางอื่น หากลูกเกดเป็นสิ่งของในคอลเลกชั่นที่ได้รับการเคลียร์พวกเขาจะถูกเก็บรวบรวมอย่างปลอดภัยและข้อมูลอ้างอิงจะหายไป - ไม่มีการรั่ว หากรายการเป็นสมาชิกและอ้างอิงโดยผู้เพิ่มรายเดียวให้ตั้งค่าเหตุการณ์เป็น null ใน raiser เมื่อคุณได้รับการรีเซ็ต - ไม่จำเป็นต้องยกเลิกการสมัครทีละรายการ
Aleksandr Dubinsky

เชื่อฉันฉันรู้ว่ามันทำงานอย่างไร เหตุการณ์ที่เป็นปัญหาอยู่บนซิงเกิลตันที่ติดอยู่เป็นเวลานาน ... ดังนั้นรายการในคอลเลกชันจึงเป็นของสมาชิก วิธีแก้ปัญหาของคุณในการตั้งค่าเหตุการณ์เป็นโมฆะไม่ทำงาน ... เนื่องจากเหตุการณ์ยังคงต้องเริ่มทำงาน ... อาจเป็นการแจ้งเตือนสมาชิกรายอื่น (ไม่จำเป็นต้องอยู่ในคอลเล็กชัน)
cplotts

คำตอบ:


46

ไม่ได้อ้างว่ารวมรายการเก่าเนื่องจากการรีเซ็ตไม่ได้หมายความว่ารายการถูกล้าง

หมายความว่ามีบางสิ่งที่น่าทึ่งเกิดขึ้นและค่าใช้จ่ายในการเพิ่ม / ลบมักจะเกินค่าใช้จ่ายในการสแกนรายการใหม่ตั้งแต่ต้น ... ดังนั้นนั่นคือสิ่งที่คุณควรทำ

MSDN แนะนำตัวอย่างของคอลเล็กชันทั้งหมดที่ถูกจัดเรียงใหม่เป็นตัวเลือกสำหรับการรีเซ็ต

เพื่อย้ำ. การรีเซ็ตไม่ได้หมายความว่าชัดเจนหมายความว่าตอนนี้สมมติฐานของคุณเกี่ยวกับรายการไม่ถูกต้อง รักษามันราวกับว่ามันเป็นรายการใหม่ทั้งหมด การเคลียร์เกิดขึ้นได้ในกรณีนี้ แต่อาจมีอื่น ๆ

ตัวอย่างบางส่วน:
ฉันมีรายการแบบนี้พร้อมรายการมากมายและเป็นฐานข้อมูลไปยัง WPF ListViewเพื่อแสดงบนหน้าจอ
หากคุณล้างรายการและเพิ่ม.Resetเหตุการณ์ประสิทธิภาพจะค่อนข้างทันที แต่ถ้าคุณเพิ่ม.Removeเหตุการณ์แต่ละเหตุการณ์ประสิทธิภาพจะแย่มากเนื่องจาก WPF ลบรายการทีละรายการ ฉันยังใช้.Resetในรหัสของตัวเองเพื่อระบุว่ารายการได้รับการจัดเรียงใหม่แทนที่จะออกMoveการดำเนินการแต่ละรายการหลายพันรายการ เช่นเดียวกับ Clear มีประสิทธิภาพที่ยอดเยี่ยมเมื่อเพิ่มเหตุการณ์แต่ละเหตุการณ์


1
ฉันจะไม่เห็นด้วยอย่างเคารพบนพื้นฐานนี้ ถ้าคุณดูเอกสารที่ระบุไว้: แสดงถึงการรวบรวมข้อมูลแบบไดนามิกที่ให้การแจ้งเตือนเมื่อมีการเพิ่มลบหรือเมื่อรายการทั้งหมดถูกรีเฟรช (ดูmsdn.microsoft.com/en-us/library/ms668613(v=VS .100) .aspx )
cplotts

6
เอกสารระบุว่าควรแจ้งให้คุณทราบเมื่อมีการเพิ่ม / ลบ / รีเฟรชรายการ แต่ไม่ได้สัญญาว่าจะบอกรายละเอียดทั้งหมดของรายการ ... เพียงแค่ว่ามีเหตุการณ์เกิดขึ้น จากมุมมองนี้พฤติกรรมก็ดี โดยส่วนตัวแล้วฉันคิดว่าพวกเขาควรจะใส่ไอเท็มทั้งหมดลงไปOldItemsเมื่อทำการล้าง (มันเป็นแค่การคัดลอกรายการ) แต่อาจมีบางสถานการณ์ที่ราคาแพงเกินไป ในอัตราใด ๆ ถ้าคุณต้องการที่คอลเลกชันที่ไม่แจ้งให้คุณทราบทุกรายการที่ถูกลบมันจะไม่ยากที่จะทำ
Orion Edwards

2
ดีถ้าคือการบ่งบอกถึงการดำเนินงานที่มีราคาแพงเป็นไปได้มากว่าการให้เหตุผลเช่นเดียวกับการคัดลอกในช่วงรายการทั้งหมดเพื่อReset OldItems
pbalaga

7
ความเป็นจริงตลก: ตั้งแต่.NET 4.5 , Resetจริงหมายถึง "เนื้อหาของคอลเลกชันที่ได้รับการล้าง ." ดูmsdn.microsoft.com/en-us/library/…
Athari

9
คำตอบนี้ไม่ได้ช่วยอะไรมากขออภัย ได้คุณสามารถสแกนรายการทั้งหมดอีกครั้งได้หากคุณได้รับการรีเซ็ต แต่คุณไม่สามารถเข้าถึงเพื่อลบรายการซึ่งคุณอาจต้องลบตัวจัดการเหตุการณ์ออกจากรายการเหล่านั้น นี่คือปัญหาใหญ่
Virus721

22

เรามีปัญหาเดียวกันที่นี่ การดำเนินการรีเซ็ตใน CollectionChanged ไม่รวม OldItems เรามีวิธีแก้ปัญหา: เราใช้วิธีการขยายต่อไปนี้แทน:

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

สุดท้ายเราไม่สนับสนุนฟังก์ชัน Clear () และโยน NotSupportedException ในเหตุการณ์ CollectionChanged เพื่อรีเซ็ตการกระทำ RemoveAll จะทริกเกอร์การดำเนินการลบในเหตุการณ์ CollectionChanged ด้วย OldItems ที่เหมาะสม


ความคิดที่ดี. ฉันไม่ชอบที่จะไม่สนับสนุน Clear เนื่องจากเป็นวิธีการ (จากประสบการณ์ของฉัน) ที่คนส่วนใหญ่ใช้ ... แต่อย่างน้อยคุณก็เตือนผู้ใช้ด้วยข้อยกเว้น
cplotts

ฉันยอมรับนี่ไม่ใช่ทางออกที่ดี แต่เราพบว่าเป็นวิธีแก้ปัญหาที่ดีที่สุดที่ยอมรับได้
decasteljau

16
ปัญหา Orion พร้อมคำแนะนำของคุณ ... เป็นกรณีการใช้งานที่ทำให้เกิดคำถามนี้ จะเกิดอะไรขึ้นเมื่อฉันมีรายการที่ต้องการแยกเหตุการณ์ออกจากรายการ ฉันไม่สามารถถ่ายโอนข้อมูลในรายการได้ ... มันจะส่งผลให้หน่วยความจำรั่ว / ความดัน
cplotts

1
อีกจุดหนึ่งที่ต้องทำที่นี่ ... ก็คือเมื่อคุณได้รับเหตุการณ์ CollectionChanged พร้อมกับ Action of Reset (จากการเรียก Clear) ... คอลเลคชันว่างเปล่าแล้ว เหตุใดคุณจึงไม่สามารถใช้เพื่อแยกออกจากเหตุการณ์ได้ ดังนั้นอีกครั้งฉันไม่สามารถเพียงแค่ถ่ายโอนข้อมูลของฉัน มันถูกทิ้งไปแล้วสำหรับฉัน
cplotts

5
ข้อเสียที่สำคัญของโซลูชันนี้คือถ้าคุณลบ 1,000 รายการคุณจะเริ่มการทำงาน CollectionChanged 1,000 ครั้งและ UI ต้องอัปเดต CollectionView 1,000 ครั้ง (การอัปเดตองค์ประกอบ UI มีราคาแพง) หากคุณไม่กลัวที่จะลบล้างคลาส ObservableCollection คุณสามารถทำให้มันเริ่มการทำงานของเหตุการณ์ Clear () แต่ให้ Args เหตุการณ์ที่ถูกต้องทำให้โค้ดการมอนิเตอร์สามารถยกเลิกการลงทะเบียนองค์ประกอบที่ถูกลบทั้งหมดได้
Alain

13

อีกทางเลือกหนึ่งคือการแทนที่เหตุการณ์รีเซ็ตด้วยเหตุการณ์ลบรายการเดียวที่มีรายการที่เคลียร์ทั้งหมดในคุณสมบัติ 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. สมาชิกจะต้องตรวจสอบ NewItems & OldItems ในเหตุการณ์ใด ๆ เท่านั้นเพื่อเพิ่ม / ลบตัวจัดการเหตุการณ์ตามที่กำหนด

ข้อเสีย:

  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 เป็นเท็จ (ค่าเริ่มต้น) เนื่องจากมีการสร้างการแจ้งลบหนึ่งรายการต่อวัตถุในคอลเลกชัน


ฉันชอบสิ่งนี้ แต่น่าเสียดายที่ Silverlight 4 NotifyCollectionChangedEventArgs ไม่มีตัวสร้างที่ใช้รายการของรายการ
Simon Brangwin

2
ฉันชอบโซลูชันนี้ แต่ไม่ได้ผล ... คุณไม่ได้รับอนุญาตให้เพิ่ม NotifyCollectionChangedEventArgs ที่มีการเปลี่ยนแปลงมากกว่าหนึ่งรายการเว้นแต่การกระทำจะเป็น "รีเซ็ต" คุณได้รับข้อยกเว้นRange actions are not supported.ฉันไม่รู้ว่าทำไมจึงทำเช่นนี้ แต่ตอนนี้ไม่มีตัวเลือกนอกจากจะลบทีละรายการ ...
Alain

2
@Alain The ObservableCollection ไม่ได้กำหนดข้อ จำกัด นี้ ฉันสงสัยว่าเป็นตัวควบคุม WPF ที่คุณผูกคอลเลกชันไว้ ฉันมีปัญหาเดียวกันและไม่เคยโพสต์การอัปเดตพร้อมวิธีแก้ปัญหาของฉันเลย ฉันจะแก้ไขคำตอบของฉันด้วยคลาสที่แก้ไขซึ่งใช้งานได้เมื่อผูกไว้กับตัวควบคุม WPF
Grantnz

ฉันเห็นว่าตอนนี้ จริง ๆ แล้วฉันพบวิธีแก้ปัญหาที่สวยงามมากที่แทนที่เหตุการณ์ CollectionChanged และวนซ้ำforeach( NotifyCollectionChangedEventHandler handler in this.CollectionChanged )ถ้าhandler.Target is CollectionViewคุณสามารถยิงตัวจัดการด้วยAction.Resetargs มิฉะนั้นคุณสามารถระบุ args แบบเต็มได้ ดีที่สุดของทั้งสองโลกด้วยตัวจัดการโดยตัวจัดการ :) แบบว่ามีอะไรบ้างที่นี่: stackoverflow.com/a/3302917/529618
Alain

ฉันโพสต์วิธีแก้ปัญหาของตัวเองด้านล่าง stackoverflow.com/a/9416535/529618 ขอบคุณมากสำหรับโซลูชันที่สร้างแรงบันดาลใจของคุณ มันพาฉันไปที่นั่นได้ครึ่งทางแล้ว
Alain

10

โอเคฉันรู้ว่านี่เป็นคำถามเก่ามาก แต่ฉันได้หาวิธีแก้ปัญหาที่ดีและคิดว่าจะแบ่งปัน โซลูชันนี้ได้รับแรงบันดาลใจจากคำตอบที่ยอดเยี่ยมมากมายที่นี่ แต่มีข้อดีดังต่อไปนี้:

  • ไม่จำเป็นต้องสร้างคลาสใหม่และแทนที่เมธอดจาก ObservableCollection
  • ไม่รบกวนการทำงานของ NotifyCollectionChanged (ดังนั้นอย่าไปยุ่งกับการรีเซ็ต)
  • ไม่ใช้ประโยชน์จากการสะท้อนแสง

นี่คือรหัส:

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

วิธีการส่วนขยายนี้ใช้เวลาเพียงเล็กน้อยActionซึ่งจะถูกเรียกใช้ก่อนที่จะมีการล้างคอลเล็กชัน


ความคิดที่ดีมาก เรียบง่ายหรูหรา
cplotts

9

ฉันได้พบวิธีแก้ปัญหาที่ช่วยให้ผู้ใช้สามารถใช้ประโยชน์จากประสิทธิภาพในการเพิ่มหรือลบหลายรายการได้ในขณะที่เริ่มทำงานเพียงเหตุการณ์เดียว - และตอบสนองความต้องการของ UIElements เพื่อรับการดำเนินการรีเซ็ตอาร์กิวเมนต์ของเหตุการณ์ในขณะที่ผู้ใช้รายอื่นทั้งหมดจะทำ เช่นรายการขององค์ประกอบที่เพิ่มและลบ

โซลูชันนี้เกี่ยวข้องกับการลบล้างเหตุการณ์ CollectionChanged เมื่อเราไปยิงเหตุการณ์นี้เราสามารถดูเป้าหมายของตัวจัดการที่ลงทะเบียนแต่ละตัวและกำหนดประเภทได้ เนื่องจากมีเพียงคลาส ICollectionView เท่านั้นที่ต้องการNotifyCollectionChangedAction.Resetargs เมื่อมีการเปลี่ยนแปลงมากกว่าหนึ่งรายการเราจึงสามารถแยกรายการเหล่านั้นออกและมอบ 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
}

7

โอเคแม้ว่าฉันจะยังคงหวังว่า 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();
    }
}

1
คุณต้องเปลี่ยนชื่อชั้นเรียนของคุณBrokenObservableCollectionไม่ใช่TrulyObservableCollection- คุณเข้าใจผิดว่าการดำเนินการรีเซ็ตหมายถึงอะไร
Orion Edwards

1
@Orion Edwards: ฉันไม่เห็นด้วย ดูความคิดเห็นของฉันสำหรับคำตอบของคุณ
cplotts

1
@ Orion Edwards: โอ้รอฉันเห็นว่าคุณกำลังตลก แต่ฉันควรเรียกมันว่า: ActuallyUsefulObservableCollection. :)
cplotts

6
ฮ่า ๆ ชื่อที่ดี ฉันยอมรับว่านี่เป็นการกำกับดูแลอย่างจริงจังในการออกแบบ
devios1

1
หากคุณกำลังจะใช้คลาส ObservableCollection ใหม่ต่อไปคุณไม่จำเป็นต้องสร้างเหตุการณ์ใหม่ที่ต้องตรวจสอบแยกกัน คุณสามารถป้องกันไม่ให้ ClearItems ทริกเกอร์อาร์กิวเมนต์เหตุการณ์ Action = Reset และแทนที่ด้วย Action = Remove อาร์กิวเมนต์เหตุการณ์ที่มีรายการ e.OldItems ของรายการทั้งหมดที่อยู่ในรายการ ดูวิธีแก้ปัญหาอื่น ๆ ในคำถามนี้
Alain

4

ฉันจัดการเหตุการณ์นี้ในลักษณะที่แตกต่างไปเล็กน้อยเนื่องจากฉันต้องการลงทะเบียนเข้าร่วมกิจกรรมหนึ่งและจัดการการเพิ่มและการลบทั้งหมดในตัวจัดการเหตุการณ์ ฉันเริ่มต้นด้วยการลบล้างเหตุการณ์ที่เปลี่ยนแปลงคอลเล็กชันและเปลี่ยนเส้นทางการดำเนินการรีเซ็ตเป็นการดำเนินการลบด้วยรายการ ทั้งหมดนี้เกิดข้อผิดพลาดเนื่องจากฉันใช้คอลเล็กชันที่สังเกตได้เป็นแหล่งรายการสำหรับมุมมองคอลเลกชันและได้รับ "ไม่รองรับการดำเนินการในช่วง"

ในที่สุดฉันก็สร้างเหตุการณ์ใหม่ชื่อ CollectionChangedRange ซึ่งทำหน้าที่ในลักษณะที่ฉันคาดหวังให้เวอร์ชัน inbuilt ทำงาน

ฉันนึกไม่ออกว่าทำไมข้อ จำกัด นี้ถึงได้รับอนุญาตและหวังว่าอย่างน้อยโพสต์นี้จะหยุดไม่ให้คนอื่นไปถึงทางตันที่ฉันทำ

/// <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);
        }
    }

}

แนวทางที่น่าสนใจ ขอบคุณที่โพสต์ หากฉันเคยประสบปัญหากับแนวทางของตัวเองฉันคิดว่าฉันจะกลับมาทบทวนของคุณ
cplotts

3

นี่คือวิธีการทำงานของ ObservableCollection คุณสามารถหลีกเลี่ยงปัญหานี้ได้โดยเก็บรายการของคุณเองไว้นอก ObservableCollection (เพิ่มในรายการเมื่อมีการเพิ่มการดำเนินการลบเมื่อการดำเนินการถูกลบเป็นต้น) จากนั้นคุณจะได้รับรายการที่ถูกลบทั้งหมด (หรือรายการที่เพิ่มเข้ามา ) เมื่อการกระทำถูกรีเซ็ตโดยการเปรียบเทียบรายการของคุณกับ ObservableCollection

อีกทางเลือกหนึ่งคือการสร้างคลาสของคุณเองที่ใช้ IList และ INotifyCollectionChanged จากนั้นคุณสามารถแนบและแยกเหตุการณ์จากภายในคลาสนั้นได้ (หรือตั้งค่า OldItems เป็น Clear ก็ได้ถ้าคุณต้องการ) - มันไม่ยากเลย แต่เป็นการพิมพ์จำนวนมาก


ฉันพิจารณาติดตามรายการอื่นเช่นเดียวกับที่คุณแนะนำก่อน แต่ดูเหมือนว่าจะมีงานที่ไม่จำเป็นมากมาย ข้อเสนอแนะที่สองของคุณใกล้เคียงกับสิ่งที่ฉันลงเอยด้วย ... ซึ่งฉันจะโพสต์เป็นคำตอบ
cplotts

3

สำหรับสถานการณ์จำลองของการเชื่อมต่อและการแยกตัวจัดการเหตุการณ์กับองค์ประกอบของ ObservableCollection ยังมีโซลูชัน "ฝั่งไคลเอ็นต์" ในโค้ดการจัดการเหตุการณ์คุณสามารถตรวจสอบว่าผู้ส่งอยู่ใน ObservableCollection หรือไม่โดยใช้เมธอดประกอบด้วย Pro: คุณสามารถทำงานกับ 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;
            }   
        }
    }

ฉันเชื่อว่าด้วยแนวทางแรกของคุณฉันต้องการรายการอื่นเพื่อติดตามรายการ ... เพราะเมื่อคุณได้รับเหตุการณ์ CollectionChanged พร้อมกับการดำเนินการรีเซ็ต ... ฉันไม่ค่อยทำตามคำแนะนำที่สองของคุณ ฉันชอบสายรัดทดสอบแบบเรียบง่ายที่แสดงให้เห็น แต่สำหรับการเพิ่มการลบและการล้าง ObservableCollection หากคุณสร้างตัวอย่างคุณสามารถส่งอีเมลถึงฉันที่ชื่อของฉันตามด้วยนามสกุลของฉันที่ gmail.com
cplotts

2

เมื่อดูที่NotifyCollectionChangedEventArgsปรากฏว่า OldItems มีเฉพาะรายการที่เปลี่ยนแปลงอันเป็นผลมาจากการดำเนินการแทนที่ลบหรือย้าย ไม่ได้ระบุว่าจะมีอะไรใน Clear ฉันสงสัยว่า Clear ทำให้เหตุการณ์เริ่มทำงาน แต่ไม่ได้ลงทะเบียนรายการที่ถูกลบและไม่เรียกใช้รหัส Remove เลย


6
ฉันก็เห็นเหมือนกัน แต่ฉันไม่ชอบมัน ดูเหมือนว่าเป็นช่องโหว่สำหรับฉัน
cplotts

ไม่เรียกใช้รหัสลบเนื่องจากไม่จำเป็นต้อง การรีเซ็ตหมายถึง "มีบางอย่างเกิดขึ้นอย่างมากคุณต้องเริ่มใหม่อีกครั้ง" การดำเนินการที่ชัดเจนเป็นตัวอย่างหนึ่งของสิ่งนี้ แต่ยังมีอีกตัวอย่างหนึ่ง
Orion Edwards

2

ฉันตัดสินใจที่จะสกปรกด้วยตัวเอง

Microsoft ใส่งานไว้เป็นจำนวนมากเพื่อให้แน่ใจว่า NotifyCollectionChangedEventArgs ไม่มีข้อมูลใด ๆ เมื่อเรียกการรีเซ็ต ฉันคิดว่านี่เป็นการตัดสินใจเรื่องประสิทธิภาพ / ความทรงจำ หากคุณกำลังรีเซ็ตคอลเล็กชันที่มีองค์ประกอบ 100,000 รายการฉันคิดว่าพวกเขาไม่ต้องการทำซ้ำองค์ประกอบเหล่านั้นทั้งหมด

แต่เนื่องจากคอลเลกชันของฉันไม่เคยมีมากกว่า 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);
    }

สิ่งนี้ยอดเยี่ยม แต่อาจใช้ไม่ได้กับอะไรนอกจากสภาพแวดล้อมที่ไว้วางใจได้อย่างเต็มที่ การสะท้อนให้เห็นถึงพื้นที่ส่วนตัวต้องการความไว้วางใจอย่างเต็มที่ใช่ไหม?
พอล

1
ทำไมคุณถึงทำเช่นนี้? มีสิ่งอื่นที่อาจทำให้การดำเนินการรีเซ็ตเริ่มทำงานได้ - เพียงเพราะคุณปิดใช้งานวิธีการล้างไม่ได้หมายความว่ามันหายไป (หรือควรจะเป็น)
Orion Edwards

แนวทางที่น่าสนใจ แต่การไตร่ตรองทำได้ช้า
cplotts

2

ObservableCollection และอินเทอร์เฟซ INotifyCollectionChanged เขียนไว้อย่างชัดเจนโดยคำนึงถึงการใช้งานเฉพาะ: การสร้าง UI และลักษณะการทำงานที่เฉพาะเจาะจง

เมื่อคุณต้องการการแจ้งเตือนการเปลี่ยนแปลงคอลเลกชันโดยทั่วไปคุณจะสนใจเฉพาะกิจกรรมเพิ่มและเอาออก

ฉันใช้อินเทอร์เฟซต่อไปนี้:

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 เพิ่มการลบ
  • InsertItem เพิ่มเข้ามา
  • RemoveItem เพิ่มการลบ
  • SetItem เพิ่มการลบและเพิ่ม

แน่นอนว่าสามารถเพิ่ม AddRange ได้เช่นกัน


+1 เพื่อชี้ให้เห็นว่า Microsoft ออกแบบ ObservableCollection โดยคำนึงถึงกรณีการใช้งานที่เฉพาะเจาะจง ... และคำนึงถึงประสิทธิภาพ ฉันเห็นด้วย. เหลือช่องว่างสำหรับสถานการณ์อื่น ๆ แต่ฉันเห็นด้วย
cplotts

-1 ฉันอาจสนใจในทุกสิ่ง บ่อยครั้งที่ฉันต้องการดัชนีของรายการที่เพิ่ม / ลบออก ฉันอาจต้องการเพิ่มประสิทธิภาพการแทนที่ ฯลฯ การออกแบบของ INotifyCollectionChanged ก็ดี ปัญหาที่ควรแก้ไขคือ nooone ที่ MS นำมาใช้
Aleksandr Dubinsky

1

ฉันเพิ่งอ่านโค้ดแผนภูมิบางส่วนในชุดเครื่องมือ Silverlight และ WPF และสังเกตเห็นว่าพวกเขาแก้ไขปัญหานี้ด้วย (ในลักษณะที่คล้ายกัน) ... และฉันคิดว่าฉันจะโพสต์วิธีแก้ปัญหาต่อไป

โดยพื้นฐานแล้วพวกเขายังสร้าง ObservableCollection ที่ได้รับมาและลบล้าง ClearItems โดยเรียก Remove ในแต่ละรายการที่ถูกล้าง

นี่คือรหัส:

/// <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);
        }
    }
}

ฉันแค่ต้องการชี้ให้เห็นว่าฉันไม่ชอบวิธีนี้มากเท่ากับวิธีที่ฉันทำเครื่องหมายเป็นคำตอบ ... เนื่องจากคุณได้รับเหตุการณ์ NotifyCollectionChanged (พร้อมการดำเนินการลบ) ... สำหรับแต่ละรายการที่ถูกลบออก
cplotts

1

นี่เป็นประเด็นร้อน ... เพราะในความคิดของฉัน Microsoft ทำงานไม่ถูกต้อง ... อย่าเข้าใจผิดฉันชอบ 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 });
                }
            }
        }
    }
}

ฉันยังคิดว่า Microsoft ควรมีวิธีที่จะสามารถล้างการแจ้งเตือนได้ ฉันยังคิดว่าพวกเขาพลาดช็อตโดยไม่ได้ให้แบบนั้น ขออภัย! ผมไม่ได้บอกว่าควรลบนะครับมีอะไรหาย !!! เพื่อให้ได้การมีเพศสัมพันธ์ที่ต่ำบางครั้งเราต้องได้รับคำแนะนำถึงสิ่งที่ถูกลบออกไป
Eric Ouellet

1

เพื่อให้มันง่ายทำไมคุณไม่แทนที่เมธอด ClearItem และทำอะไรก็ได้ที่คุณต้องการนั่นคือถอดไอเท็มออกจากเหตุการณ์

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

  rest of the code omitted
}

เรียบง่ายสะอาดตาและมีรหัสคอลเลกชัน


นั่นใกล้เคียงกับสิ่งที่ฉันทำจริง ... ดูคำตอบที่ยอมรับ
cplotts

0

ฉันมีปัญหาเดียวกันและนี่คือวิธีแก้ปัญหาของฉัน ดูเหมือนว่าจะได้ผล มีใครเห็นปัญหาที่อาจเกิดขึ้นกับแนวทางนี้หรือไม่?

// 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);
}

0

ฉันพบวิธีแก้ปัญหา "ง่ายๆ" อีกวิธีหนึ่งที่ได้มาจาก 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 (ผ่านการสะท้อน) ก่อนที่จะเปิดฐาน


0

คุณสามารถแทนที่เมธอด ClearItems และเพิ่มเหตุการณ์ด้วย Remove action และ 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[]";
}

-4

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

โปรดอ่านเอกสารนี้โดยลืมตาและเปิดสมอง ไมโครซอฟท์ทำทุกอย่างถูกต้อง คุณต้องสแกนคอลเลกชันของคุณอีกครั้งเมื่อมีการแจ้งเตือนการรีเซ็ตให้คุณ คุณได้รับการแจ้งเตือนการรีเซ็ตเนื่องจากการเพิ่ม / ลบสำหรับแต่ละรายการ (ถูกลบออกและเพิ่มกลับไปที่คอลเล็กชัน) มีราคาแพงเกินไป

Orion Edwards ถูกต้องสมบูรณ์ (เคารพมนุษย์) โปรดคิดให้กว้างขึ้นเมื่ออ่านเอกสาร


5
ฉันคิดว่าคุณและ Orion เข้าใจถูกต้องแล้วว่า Microsoft ออกแบบมาให้ทำงานอย่างไร :) อย่างไรก็ตามการออกแบบนี้ทำให้ฉันมีปัญหาที่ฉันต้องแก้ไขสถานการณ์ของฉัน สถานการณ์นี้เป็นเรื่องปกติเช่นกัน ... และทำไมฉันจึงโพสต์คำถามนี้
cplotts

ฉันคิดว่าคุณควรดูคำถามของฉัน (และทำเครื่องหมายคำตอบ) ให้มากกว่านี้สักหน่อย ฉันไม่ได้แนะนำให้นำออกสำหรับทุกรายการ
cplotts

และสำหรับบันทึกนี้ฉันเคารพในคำตอบของ Orion ... ฉันคิดว่าเราแค่สนุกกันเล็กน้อย ... อย่างน้อยก็เป็นวิธีที่ฉันได้รับ
cplotts

สิ่งสำคัญอย่างหนึ่ง: คุณไม่จำเป็นต้องแยกขั้นตอนการจัดการเหตุการณ์ออกจากวัตถุที่คุณกำลังลบ การปลดจะกระทำโดยอัตโนมัติ
Dima

1
ดังนั้นโดยสรุปเหตุการณ์จะไม่ถูกแยกออกโดยอัตโนมัติเมื่อลบวัตถุออกจากคอลเลกชัน
cplotts

-4

หากคุณObservableCollectionยังไม่ชัดเจนคุณอาจลองใช้รหัสด้านล่างนี้ มันอาจช่วยคุณได้:

private TestEntities context; // This is your context

context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.