เมื่อล้าง 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.