Click here to Skip to main content
15,887,175 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hi folks.
I have an Observation Collection.
I used event CollectionChanged.
But this event only raises when new item added, or an item deleted.
So how can I detect if an item edited?
(And ofcourse, it would better also to detect which item got edition.)

Any help please?

What I have tried:

VB.NET
Imports System.Collections.ObjectModel

Public Class Person
    Public Property Name As String
End Class

Public listORG As New ObservableCollection(Of Person)()

Sub New()
    InitializeComponent()

    Dim names As New List(Of String) From 
        {"Alice", "Bob", "Charlie", "David", "Eve",
         "Frank", "Grace", "Harry", "Ivy", "Jack"}
    Dim rand As New Random()
    For i As Integer = 0 To 29
        Dim index As Integer = rand.Next(names.Count)
        Dim person As New Person With {.Name = names(index)}
        listORG.Add(person)
    Next i

    ' Bind ObservableCollection to DataGrid in WPF
    DGlistORG.ItemsSource = listORG

    AddHandler listORG.CollectionChanged, AddressOf listORG_CollectionChanged
End Sub

Private Sub listORG_CollectionChanged(sender As Object, e As System.Collections.Specialized.NotifyCollectionChangedEventArgs)
    MsgBox("Updated")
End Sub
Posted
Comments
0x01AA 17-Mar-24 13:20pm    
Not sure if I understand correctly. But for me this is maybe the thing you are looking for: ObservableCollection<T>.PropertyChanged Event (System.Collections.ObjectModel) | Microsoft Learn[^]

You cannot tell if an object has been changed using an ObservableCollection. The way to do this is to use INotifyPropertyChanged and raise the PropertyChanged event whenever you change the value in a property.
 
Share this answer
 
Comments
Sh.H. 17-Mar-24 13:49pm    
@ Pete O'Hanlon
Then should I put PropertyChanged event in every properties of the Class which ObservableCollection binded to ?
Pete O'Hanlon 17-Mar-24 13:56pm    
You should raise it from each property. The key to using this event is knowing that, if you raise the event with the property name as an argument then the binding engine knows the name of the property that changed. If you leave the property name as an empty string, the binding engine thinks all properties have changed.
0x01AA 17-Mar-24 13:59pm    
My 5
Pete O'Hanlon 17-Mar-24 14:00pm    
Thank you.
Graeme_Grant 18-Mar-24 20:37pm    
100% ... +5
I am not sure what you are trying to do however I'll show you how to handle each item in the ObservableCollection.

The Item class needs to implement the INotifyPropertyChanged Interface (System.ComponentModel)[^]. Microsoft provide a sample: How to: Implement Property Change Notification - WPF .NET Framework | Microsoft Learn[^]. They also have a lib to make implementation easy: Introduction to the MVVM Toolkit - Community Toolkits for .NET | Microsoft Learn[^]. Each item class that requires change tracking needs to implement the ObservableObject[^].

To keep my sample not reliant on the Community Toolkit, I've implemented my own ObservableObject that can be replace with the toolkit version:
C#
using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ObservableObject : INotifyPropertyChanged
{
    protected bool Set<TValue>
    (ref TValue field, TValue newValue,
        [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer<TValue>
            .Default.Equals(field, newValue)) return false;

        field = newValue;
        OnPropertyChanged(propertyName);

        return true;
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged(
        [CallerMemberName] string? propertyName = null)
        => PropertyChanged?.Invoke(this,
            new PropertyChangedEventArgs(propertyName));
}

Now we update the Person class from my previous solution[^]:
C#
public class Person : ObservableObject
{
    private string name;
    private string country;

    public string Name
    {
        get => name;
        set => Set(ref name, value);
    }

    public string Country
    {
        get => country;
        set => Set(ref country, value);
    }
}

Now we can update the MainWindow.cs class from my previous solution[^]:
C#
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Timers;
using System.Windows;

namespace WpfCollectionViewSourceGrouping;

public partial class MainWindow : IDisposable
{
    public ObservableCollection<Person> People { get; set; } = new();

    private static List<string> countries = new()
    {
        "Australia",
        "Canada",
        "United Kingdom",
        "United States"
    };

    private Random rand = new();

    private System.Timers.Timer timer;

    public MainWindow()
    {
        InitializeComponent();

        People.CollectionChanged += OnPeopleCollectionChanged;
        
        for (int i = 0; i < 20; i++)
        {
            People.Add(new(){ Name = $"Person {i}", Country = countries[rand.Next(0, countries.Count)]});
        }

        DataContext = this;

        // listen and cleanup on closing
        this.Closing += OnClosing;

        // simulate changes
        timer = new System.Timers.Timer(1000);
        timer.Elapsed += OnTimerTick;
        timer.AutoReset = true;
        timer.Enabled = true;
    }

    private void OnTimerTick(object? sender, ElapsedEventArgs e)
    {
        Person person = People[rand.Next(0, People.Count)];
        string countryName = person.Country;

        // make a change...
        while (countryName == person.Country)
        {
            person.Country = countries[rand.Next(0, countries.Count)];
        }
    }


    private void OnPeopleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                if (e.NewItems is null)
                    return;

                foreach (Person person in e.NewItems)
                {
                    Debug.WriteLine($"Listening to: {person.Name}");
                    person.PropertyChanged += OnPersonPropertyChanged;
                }
                break;
            case NotifyCollectionChangedAction.Remove:
                if (e.OldItems is null)
                    return;

                foreach (Person person in e.OldItems)
                {
                    Debug.WriteLine($"Unhooking: {person.Name}");
                    person.PropertyChanged -= OnPersonPropertyChanged;
                }
                break;

            // not normally required.. will leave you to check.
            //case NotifyCollectionChangedAction.Replace:
            //    break;
            //case NotifyCollectionChangedAction.Move:
            //    break;
            //case NotifyCollectionChangedAction.Reset:
            //    break;
            default:
                throw new ArgumentOutOfRangeException();
        }
    }

    private void OnPersonPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        // do work here...
        Person? person = sender as Person;
        Debug.WriteLine($"{person.Name}'s {e.PropertyName} has changed to {person!.Country}");
    }

    private void OnClosing(object? sender, CancelEventArgs e)
    {
        // cleanup
        this.Dispose();
    }

    public void Dispose()
    {
        // remove possible memory leaks

        foreach (Person person in People)
        {
            Debug.WriteLine($"Unhooking: {person.Name}");
            person.PropertyChanged -= OnPersonPropertyChanged;
        }

        People.CollectionChanged -= OnPeopleCollectionChanged;

        timer.Stop();
        timer.Elapsed -= OnTimerTick;
        timer.Dispose();

        this.Closing -= OnClosing;
    }
}

