0

I am trying to implement the CollectionChanged or ListChanged event in my View.

After a lot of research and experimenting I visualized the differencene between PropertyChanged and CollectionChanged on an ObservableCollection (code and output are listed below). The behavior of CollectionChanged is what I need - it is raised when clearing, adding and removing members.

In my little project I have implemented PropertyChanged and this is working so far. But there is one situation I need to implement CollectionChanged and I don't get it working.

Here my first attempt

Edited

I have read the links in the comments below. And did some more research. The subscription of CollectionChanged in the property's setter was just to visualize how the event is working and if it does work with my code.

Is this quote leading me to the correct direction for my particular question?

An ItemsControl listens to CollectionChanged to manage the display of the collection of items it's presenting on the screen. A ContentControl listens to PropertyChanged to manage the display of the specific item that it's presenting on the screen. It's pretty easy to keep the two concepts separate in your mind once you understand this.
Implementing CollectionChanged

And should I create a DataTemplate that is handling the CollectionChanged instead of defining 9x label?

However, reading all these sources is just raising more questions to me. Which, in fact is good - effecting my learning curve. What can I do to resolve my particular problem? I suppose it is all done in xaml. And xaml is not easy to me.

End of Edit

Setting:

  • MVVM
  • The application is about solving a Sudoku (my issue is not about the solving algorithm)
  • INotifyChanged is implemented in Model and ViewModel
  • Converters are implemented
  • Debugging the content of all the properties was fine; correct values are stored

Here is the xaml code that is not working as I want to. In the attached picture it is the orange box.

Screenshot of the UI

The binding to the property Sudoku[0].Activity works fine. Always, if the activity is updated by the application, the UI is updated. So, PropertyChanged is working.
What I want now, is the property Sudoku[0].SetOfCandidates to update when the CollectionChanged event is raising. In the current setting it does just listen to PropertyChanged and that is happening once - at initialization only (which is the correct behavior).

This application is about a Sudoku. If one is entering a value to a cell, the possible candidates in all empty cell need to be updated. This takes place by clearing the property SetOfCandidates. The next step of the algorithm is to figure out the new candidates for each cell by adding the numbers to the property. Thus, the CollectionChanged event is suitable for this.

I have subscribed the CollectionChanged event. And while debugging I see how often the event handler is called.

What needs to be done so the UI is binding to this event?


<Border Style="{StaticResource borderSudokuCandidatesCell}" Background="{Binding Sudoku[0].Activity, Converter={StaticResource sudokuValueColorConverter}}">
  <UniformGrid>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=1}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=2}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=3}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=4}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=5}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=6}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=7}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=8}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
    <Label Content="{Binding Sudoku[0].SetOfCandidates, Converter={StaticResource sudokuCandidateToContentConverter}, ConverterParameter=9}" 
           Style="{StaticResource labelSudokuCandidates}"></Label>
  </UniformGrid>
</Border>

Here is the property.


        private ObservableCollection<int> _SetOfCandidates;
        public ObservableCollection<int> SetOfCandidates
        {
            get => _SetOfCandidates;
            set
            {
                Debug.WriteLine($"Passing Setter of {nameof(SetOfCandidates)}");
                SetProperty(ref _SetOfCandidates, value);
                SetOfCandidates.CollectionChanged += SetOfCandidates_CollectionChanged;
            }
        }

        private void SetOfCandidates_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Debug.WriteLine($"Passing {nameof(SetOfCandidates_CollectionChanged)}");

            // what do I have to put here?
        }

Here is the converter.




    public class SudokuValueColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (Enum.IsDefined(value.GetType(), value) == false)
                return DependencyProperty.UnsetValue;

            var activity = (Activity)value;
            switch (activity)
            {
                case Activity.Clue:
                    return Brushes.Blue;
                case Activity.Singleton:
                    return Brushes.Orange;
                case Activity.Single:
                    return Brushes.Green;
                case Activity.Twin:
                    return Brushes.Brown;
                case Activity.Triple:
                    return Brushes.Yellow;
                case Activity.BruteForce:
                    return Brushes.Red;
                case Activity.Initialization:
                    return null;
                default:
                    return Brushes.Black;
            }
        }
    }

    public class SudokuCandidateToContentConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string contentValue = string.Empty;

            var arrayList = (ObservableCollection<int>)value;

            if (arrayList == null) return "+"; // just for testing
            if (arrayList.Count == 0) return "++"; // just for testing
            //if (arrayList == null) return contentValue;
            //if (arrayList.Count == 0) return contentValue;

            foreach (var item in arrayList)
            {
                if (item == (int)parameter)
                {
                    contentValue = (string)parameter;
                }
            }

            return contentValue;
        }
    }


