Reputation: 74
I am having some difficulty reflecting changes in a data bound observable collection.
I have a listbox that is populated with values from the observation collection. When I add or remove items, this is reflected in the bound control. However, when I change the value to be displayed this is not reflected.
For example, Add a week object with a title of 'Week 1' (Works fine) - Now, change the title to 'Hobbies' (Not reflected in control bound to the observable list)
Below is my course management namespace with the parent class courses and the child class weeks
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Windows;
namespace Management.Courses
{
[DataContract(Name = "Course")]
class cCourse
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
MessageBox.Show(propertyName);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public cCourse()
{
Weeks = new ObservableCollection<cWeek>();
}
#region "Core Properties"
[DataMember(Name = "Title")]
public string Title { get; set; }
[DataMember(Name = "Weeks")]
private ObservableCollection<cWeek> _Weeks;
public ObservableCollection<cWeek> Weeks
{
get
{
return _Weeks;
}
set
{
if (value != this._Weeks)
{
_Weeks = value;
NotifyPropertyChanged();
}
}
}
}
[DataContract(Name = "Week")]
class cWeek
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region "Core Properties"
[DataMember(Name = "Title")]
private string _Title { get; set; }
public string Title
{
get
{
return _Title;
}
set
{
if (value != this.Title)
{
_Title = value;
NotifyPropertyChanged();
}
}
}
#endregion
}
}
And in my window, I have as follows:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Core;
namespace Management.Courses
{
public partial class MgnCourses_Add : Window
{
private cCourse oCourse;
public MgnCourses_Add()
{
InitializeComponent();
oCourse = new cCourse();
//Set Data Context
DataContext = oCourse;
cWeek oWeek = new cWeek();
//Get/Set week title
oWeek.Title = "Week " + e.NewValue;
//Update week listing
oCourse.Weeks.Add(oWeek);
}
private void ctrlTopics_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
oCourse.Weeks[ctrlTopics.SelectedIndex].Title = "New String";
}
}
}
The control binding for the listbox is:
<ListBox x:Name="ctrlTopics" Margin="100,131,439,49" ItemsSource="{Binding Weeks}" DisplayMemberPath="Title" MouseDoubleClick="ctrlTopics_MouseDoubleClick">
I can see through debugging that the observable list values are changing correctly - It's just not reflected in the listbox.
Any help / pointer would be greatly appreciated.
Upvotes: 0
Views: 887
Reputation: 1082
When wanting to use binding with INotifyPropertyChanged and seeing it reflect changes to the UI, you'll need to use the Item Listbox.Itemtemplate
. This is because the DisplayMemberPath
is used as a pointer on which property
from within the object should be used for display. Once it is set it does not keep track of changes that are made from within the object.
So to give an example of how this should work.
<ListBox x:Name="ctrlTopics" Margin="100,131,439,49" ItemsSource="{Binding Weeks}"
MouseDoubleClick="ctrlTopics_MouseDoubleClick">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
To explain the above code, the Listbox.ItemTemplate
cannot be used in combination with the DisplayMemberPath
. So instead of using the DisplayMemberPath
we create a binding within the template.
< Part 2 >
The UI did not receive any notifications because the PropertyChanged event does not get invoked. The INotifyPropertyChanged Interface should also be implemented on the cWeek class. Thus giving it the following definition.
class cWeek : INotifyPropertyChanged
Lastly, the following
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
could be reduced to and the event should be invoked to let the UI be notified.
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Upvotes: 2
Reputation: 556
@M.B has already provided the best solution for your situation. However, I would like to make things a little more clear and add a walk around to the question. The reason that your UI does not reflects the change you made to data is that your code only notify UI about the entire object change
Week = other_week;
but not the object 's properties change
Week.Title = new_week_title;
Now, the walk around I mentioned before: just notify UI about the property change explicitly:
Week.Title = new_week_title;
PropertyChange(this, nameof(Week));
You can also assign new value to current object to invoke the notify event:
Week = new Week(new_week_title);
However, this action will affect the observable collection since you have assign an object that does not belongs to the existing collection and it can introduce a lot of problems. With that being said, the best solution you should take is @M.B 's solution.
Upvotes: 1