The above code shows:
* How to implement property changed notifications INotifyProperyChanged event
* How start & stop listening using the CollectionChanged event when items are added and removed from the ObservableCollection.
* sample code to fire property changed events and how to handle them.
* how to avoid memory leaks

Now run the app. What you will see in the Output Window (debug output):
Listening to: Person 0
Listening to: Person 1
Listening to: Person 2
Listening to: Person 3
Listening to: Person 4
Listening to: Person 5
Listening to: Person 6
Listening to: Person 7
Listening to: Person 8
Listening to: Person 9
Listening to: Person 10
Listening to: Person 11
Listening to: Person 12
Listening to: Person 13
Listening to: Person 14
Listening to: Person 15
Listening to: Person 16
Listening to: Person 17
Listening to: Person 18
Listening to: Person 19

while the app is running:
Person 13's Country has changed to United Kingdom
Person 15's Country has changed to United States
Person 8's Country has changed to Australia
Person 6's Country has changed to Australia
Person 11's Country has changed to Canada
Person 2's Country has changed to Australia
Person 17's Country has changed to Canada
Person 2's Country has changed to United States
Person 14's Country has changed to Canada

and when you close the window:
Unhooking: Person 0
Unhooking: Person 1
Unhooking: Person 2
Unhooking: Person 3
Unhooking: Person 4
Unhooking: Person 5
Unhooking: Person 6
Unhooking: Person 7
Unhooking: Person 8
Unhooking: Person 9
Unhooking: Person 10
Unhooking: Person 11
Unhooking: Person 12
Unhooking: Person 13
Unhooking: Person 14
Unhooking: Person 15
Unhooking: Person 16
Unhooking: Person 17
Unhooking: Person 18
Unhooking: Person 19

Again, If you do your research with Google search, all of the information is tere for you, you just need to make the effort and look.

For a DataGrid, if you use an ObservableCollection and implement the INotifyPropertyChanged for the item class, as above, the DataGrid will handle the events for both the ObservableCollection and all items in the collection that implement the INotifyPropertyChanged as demonstrated above.

If we peek at the ObservableCollection we can see the event implemented:
C#
public class ObservableCollection<T> :
  Collection<T>,
  INotifyCollectionChanged,
  INotifyPropertyChanged

Enjoy!
 
Share this answer
 
v3
Comments
Sh.H. 19-Mar-24 11:20am    
@Graeme_Grant
Thank you friend, you spend a lot of time to write this code. It is valuable for me and I am very excited. Thank you very much.
Just a question: As I know, Observable Collection is a list that updates the DataGrid immediately. So why I should use InotifyPropertyChanged? Because if I use INotifyPropertyChanged, then there is no different between List and Observable Collection!
Graeme_Grant 19-Mar-24 11:43am    
Both matter.
Sh.H. 21-Mar-24 2:28am    
@Graeme_Grant
What do you mean by both matter ?
Graeme_Grant 21-Mar-24 2:53am    
Both event types for the ObservableCollection (INotifyPropertyChanged.PropertyChanged & INotifyCollectionChanged.CollectionChanged events) and the INotifyPropertyChanged.PropertyChanged event for each item class. You can learn more about it here: Data binding overview - WPF .NET | Microsoft Learn[^]
Sh.H. 21-Mar-24 16:28pm    
@Graeme_Grant
I think my question was not clear.
I meant, if I should use notify in program, then what is the different between List and Observable Collection ?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900