Here is me code to visualize the behavior of the events.

using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;

namespace CodingSolutions.Model
{
    /// <summary>
    /// This class is hosting the model of the <see cref="ObservableCollectionSolutions"/>.
    /// <para>
    /// This class is providing some solutions how to handle events for this particular collection.
    /// The goal is to learn different approaches and skills in coding.    
    /// </para>
    /// </summary>
    public class ObservableCollectionSolutions : ModelBase
    {
        #region ***** Field *****
        Person personA;
        Person personB;

        private ObservableCollection<int> _ObservableCollectionWithInteger;
        private ObservableCollection<Person> _ObservableCollectionWithPerson;
        private BindingList<Person> _BindingListWithPerson;
        #endregion

        #region ***** Property *****
        public ObservableCollection<int> ObservableCollectionWithInteger
        {
            get => _ObservableCollectionWithInteger;
            set
            {
                Console.WriteLine($"*  Passing Setter of {nameof(ObservableCollectionWithInteger)}");
                _ObservableCollectionWithInteger = value;
            }
        }
        public ObservableCollection<Person> ObservableCollectionWithPerson
        {
            get => _ObservableCollectionWithPerson;
            set
            {
                Console.WriteLine($"*  Passing Setter of {nameof(ObservableCollectionWithPerson)}");
                _ObservableCollectionWithPerson = value;
            }
        }

        public BindingList<Person> BindingListWithPerson
        {
            get => _BindingListWithPerson;
            set
            {
                Console.WriteLine($"*  Passing Setter of {nameof(BindingListWithPerson)}");
                _BindingListWithPerson = value;
            }
        }
        #endregion

