39

I have a class with an indexer property, with a string key:

public class IndexerProvider {
    public object this[string key] {
        get
        {
            return ...
        }
        set
        {
            ...
        }
    }

    ...
}

I bind to an instance of this class in WPF, using indexer notation:

<TextBox Text="{Binding [IndexerKeyThingy]}">

That works fine, but I want to raise a PropertyChanged event when one of the indexer values changes. I tried raising it with a property name of "[keyname]" (i.e. including [] around the name of the key), but that doesn't seem to work. I don't get binding errors in my output window whatsoever.

I can't use CollectionChangedEvent, because the index is not integer based. And technically, the object isn't a collection anyway.

Can I do this, and so, how?

Inferis
  • 4,582
  • 5
  • 37
  • 47

4 Answers4

55

According to this blog entry, you have to use "Item[]". Item being the name of the property generated by the compiler when using an indexer.

If you want to be explicit, you can decorate the indexer property with an IndexerName attribute.

That would make the code look like:

public class IndexerProvider : INotifyPropertyChanged {

    [IndexerName ("Item")]
    public object this [string key] {
        get {
            return ...;
        }
        set {
            ... = value;
            FirePropertyChanged ("Item[]");
        }
    }
}

At least it makes the intent more clear. I don't suggest you change the indexer name though, if your buddy found the string "Item[]" hard coded, it probably means that WPF would not be able to deal with a different indexer name.

Jb Evain
  • 17,319
  • 2
  • 67
  • 67
  • That works great. Strange that I missed that blogpost in my Google searches. – Inferis Mar 18 '09 at 10:35
  • 2
    This solution works great, but it has an annoying limitation: you can't specify that the value changed only for one key... So if you have bindings on many keys, they will all be refreshed – Thomas Levesque Aug 04 '11 at 10:12
  • A few years later, now there's the 'nameof' keyword. I use that for all my `FirePropertyChange` calls, but can you `nameof` the indexer in any way? – Flynn1179 Apr 04 '17 at 12:28
  • 1
    Not sure what is implemented in `FirePropertyChanged` but using `OnPropertyChanged("Item")` is the only working code at least in *Xamarin Forms*. Not sure why it is so troublesome with the appending `[]`. – King King Jul 26 '17 at 00:16
  • 1
    You should use Binding.IndexerName (https://learn.microsoft.com/fr-fr/dotnet/api/system.windows.data.binding.indexername) instead of hard coding "Item[]" – Maxence Jan 21 '21 at 08:21
18

Additionaly, you can use

FirePropertyChanged ("Item[IndexerKeyThingy]");

To notify only controls bound to IndexerKeyThingy on your indexer.

ghord
  • 13,260
  • 6
  • 44
  • 69
8

There are at least a couple of additional caveats when dealing with INotifyPropertyChang(ed/ing) and indexers.

The first is that most of the popular methods of avoiding magic property name strings are ineffective. The string created by the [CallerMemberName] attribute is missing the '[]' at the end, and lambda member expressions have problems expressing the concept at all.

() => this[]  //Is invalid
() => this[i] //Is a method call expression on get_Item(TIndex i)
() => this    //Is a constant expression on the base object

Several other posts have used Binding.IndexerName to avoid the string literal "Item[]", which is reasonable, but raises the second potential issue. An investigation of the dissasembly of related parts of WPF turned up the following segment in PropertyPath.ResolvePathParts.

if (this._arySVI[i].type == SourceValueType.Indexer)
  {
    IndexerParameterInfo[] array = this.ResolveIndexerParams(this._arySVI[i].paramList, obj, throwOnError);
    this._earlyBoundPathParts[i] = array;
    this._arySVI[i].propertyName = "Item[]";
  }

The repeated use of "Item[]" as a constant value suggests that WPF is expecting that to be the name passed in the PropertyChanged event, and, even if it doesn't care what the actual property is called (which I didn't determine to my satisfaction one way or the other), avoiding use of [IndexerName] would maintain consistency.

Andrew Elford
  • 81
  • 1
  • 2
5

Actually, I believe setting the IndexerName attribute to "Item" is redundant. The IndexerName attribute is specifically designed to rename an index, if you want to give it's collection item a different name. So your code could look something like this:

public class IndexerProvider : INotifyPropertyChanged {

    [IndexerName("myIndexItem")]
    public object this [string key] {
        get {
            return ...;
        }
        set {
            ... = value;
            FirePropertyChanged ("myIndexItem[]");
        }
    }
}

Once you set the indexer name to whatever you want, you can then use it in the FirePropertyChanged event.

dimondwoof
  • 51
  • 1
  • 1