views:

426

answers:

5

Hello,

I am a beginner to WPF Databinding and I just found a confusing kind of behaviour. Maybe someone can help me:

My xaml code binds a listbox to a list:

<ListBox ItemsSource="{Binding Path=Axes}"  SelectedItem="{Binding Path = SelectedAxis}" DisplayMemberPath = "Name" Name="listboxAxes"/>

If the actual item that is binded to is an List everythink works as expected.

But when I change to KeyedCollection there occurs an update problem. Calls to NotifyPropertychanged("Axes") does NOT update the ListBox as a whole.

Any ideas about this?

+2  A: 

Try calling listboxAxes.Items.Refresh(). That helps sometimes.

Marcel Benthin
That will work probably. But we don't want to add code. It should work just by default binding mechanics.
Matze
I don't have any experience binding to an KeyedCollection. So i hope somebody else is abled to help you.
Marcel Benthin
+2  A: 

KeyedCollection does not implement neither INotifyCollectionChanged nor INotifyPropertyChanged. To have the binding automatically update itself, use a collection which implements these interfaces (ObservableCollection for example).

Oskar
It's NOT about the elements of the KeyCollection ...It's about a complete new KeyCollection object. It's change is notified properly by an NotifyPropertyChanged(..) event.
Matze
+4  A: 

If whatever Axes is collecting isn't a class with it's own Name property then the DisplayMemberPath="Name" property may be causing you to not see anything.

Using a KeyedCollection as an ItemsSource is perfectly fine though. The ItemsSource property is the ItemsControl.ItemsSource property. And it merely requires that whatever it's bound to implements IEnumerable.

KeyedCollection does implement IEnumerable, as it implements Collection.

Here's a short sample using a KeyedCollection:

<Grid>
 <DockPanel x:Name="QuickListButtonsStackPanel">
  <Button DockPanel.Dock="Top"
    Content="Load Animals"
    Click="AnimalButtonClick" />
  <Button DockPanel.Dock="Top"
    Content="Load Objects"
    Click="ObjectButtonClick" />

  <TextBlock DockPanel.Dock="Bottom"
       Background="Azure"
       Text="{Binding SelectedExample}" />

  <ListBox x:Name="uiListBox"
                 ItemsSource="{Binding Examples}"
     SelectedItem="{Binding SelectedExample}" />

 </DockPanel>
</Grid>

And the code for our Window & KeyedCollection:

public partial class Window1 : Window, INotifyPropertyChanged
{
 public Window1()
 {
  InitializeComponent();
  this.DataContext = this;
 }

 public KeyedCollection<char, string> Examples
 {
  get;
  set;
 }

 private string mySelectedExample;
 public string SelectedExample
 {
  get
  { return this.mySelectedExample; }
  set
  {
   this.mySelectedExample = value;
   this.NotifyPropertyChanged("SelectedExample");
  }
 }

 private void AnimalButtonClick(object sender, RoutedEventArgs e)
 {
  Examples = new AlphabetExampleCollection();
  Examples.Add("Ardvark");
  Examples.Add("Bat");
  Examples.Add("Cat");
  Examples.Add("Dingo");
  Examples.Add("Emu");

  NotifyPropertyChanged("Examples");
 }

 private void ObjectButtonClick(object sender, RoutedEventArgs e)
 {
  Examples = new AlphabetExampleCollection();
  Examples.Add("Apple");
  Examples.Add("Ball");
  Examples.Add("Chair");
  Examples.Add("Desk");
  Examples.Add("Eee PC");

  NotifyPropertyChanged("Examples");
 }



 #region INotifyPropertyChanged Members

 private void NotifyPropertyChanged(String info)
 {
  if (PropertyChanged != null)
  {
   PropertyChanged(this, new PropertyChangedEventArgs(info));
  }
 }

 public event PropertyChangedEventHandler PropertyChanged;

 #endregion
}

public class AlphabetExampleCollection : KeyedCollection<char, string>
{
 public AlphabetExampleCollection() : base() { }

 protected override char GetKeyForItem(string item)
 {
  return Char.ToUpper(item[0]);
 }
}

The other issue that may be occurring is if you are just adding/removing items from the collection, and not re-setting the collection. In the example above, you can see that we are re-instating the keyed collection. If we didn't do this, then just calling NotifyPropertyChanged wouldn't do anything.

Lets add in two more buttons to demonstrate this:

<Button DockPanel.Dock="Top"
  Content="Add Zebra"
  Click="AddZebraClick" />
<Button DockPanel.Dock="Top"
  Content="Add YoYo"
  Click="AddYoYoClick" />

And the Hanlders:

private void AddZebraClick(object sender, RoutedEventArgs e)
{
 Examples.Add("Zebra");
 NotifyPropertyChanged("Examples");
}

private void AddYoYoClick(object sender, RoutedEventArgs e)
{
 Examples.Add("YoYo");
 NotifyPropertyChanged("Examples");
}

Both of these, as is will not work. When you call NotifyPropertyChanged the property has to actually be changed. And in this case, it is not. We've modified it's items, but the Examples collection is still the same collection. To fix this we could cycle the collection as we did in the first part, or if we have access to the UI, we could call:

uiListBox.Items.Refresh();

We could also cycle the DataSource, we could cycle the ListBox's ItemsSource, or we could clear and re-assign the binding but just calling UpdateTarget() won't do it however.

rmoore
A: 

Expose your property as an IList-typed property, and bind the ItemSource to this property:

public IList MyIListKeyedCollection { get { return myKeyedCollection; } }

and do the call NotifyPropertyChanged("MyIListKeyedCollection") when the whenever the myKeyedCollection changes. I think that should work...

Palesz