        #region ***** Constructor *****
        public ObservableCollectionSolutions()
        {
            Debug.WriteLine($"*** Constructor <{nameof(ObservableCollectionSolutions)}()> ***");

            Console.WriteLine($"Creating an object of class {nameof(Person)}. PropertyChanged is subscribed.");
            personA = new Person();
            personA.PropertyChanged += Person_PropertyChanged;
            Console.WriteLine("Assigning a value to the property.");
            personA.FirstName = "Thorsten";
            DisplayMember(personA, "Property:");
            Console.WriteLine("Changing the property with the same value.");
            personA.FirstName = "Thorsten";
            DisplayMember(personA, "Property:");
            Console.WriteLine("Changing the property with a different value.");
            personA.FirstName = "Thorsten W.";
            DisplayMember(personA, "Property:");

            Console.WriteLine($"Creating a second object of class {nameof(Person)}. PropertyChanged is subscribed.");
            personB = new Person();
            personB.PropertyChanged += Person_PropertyChanged;
            personB.FirstName = "Hans";
            DisplayMember(personB, "Property:");
            Console.WriteLine("********************");
            Console.WriteLine("");

            Console.WriteLine($"Initializing an ObservableCollection<{nameof(Person)}>. CollectionChanged is subscribed.");
            ObservableCollectionWithPerson = new ObservableCollection<Person>();
            ObservableCollectionWithPerson.CollectionChanged += CollectionWithPerson_CollectionChanged;
            Console.WriteLine("Adding an existing object.");
            ObservableCollectionWithPerson.Add(personA);
            Console.WriteLine("Adding an existing object.");
            ObservableCollectionWithPerson.Add(personB);
            DisplayMember(ObservableCollectionWithPerson, "Collection:");

            Console.WriteLine("Manipulating a member of the collection with the same value.");
            ObservableCollectionWithPerson[0].FirstName = "Thorsten W.";
            DisplayMember(ObservableCollectionWithPerson, "Collection:");

            Console.WriteLine("Manipulating a member of the collection with a different value.");
            ObservableCollectionWithPerson[0].FirstName = "Thorsten Wolfgang";
            DisplayMember(ObservableCollectionWithPerson, "Collection:");

            Console.WriteLine("Deleting a member of the collection.");
            ObservableCollectionWithPerson.Remove(personA);
            DisplayMember(ObservableCollectionWithPerson, "Collection:");

            Console.WriteLine("Adding a member to the collection.");
            ObservableCollectionWithPerson.Add(personA);
            DisplayMember(ObservableCollectionWithPerson, "Collection:");

            Console.WriteLine("Clearing the collection.");
            ObservableCollectionWithPerson.Clear();
            DisplayMember(ObservableCollectionWithPerson, "Collection:");
            Console.WriteLine("********************");
            Console.WriteLine("");

            Console.WriteLine($"Initializing an ObservableCollection<int> with four members. CollectionChanged is subscribed.");
            ObservableCollectionWithInteger = new ObservableCollection<int> { 100, 200, 300, 400 };
            ObservableCollectionWithInteger.CollectionChanged += CollectionWithInteger_CollectionChanged;
            DisplayMember(ObservableCollectionWithInteger, "Collection:");

            Console.WriteLine("Adding two members to the collection.");
            ObservableCollectionWithInteger.Add(500);
            ObservableCollectionWithInteger.Add(600);
            DisplayMember(ObservableCollectionWithInteger, "Collection:");

            Console.WriteLine("Manipulating a member of the collection with the same value.");
            ObservableCollectionWithInteger[0] = 100;
            DisplayMember(ObservableCollectionWithInteger, "Collection:");

            Console.WriteLine("Manipulating a member of the collection with a different value.");
            ObservableCollectionWithInteger[0] = 999;
            DisplayMember(ObservableCollectionWithInteger, "Collection:");

            ObservableCollectionWithInteger.Clear();
            ObservableCollectionWithInteger.Add(100);
            ObservableCollectionWithInteger.Insert(0, 200);
            ObservableCollectionWithInteger.RemoveAt(1);
            DisplayMember(ObservableCollectionWithInteger, "Collection after clearing, adding, insterting and removing members:");

            Console.WriteLine("********************");
            Console.WriteLine("");

            Console.WriteLine($"Initializing an BindingList<{nameof(Person)}>. ListChanged is subscribed.");
            BindingListWithPerson = new BindingList<Person>();
            BindingListWithPerson.ListChanged += BindingListWithPerson_ListChanged;
            Console.WriteLine("Adding an existing object.");
            BindingListWithPerson.Add(personA);
            Console.WriteLine("Adding an existing object.");
            BindingListWithPerson.Add(personB);
            DisplayMember(BindingListWithPerson, "Collection:");

            Console.WriteLine("Manipulating a member of the collection with the same value.");
            BindingListWithPerson[0].FirstName = "Thorsten Wolfgang";
            DisplayMember(BindingListWithPerson, "Collection:");

            Console.WriteLine("Manipulating a member of the collection with a different value.");
            BindingListWithPerson[0].FirstName = "Thorsten W.";
            DisplayMember(BindingListWithPerson, "Collection:");

            Console.WriteLine("Deleting a member of the collection.");
            BindingListWithPerson.Remove(personA);
            DisplayMember(BindingListWithPerson, "Collection:");

            Console.WriteLine("Adding a member to the collection.");
            BindingListWithPerson.Add(personA);
            DisplayMember(BindingListWithPerson, "Collection:");

            Console.WriteLine("Clearing the collection.");
            BindingListWithPerson.Clear();
            DisplayMember(BindingListWithPerson, "Collection:");

            Console.WriteLine("********************");
            Console.WriteLine("");
        }
        #endregion

        #region ***** Private Method *****
        private void DisplayMember(ObservableCollection<int> collection, string header)
        {
            Console.WriteLine(header);
            foreach (var item in collection)
            {
                Console.WriteLine($"   Member: {item}");
            }
            Console.WriteLine();
        }

        private void DisplayMember(ObservableCollection<Person> member, string header)
        {
            Console.WriteLine(header);
            foreach (var item in member)
            {
                Console.WriteLine($"   {nameof(item.FirstName)}: {item.FirstName}");
            }
            Console.WriteLine();
        }

        private void DisplayMember(BindingList<Person> member, string header)
        {
            Console.WriteLine(header);
            foreach (var item in member)
            {
                Console.WriteLine($"   {nameof(item.FirstName)}: {item.FirstName}");
            }
            Console.WriteLine();
        }

        private void DisplayMember(Person member, string header)
        {
            Console.WriteLine(header);
            Console.WriteLine($"   {nameof(member.FirstName)}: {member.FirstName}");
            Console.WriteLine();
        }
        #endregion

        #region ***** EventHandler *****
        private void CollectionWithInteger_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Console.WriteLine($"** Passing CollectionChanged of ObservableCollection<int>");

        }

