Reputation: 29
I have a very simple WPF form :
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="100" Width="100" Name="abcdef">
<Grid>
<ListBox x:Name="listBox1"
ItemsSource="{Binding ElementName=abcdef, Path=Clients}"
DisplayMemberPath="Name" />
</Grid>
</Window>
the code behind the window :
public partial class MainWindow : Window
{
public List<Client> Clients { get; set; }
public MainWindow()
{
Clients = new List<Client>();
Clients.Add(new Client() { ID = 1, Name = "element 1" });
Clients.Add(new Client() { ID = 2, Name = "element 2" });
InitializeComponent();
}
}
and the code of my class :
public class Client : INotifyPropertyChanged
{
public int ID { get; set; }
private string name;
public string Name
{
get { return name; }
set { this.name = value; FirePropertyChanged("Name");}
}
public event PropertyChangedEventHandler PropertyChanged;
private void FirePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is working because InitializeComponent(); is after the initialisation. However, if i put InitializeComponent() before :
InitializeComponent();
Clients = new List<Client>();
Clients.Add(new Client() { ID = 1, Name = "element 1" });
Clients.Add(new Client() { ID = 2, Name = "element 2" });
I should expect INotifyPropertyChanged to update my UI which is not the case.
Could you tell me what I missed ?
Updated : answer has been given using datacontext and ObservableCollection instead of List. However it seems that the datacontext solution has solved the problem itself. I must miss something in my understanding then :
public partial class MainWindow : Window
{
public List<Client> Clients { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Clients = new List<Client>();
Clients.Add(new Client() { ID = 1, Name = "element 1" });
Clients.Add(new Client() { ID = 2, Name = "element 2" });
Clients.Add(new Client() { ID = 3, Name = "element 3" });
Clients.ElementAt(0).Name = "element 1 changed";
Clients.RemoveAt(1);
}
}
In my case, this code updates the control and I am not using ObservableCollection. Why ?
EDIT 1 Aron gave a clear answer : You need to understand that there are two different change notification behaviours here. The Collection changes (e.g. You add, Remove or reorder items). An Item changes (you change the Name on an element). These are handled by different processes. INotifyCollectionChanged is for the former, and INotifyPropertyChanged
And that is right because in debug mode we don't call FirePropertyChanged when callin Clients.RemoveAt(1) However the UI is still updated with the remove... Still don't understand ;-)
EDIT 2 Now It is clear; special thanks to Amnezjak The UI is updated because the code is used before runtime. If you add a button with a method Clients.RemoveAt(1) on click, the UI is not updated. Now if you replace List by ObservableCollection; the button click is reflected in the UI. Also note that you can remove all the implementation of INotifyPropertyChanged in your Client Class because ObservableCollection takes care of that !
Upvotes: 2
Views: 545
Reputation: 2031
EDIT (after question was altered)
ObservableCollection<T>
has advantage if you want to modify collection later during application run (eg. on button click). The List<T>
will not work in this case.
All the misunderstanding here is the difference between biding to DataContext
and to another element (control) on the window. The first is resolved several times, also when window is showing (and then your collection is filled the way you want it to be). The latter, I believe, is resolved only during InitializeComponent
. In that case, when you create your collection (no matter if this is ObservableCollection<T>
or List<T>
after InitializeComponent
, binding gets NULL value and doesn't ask for it later.
PREVIOUS PART
It won't work because you are binding using ElementName
which is indeed resolved during component initialization and is not deferred. To make it work you have to either:
Set DataContext
to this
in your window constructor and remove
ElementName
from your binding. This way it will work no matter
whether you put it before or after InitializeComponent
<ListBox x:Name="listBox1"
ItemsSource="{Binding Path=Clients}"
DisplayMemberPath="Name" />
public MainWindow()
{
Clients = new List<Client>();
Clients.Add(new Client() { ID = 1, Name = "element 1" });
Clients.Add(new Client() { ID = 2, Name = "element 2" });
InitializeComponent();
this.DataContext = this;
}
As others pointed out, change the collection to ObservableCollection<T>
and also make it DepenedencyProperty
so it can use INotifyPropertyChanged
to notify window it has changed (created)
after component initialization.
public ObservableCollection<Client> Clients
{
get { return (ObservableCollection<Client>)GetValue(ClientsProperty); }
set { SetValue(ClientsProperty, value); }
}
public static readonly DependencyProperty ClientsProperty =
DependencyProperty.Register("Clients", typeof(ObservableCollection<Client>), typeof(MainWindow));
Upvotes: 1
Reputation: 29
Thanks for all these great answers.
Amnezjak, as soon as I set datacontext to this, I have no problem anymore even with List instead of ObservableCollection :
InitializeComponent();
Clients = new List<Client>();
Clients.Add(new Client() { ID = 1, Name = "element 1" });
Clients.Add(new Client() { ID = 2, Name = "element 2" });
this.DataContext = this;
Clients.Add(new Client() { ID = 3, Name = "element 3" });
UI is continously updated from the model
Thus, could you explain what is the advantage to switch to ObservableCollection in this context ?
Upvotes: -1
Reputation: 73442
The problem is not with INotifyPropertyChanged
, it is because your collection doesn't support change notifications. You need a collection which supports change notifications. ObservableCollection
does this by implementing INotifyCollectionChanged interface.
Use ObservableCollection<Client>
instead of List<Client>
that should work.
Upvotes: 2
Reputation: 19598
Try using ObservableCollection Class instead of normal List. Your collection need to implement INotifyPropertyChanged instead of your model class. ObservableCollection implements INotifyPropertyChanged.
public class ObservableCollection<T> : Collection<T>,
INotifyCollectionChanged, INotifyPropertyChanged
Upvotes: 1