8

I have problem updating Listbox containing ObservableCollection when property of collection changes (adding/removing items from list works fine):

listbox has set ItemsSource="{Binding Path=AllPerson}" and data context in code behind is set like this this.DataContext = allPersonClass;.

allPersonClass contains ObservableCollection<Person> allPerson

Class Person contains properties like Name etc.

I've overwritten person's ToString method to return Name property so listBox shows valid data

I've tried to make Person implement INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;
private void onPropertyChanged(object sender, string propertyName) {
    if (this.PropertyChanged != null) {
        PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
    }
}

public string Name {
     get { return name; }
     set {
        name = value;
        onPropertyChanged(this, "allPersonClass");
     }
}

and in every property setter has onPropertyChanged(this, "propertyName"); which is executed but listBox never updates already created item

Any idea what might be wrong?

here is window with listBox xaml

     <Button x:Name="btnDetail" Content="Detail" HorizontalAlignment="Left" Margin="361,249,0,0" VerticalAlignment="Top" Width="75" Click="ButtonDetailClick"/>
     <ListBox x:Name="listPerson" ItemsSource="{Binding Path=AllPerson}" HorizontalAlignment="Left" Height="170" Margin="33,29,0,0" VerticalAlignment="Top" Width="155" IsSynchronizedWithCurrentItem="True"/>
     <Button x:Name="btnLoad" Content="Load" HorizontalAlignment="Left" Margin="58,249,0,0" VerticalAlignment="Top" Width="75" Click="btnLoad_Click"/>
     <Button x:Name="btnSave" Content="Save" HorizontalAlignment="Left" Margin="138,249,0,0" VerticalAlignment="Top" Width="75" Click="ButtonSaveClick"/>

this is part of DetailView window where modifications are made (binded to Person)

<TextBox Text="{Binding Path=Name}" Height="23" HorizontalAlignment="Left" Margin="118,20,0,0" Name="txtName" VerticalAlignment="Top" Width="141" />

here is part AllPersonClass:

public class AllPersonClass {
  private ObservableCollection<Person> allPerson;

public AllPersonClass() {
     allPerson = new ObservableCollection<Person>();
  }

public ObservableCollection<Person> AllPerson {
     get { return allPerson; }
     set { allPerson = value; }
  }

 public void addPerson(Person newPerson) {
     allPerson.Add(newPerson);
  }


public Person getPerson(int personIndex) {
     return allPerson[personIndex];
  }
}

EDIT

here is relevant part of method to save changes in detail view

private void OnBtnSaveClick(object sender, RoutedEventArgs e) {
     person.Name = txtName.Text;
     person.SurName = txtSurName.Text;
}

note that changes are made in ´ObservableCollection allPerson´ only listBox keeps showing old data

Jehof
  • 34,674
  • 10
  • 123
  • 155
Abdul
  • 537
  • 1
  • 5
  • 21

4 Answers4

12

If you want the UI to update when you make changes to Person properties you have to notifiy that that property has changed, not the class

public string Name 
{
     get { return name; }
     set 
     {
        name = value;
        onPropertyChanged(this, "allPersonClass");
     }
}

<TextBox Text="{Binding Path=Name}" .....

Should be

public string Name 
{
     get { return name; }
     set 
     {
        name = value;
        onPropertyChanged(this, "Name");
     }
}

<TextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" .....

And you dont have to override ToString() on the Person model to show correctly in your ListBox, you can set the ListBox DisplayMemberPath to show the property you want in the ListBox

  <ListBox ItemsSource="{Binding Path=AllPerson}" DisplayMemberPath="Name" />

Edit: to answer your comment question:

public string Name 
{
     get { return name; }
     set 
     {
        name = value;
        onPropertyChanged(this, "Name");
        onPropertyChanged(this, "Display");
     }
}

public string Surname
{
     get { return surname; }
     set 
     {
        surname= value;
        onPropertyChanged(this, "Surname");
        onPropertyChanged(this, "Display");
     }
}