        private void CollectionWithPerson_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Console.WriteLine($"** Passing CollectionChanged of ObservableCollection<{nameof(Person)}>");

        }

        private void BindingListWithPerson_ListChanged(object sender, ListChangedEventArgs e)
        {
            Console.WriteLine($"** Passing ListChanged of BindingList<{nameof(Person)}>");
        }

        private void Person_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine($"** Passing PropertyChanged of {nameof(Person)}");

        }
        #endregion

    }

    public class Person : ModelBase
    {
        #region ***** Field *****
        private string _FirstName;
        #endregion

        #region ***** Property *****
        public string FirstName
        {
            get => _FirstName;
            set
            {
                Console.WriteLine($"*  Passing Setter of {nameof(FirstName)}");
                if (_FirstName != value)
                {
                    _FirstName = value;
                    OnPropertyChanged(nameof(FirstName));
                }
            }
        }
        #endregion
    }
}

And here is the output.

Creating an object of class Person. PropertyChanged is subscribed.
Assigning a value to the property.
*  Passing Setter of FirstName
** Passing PropertyChanged of Person
Property:
   FirstName: Thorsten

Changing the property with the same value.
*  Passing Setter of FirstName
Property:
   FirstName: Thorsten

Changing the property with a different value.
*  Passing Setter of FirstName
** Passing PropertyChanged of Person
Property:
   FirstName: Thorsten W.

Creating a second object of class Person. PropertyChanged is subscribed.
*  Passing Setter of FirstName
** Passing PropertyChanged of Person
Property:
   FirstName: Hans

********************

Initializing an ObservableCollection<Person>. CollectionChanged is subscribed.
*  Passing Setter of ObservableCollectionWithPerson
Adding an existing object.
** Passing CollectionChanged of ObservableCollection<Person>
Adding an existing object.
** Passing CollectionChanged of ObservableCollection<Person>
Collection:
   FirstName: Thorsten W.
   FirstName: Hans

Manipulating a member of the collection with the same value.
*  Passing Setter of FirstName
Collection:
   FirstName: Thorsten W.
   FirstName: Hans

Manipulating a member of the collection with a different value.
*  Passing Setter of FirstName
** Passing PropertyChanged of Person
Collection:
   FirstName: Thorsten Wolfgang
   FirstName: Hans

Deleting a member of the collection.
** Passing CollectionChanged of ObservableCollection<Person>
Collection:
   FirstName: Hans

Adding a member to the collection.
** Passing CollectionChanged of ObservableCollection<Person>
Collection:
   FirstName: Hans
   FirstName: Thorsten Wolfgang

Clearing the collection.
** Passing CollectionChanged of ObservableCollection<Person>
Collection:

********************

Initializing an BindingList<Person>. ListChanged is subscribed.
*  Passing Setter of BindingListWithPerson
Adding an existing object.
** Passing ListChanged of BindingList<Person>
Adding an existing object.
** Passing ListChanged of BindingList<Person>
Collection:
   FirstName: Thorsten Wolfgang
   FirstName: Hans

Manipulating a member of the collection with the same value.
*  Passing Setter of FirstName
Collection:
   FirstName: Thorsten Wolfgang
   FirstName: Hans

Manipulating a member of the collection with a different value.
*  Passing Setter of FirstName
** Passing PropertyChanged of Person
** Passing ListChanged of BindingList<Person>
Collection:
   FirstName: Thorsten W.
   FirstName: Hans

Deleting a member of the collection.
** Passing ListChanged of BindingList<Person>
Collection:
   FirstName: Hans

Adding a member to the collection.
** Passing ListChanged of BindingList<Person>
Collection:
   FirstName: Hans
   FirstName: Thorsten W.

Clearing the collection.
** Passing ListChanged of BindingList<Person>
Collection:

********************

halfer
  • 19,824
  • 17
  • 99
  • 186
karwenzman
  • 23
  • 1
  • 1
  • 8
  • It is unclear why you think you need to subscribe to the CollectionChanged event of the SetOfCandidates collection inside the view model. It's usually the view that subscribes to that event, e.g. when the ItemsSource property of an ItemsControl is set. – Clemens May 04 '23 at 14:41
  • 1
    There should be an ItemsControl with a UniformGrid as ItemsPanel. Its ItemsSource property would probably be bound to the SetOfCandidates property. See [Binding to collections](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/?view=netdesktop-7.0#binding-to-collections) and [Data Templating Overview](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/data-templating-overview?view=netframeworkdesktop-4.8&viewFallbackFrom=netdesktop-7.0). – Clemens May 04 '23 at 14:45
  • Thanks for your comments! I have edidted my thread. – karwenzman May 06 '23 at 13:46

0 Answers0