More about the question
First, let me clear the air... I'm somewhat new to C#. I bumped into this issue in an application I was working on. This particular class only had ObservableCollections exposed as Properties so my initial thought was that ObservervableCollection has it's own event, so I don't need the PropertyChanged event. The first attempt worked beautifully. Then I started cleaning up my code and found I didn't really need one of the backing vars, so I moved it all into the method...and it quit updating UI. Adding the INotifyPropertyChanged fixed the issue but I was left with a very big "WHY?"
Here is some test code I put together to see what I could figure out. Be advised, it is littered with bad practice, but I am really only trying to figure out when CollectionChanged can be depended on and when PropertyChagned must be added. Should I ever depend on CollectionChanged over PropertyChanged? If not, should we be using another List type, as we wouldn't really need the overhead of the ObservableCollection. It really seems like a waste to use both.
The bulk of the code
private TestClass test = new TestClass();
public MainWindow()
{
InitializeComponent();
this.DataContext = test;
}
internal class TestClass : INotifyPropertyChanged
{
public ObservableCollection<String> test1 = new ObservableCollection<String>();
public ObservableCollection<String> test2 = new ObservableCollection<String>() { "T2-A" };
public TestClass()
{
}
public ObservableCollection<String> Test1 { get => test1; set { } }
public ObservableCollection<String> Test2 { get => test2; set { this.test2 = value; } }
public ObservableCollection<String> Test3 { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void BT1_Click(object sender, RoutedEventArgs e)
{
//Both of these work fine
test.test1.Add("T1-A");
test.Test1.Add("T1-B");
//First line works...Second line breaks it... but there is a setter.
test.Test2.Add("T2-C");
test.Test2 = new ObservableCollection<String>() { "T2-A", "T2-B" };
test.test2.Add("T2-D");
//First Line throws Null exception... So it's not creating a backing var of it's own.
//test.Test3.Add("T3-A");
var t3 = new ObservableCollection<String>() { "T3-B", "T3-C" };
test.Test3 = t3;
test.Test3.Add("T3-D");
//It updates when I trigger the property changed...but what broke CollectionChanged
test.OnPropertyChanged(nameof(test.Test3));
}
Here is the XAML if anyone is interested
<ListBox ItemsSource="{Binding Test1}" HorizontalAlignment="Left" Height="205" VerticalAlignment="Top" Width="161" Margin="10,5,0,0" />
<ListBox ItemsSource="{Binding Test2}" Height="205" Margin="186,5,187,0" VerticalAlignment="Top" />
<ListBox ItemsSource="{Binding Test3}" Height="205" Margin="366,5,8,0" VerticalAlignment="Top" />
<Button x:Name="bT1" Content="Test It" HorizontalAlignment="Left" Height="33" Margin="10,215,0,0" VerticalAlignment="Top" Width="516" Click="BT1_Click" />
My Findings so far
It seems that if you want to use the ObservableCollection CollectionChanged event, you must create your backing var, and never alter the instance of Collection. In otherwords, use Clear() to wipe and rebuild rather than 'var col = new ObservableCollection()'. Is there something I am missing? I would think this would get rather flaky when you start looking at TwoWay data. How would you prevent someone from breaking your code downstream following the 2nd line of test2?
readonly
to nail it down. – Hans Passant