5

I have a WPF / XAML form data-bound to a property in a dictionary, similar to this:

<TextBox Text="{Binding Path=Seat[2B].Name}">

Seat is exposed as a property IDictionary<String, Reservation> on the airplane object.

class airplane
{
    private IDictionary<String, Reservation> seats;
    public IDictionary<String, Reservation> Seat
    {
        get { return seats; }
        // set is not allowed
    }
}

From within the code of my Window, the value of seat 2B is sometimes changed, and after that, I want to notify the UI that the property has changed.

class MyWindow : Window
{
    private void AddReservation_Click(object sender, EventArgs e)
    {
        airplane.Seat["2B"] = new Reservation();
        // I want to override the assignment operator (=)
        // of the Seat-dictionary, so that the airplane will call OnNotifyPropertyChanged.
    }
}

I've looked to see if the Dictionary is IObservable, so that I could observe changes to it, but it doesn't seem to be.

Is there any good way to "catch" changes to the dictionary in the airplane-class so that I can NotifyPropertyChanged.

abelenky
  • 63,815
  • 23
  • 109
  • 159
  • If you're here trying to render grouped lists in WPF, this post offers an alternate native solution: https://stackoverflow.com/questions/10809278/wpf-grouping-with-a-collection-using-mvvm. It uses a `CollectionsViewSource` with a `PropertyGroupDescription`. – Ben Mar 21 '18 at 18:45

5 Answers5

13

Dr. WPF has created an ObservableDictionary at this link: http://drwpf.com/blog/2007/09/16/can-i-bind-my-itemscontrol-to-a-dictionary/

Update: The comment made by Dr. WPF in the following link says that he has fixed this problem himself so the following change should no longer be required

Also, an addition was made at this link: http://10rem.net/blog/2010/03/08/binding-to-a-dictionary-in-wpf-and-silverlight

The small change was

// old version
public TValue this[TKey key]
{
    get { return (TValue)_keyedEntryCollection[key].Value; }
    set { DoSetEntry(key, value);}
}

// new version
public TValue this[TKey key]
{
    get { return (TValue)_keyedEntryCollection[key].Value; }
    set
    {
        DoSetEntry(key, value);
        OnPropertyChanged(Binding.IndexerName);
    }
}
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • @H.B.: Same thing here. I know from experience that everything that Dr.WPF does works, but I thought I should try it out anyway before posting an answer. So when I was about to save the sample app I got a question if I wanted to replace my local version of it :) Then I remembered... – Fredrik Hedblad Feb 11 '11 at 02:10
  • I'm loading up and testing the ObservableDictionary now. I suspect it will solve my issue, then I'll accept your answer. Thank you! – abelenky Feb 11 '11 at 19:49
  • @abelenky: Yes, hopefully it will, Good luck! – Fredrik Hedblad Feb 11 '11 at 21:24
  • The code as downloaded generated several compile-time warnings. There is more generic-specialization in encapsulated classes than necessary, causing the warnings. Fortunately, this was easy to fix with minor edits, and the code compiled and ran just fine. Observing the dictionary is easy, and did everything I needed. Thank you! – abelenky Feb 18 '11 at 00:36
  • It is also useful to know that Binding.IndexerName is a string constant that is set to "Item[]" and will cause all bindings to the dictionary to be updated (not just those with a specific key). – Darren Feb 25 '14 at 11:42
4

You can just create a class that implement INotifyPropertyChanged interface, wrap your value by this class and use it in Dictionary. I had a similar problem and did next:

class Parameter : INotifyPropertyChanged //wrapper
{
    private string _Value;
    public string Value //real value
    {
        get { return _Value; }
        set { _Value = value; RaisePropertyChanged("Value"); } 
    }

    public Parameter(string value)
    {
        Value = value;
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
public class ViewModel
{
    public Dictionary<string, Parameter> Parameters
}
<ItemsControl ItemsSource="{Binding Path=Parameters, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Margin="3" >
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Label Grid.Column="0" Content="{Binding Path=Key}" Margin="3" />
                <TextBox Grid.Column="1" Text="{Binding Path=Value.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True" Margin="3" />
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

After that, if you change values in the dictionary, you will see the changes in UI.

Paval
  • 976
  • 1
  • 11
  • 20
2

You need to have a clear idea of what you're trying to accomplish before you start implementing change notification. If you want the UI to be updated when an object stored in the dictionary with a given key changes, that's one thing. If you want the UI to be updated when a property of the object stored in the dictionary changes, that's another thing entirely.

In other words, if you want the UI to update when Reservation.Name changes, you need the Reservation object to perform change notification. If you want the UI to update when Seat[2B] is set to a different Reservation, then the dictionary will need to perform change notification.

Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • Excellent point, and I wasn't entirely clear on it. I want the dictionary (or the owner-of-the-dictionary, the airplane) to do the notification when the Reservation changes. (I will make the Reservation do its own notifications when its properties change... that part is easy) – abelenky Feb 11 '11 at 18:25
1

One way to get around this would probably be to encapsulate a dictionary, then you can implement notifying interfaces and control the access to the dictionary, i.e. if someone uses the brackets to set a value you can set the value of the internal dictionary and raise the notification.

H.B.
  • 166,899
  • 29
  • 327
  • 400
1

You could always derive from Dictionary (or IDictionary) to produce an ObservableDictionary:

public class ObservableDictionary<TKey, TVal>:IDictionary<TKey, TVal>, IObservable
{
   private Dictionary<TKey, TVal> _data;

   //Implement IDictionary, using _data for storage and raising NotifyPropertyChanged
}

The biggest problem you'll likely encounter is that you won't be able to directly detect a change to a value; only adding and removing KVPs. To do that, change _data to a List<ObservableKeyValuePair>, implement IObservable there as well, and attach a handler to every new element you create or receive that will respond to the KVP's NotifyPropertyChanged and raise your parent class's NotifyValueChanged.

KeithS
  • 70,210
  • 21
  • 112
  • 164