public string Display
{
     get { return Name + " " + Surname; }
}
sa_ddam213
  • 42,848
  • 7
  • 101
  • 110
  • The last part of your answer is incorrect about the add and remove methods. Its the same if he uses the variable `allPerson` or the property `AllPerson` to add and remove persons cause its the same reference. The Nofications are raised by the ObservableCollection and not by the property AllPerson – Jehof Mar 14 '13 at 07:43
  • Hmmm.. I remember having troubles with that about 3 years ago and that was the only answer I found to fix it, I have stuck to that method ever since. I just tested and you are indeed correct, now I want to go check the older .NET frameworks to see if I can replicate my old issue :), Thanks for the heads up, I will remove from my answer so it does not cause confusion. – sa_ddam213 Mar 14 '13 at 07:50
  • This helps a little but doesn't give me what I want: with DisplayMemberPath="Name" it works fine but shows only Name in my list (and updates correctly after changes). When i try to show more info like ´listPerson.DisplayMemberPath = "Display";´ where ´Display´ is property of ´Person´ and retuns ´Name + " " + Surname´ it doesn't work again – Abdul Mar 14 '13 at 08:20
  • @Abdul, Make sure if you change any properties used in `Display` that you call `onPropertyChanged(this, "Display");`, you have to tell the UI the property has changed, so in `Name` and `Surname` you will call `onPropertyChanged` twice, once for the property, once for `Display` – sa_ddam213 Mar 14 '13 at 08:24
  • 1
    @Abdul, Added example in answer – sa_ddam213 Mar 14 '13 at 08:29
  • It works with ´listPerson.DisplayMemberPath = "Display";´ but it changes values of listBox immidiatelly (or after textBox looses focus), i'd like to make changes in listBox only after pressing Save Button in detail window and thus makikg manual change in Person class – Abdul Mar 14 '13 at 08:52
  • Thats a very popular question, and its somthing thats not easy to do in MVVM with WPF, I would suggest asking a new SO quesion for that or see if any of the existing questions point you in the correct direction. – sa_ddam213 Mar 14 '13 at 09:00
5

In your DetailView you need to define a TwoWay-Binding to update the Property Name of your person class.

<TextBox Text="{Binding Path=Name, Mode=TwoWay}" Height="23" HorizontalAlignment="Left" Margin="118,20,0,0" Name="txtName" VerticalAlignment="Top" Width="141" />

Also if you want to update the property every time text is written you also need to define the UpdateSourceTrigger with PropertyChanged. Otherwise is the property only update when the TextBox loses focus.

<TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="118,20,0,0" Name="txtName" VerticalAlignment="Top" Width="141" />
Jehof
  • 34,674
  • 10
  • 123
  • 155
  • property in ´Person´ is updated manually after clicking Save Button (see EDIT), ListBox probably doesn't recieve property changed event – Abdul Mar 14 '13 at 08:27
4

your problem is that your collection get never Notifed about your PropertyChanged

this should help you

ObservableCollection<INotifyPropertyChanged> items = new ObservableCollection<INotifyPropertyChanged>();
    items.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(items_CollectionChanged);


    static void items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        foreach (INotifyPropertyChanged item in e.OldItems)
            item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);

        foreach (INotifyPropertyChanged item in e.NewItems)
            item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
    }

    static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

see also this Link

Community
  • 1
  • 1
WiiMaxx
  • 5,322
  • 8
  • 51
  • 89
  • adding if (e.OldItems != null) or if (e.NewItems != null) before each loop results in throwing NotImplementedException - now I'm trying to implement it somehow according to answers in your link – Abdul Mar 14 '13 at 09:35
  • 1
    @Abdul maybe this cloud also help you [LINK] (http://stackoverflow.com/questions/4588359/implementing-collectionchanged) – WiiMaxx Mar 14 '13 at 09:57
  • You are not unsubscribing events. – klm_ Jun 23 '14 at 06:33
  • @klm_ which on the CollectionChanged? Its just to show that he need to bind to this Event how he implement this subscribtion and unsubscribtion will be part of his code – WiiMaxx Jun 23 '14 at 06:46
3

I think you might want to review how you're doing the binding at the item level. Check out this link, it will get you started:

How to determine if a row in ObservableCollection<T> actually changed

Community
  • 1
  • 1
code4life
  • 15,655
  • 7
  • 50
  • 82