5

At the moment I'm writing my own dictionary that implements INotifyPropertyChanged. See below:

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(string propertyName)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

new public Item this[TKey key]
{
    get { return base.Item[key]; }
    set(TValue value)
    {
        if (base.Item[key] != value)
        {
            base.Item[key] = value;
            OnPropertyChanged("XXX"); // what string should I use?
        }
    }
}

My goal is simple: when a certain value in the Dictionary has changed, notify that it has changed. All WPF elements that have a binding with the corresponding key name should then update themselves.

Now my question is: what string should I use as propertyName in order to notify?

I have tried "[" + key.ToString() + "]", "Item[" + key.ToString() + "]" and simply key.ToString(). They all did not seem to work, because the WPF elements did not update.

Using String.Empty ("") does update the WPF elements, but I'm not using this, because this will update all WPF elements that are bound the same dictionary, even though they have different keys.


This is how my binding looks like in XAML:

<TextBlock DataContext="{Binding Dictionary}" Text="{Binding [Index]}" />

Index is of course the name of the key in my dictionary.


In stead of using INotifyPropertyChanged, some have suggested to use INotifyCollectionChanged. I tried this:

Dim index As Integer = MyBase.Keys.ToList().IndexOf(key)
Dim changedItem As Object = MyBase.ToList().ElementAt(index)

RaiseEvent CollectionChanged(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, changedItem, index))

But that does not update the bound WPF elements either.

Rudey
  • 4,717
  • 4
  • 42
  • 84
  • Maybe [this code article](http://blogs.microsoft.co.il/blogs/shimmy/archive/2010/12/26/observabledictionary-lt-tkey-tvalue-gt-c.aspx) helps? – J. Steen Jan 30 '13 at 13:37
  • @J.Steen, unfortunatelly, that implementation will notify **all** properties, even though only one changes. I'm looking for a cleaner way, that only updates the properties that actually have changed. – Rudey Jan 30 '13 at 13:43
  • Dang. Well, worth a try. Sorry for taking your time. =) – J. Steen Jan 30 '13 at 13:44
  • I'm not sure this relates to the problem, but since you declare your indexer property using `new`, it will not be called by any code that accessed the dictionary through the `IDictionary` interface (or a base class reference). – Olly Jan 30 '13 at 14:40
  • @Olly, yes I realise that, and no that does not relate to the problem :) – Rudey Jan 30 '13 at 14:44

2 Answers2

7

Perhaps you need to consider the INotifyCollectionChanged interface rather than INotifyPropertyChanged. If that's the case, have a look at this question and the blog post that it links to.

Also, check this answer to a previous question that was similar to yours. It suggests that you use the value of Binding.IndexerName as the property name for the change notification.

Edit:

If you want to avoid using INotifyCollectionChanged, you might try an alternative approach. Change your dictionary so that the type of its values is a proxy object that implements INotifyPropertyChanged.

public class MyProxy<T> : INotifyPropertyChanged
{
    public T Value
    {
        // Getter and setter with change notification code here.
    }
}

Then insert instances of this type into your dictionary just once. Replace the code that updates a value in the dictionary with code that retrieves the proxy, then updates its Value property.

You would need to change your XAML binding to something like ([Item]).Value.

After reviewing some of your other comments, it does look as though this approach might work for you. It's similar to something I had to do recently. Create a custom KeyedCollection and a corresponding proxy. Add a ItemName property to the proxy; it can be read only, and does not need to support change notifications. Make your KeyedCollection implementation index on the ItemName property. Works a treat! I'll add sample code if you would like it.

Community
  • 1
  • 1
Olly
  • 5,966
  • 31
  • 60
  • Nah, INotifyCollectionChanged does not care about property changes inside a collection. It'll only notify others when an item is added, removed, moved, replaced, or when the entire collection is reset. This interface is useful when binding to the entire collection to an `ItemsSource`, but it's not for my needs. – Rudey Jan 30 '13 at 14:47
  • @Ruud, I've added a link to another post that might be useful. – Olly Jan 30 '13 at 14:48
  • I really appreciate your input, but unfortunatelly using `Binding.IndexerName` is the same as `"Item[]"`, which, as you can probably guess, will update **all** keys. I'm starting to think it is not possible to notify about changes *at a certain index/key*. – Rudey Jan 30 '13 at 14:56
  • @RuudLenders - Hmm, I see your problem. I really do think that this is closer to the `INotifyCollectionChanged` behavior than `INotifyPropertyChanged`. In effect, when you change the value that corresponds to a given key, you are replacing an item in the `Dictionary.Values` collection. That corresponds with the `Replace` member of the `NotifyCollectionChangedAction` enumeration. The indexer property is, conceptually, just a convenient wrapper around these underlying collection structures. – Olly Jan 30 '13 at 15:04
  • @RuudLenders Your indexer implementation suggests that you want to send a notification when an item in the collection has been replaced (not when some property of the item has changed). `INotifyCollectionChanged` supports this case by raising a CollectionChanged event with an [Action](http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedeventargs.action.aspx) value of `NotifyCollectionChangedAction.Replace`. – Clemens Jan 30 '13 at 15:04
  • @RuudLenders - Edited my answer with an alternative that doesn't involve `INotifyCollectionChanged`. However, it would mean a bit more refactoring of your code. – Olly Jan 30 '13 at 15:12
  • I have edited my answer to show you that `INotifyCollectionChanged` does not work as you said. However, I'll try the other suggestion you've put in your answer. – Rudey Jan 30 '13 at 15:17
  • @RuudLenders - I suspect that for `INotifyCollectionChanged` to work, your dictionary class would also need to implement `ICollection`. But I think the 'proxy object' approach is probably the way to go. – Olly Jan 30 '13 at 15:20
  • @Olly, today I've succesfully implemented my `ObservableDictionary`, using your suggestion to use proxy objects. Many thanks for this wonderful suggestion. – Rudey Jan 31 '13 at 14:10
1
if (base.Item[key] != value)
{
     base.Item[key] = value;
     if (key == "notifykey") OnPropertyChanged("[Item]");
}

String.empty will notify all properties not just Item

Per op this as the problem of all the bindings to Dictionary still update.
So I would make a new property

Let just assume you value is string Bind to KeyedValue

public String KeyedValue
{
    get { return base.Item[Index]; }
}

if (key == "Index") OnPropertyChanged("KeyedValue");
paparazzo
  • 44,497
  • 23
  • 105
  • 176
  • Strangely, that does not work either. I'll update my answer so you can see how I set up the binding, maybe the problem comes from there. – Rudey Jan 30 '13 at 14:12
  • Wow, OnPropertyChanged("") updates everything - correct? Then there has to be a correct value. A reach but try without the new. – paparazzo Jan 30 '13 at 14:19
  • `new` is required, because I'm inheriting the property from the `Dictionary` class itself. I found out that using `"Item[]"` as property name does work. However, that way bindings to other keys will also update. – Rudey Jan 30 '13 at 14:25
  • Using properties is exactly what I'm trying to avoid at the moment :P I currently have 50+ properties, that's why I hoped using a `Dictionary(Of String, Object)` would make my code more simple. – Rudey Jan 30 '13 at 14:37
  • I thought only had one KeyedValue and the other were simply not. You should make that more clear in the question. On a property you either fire Notify or not. If you have 50+ bindings then I would just make 50+ properties. – paparazzo Jan 30 '13 at 15:02