1

I've implemented my own ObservableDictionary since A) I wasn't happy with the code I saw other people posting and B) wanted the practice. As far as I can tell, everything works correctly including notifying the listeners, but for some reason, the ListView I'm trying to use to display the contents doesn't update.

My understanding is that it listens for changes using INotifyCollectionChanged. Just as a sanity check, I've attached a listener to the CollectionChanged event on the page initialization, and that does get triggered correctly when I add items, even though the ListView doesn't display anything. I know that I've set up the XAML correctly -- if I re-set the ItemsSource in the codebehind after I've added everything to the dictionary, everything shows up properly; likewise, replacing the ObservableDictionary with the builtin ObservableCollection (without touching the XAML) works perfectly well even without that manual intervention, as long as I change the parameters of the two-element dictionary .Add(...) to fit the single-element collection one.

Comparing my source code to that for the ObservableCollection, the only major difference I can see is that they include the index of the added object, but even adding that to my code doesn't change anything.

To extract the relevant code, my (admittedly somewhat overdesigned) event triggering system:

Tuple<bool, TValue> SendAddEvents(Func<bool> preTest, Func<Tuple<bool, TValue>> action, KeyValuePair<TKey, TValue> item) {
    if (preTest.Invoke()) {
#if (SUPPORT_PROPERTYCHANGING_EVENT)
        PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Keys)));
        PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Values)));
        PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Count)));
#endif
        OnAdding(item);
    }

    var result = action.Invoke();
    if (result.Item1) {
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Add,
            item
        ));
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Keys)));
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Values)));
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));

        OnAdd(item);
    }

    return result;
}

And (as far as I can tell), the official system:

protected override void InsertItem(int index, T item)
{
    CheckReentrancy();
    base.InsertItem(index, item);

    OnPropertyChanged(CountString);
    OnPropertyChanged(IndexerName);
    OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
}

private void OnPropertyChanged(string propertyName)
{
    OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}

protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, e);
    }
}

private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
{
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
}

protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    if (CollectionChanged != null)
    {
        using (BlockReentrancy())
        {
            CollectionChanged(this, e);
        }
    }
}

Like I said, I can't see any substantial difference between the two. Am I missing something obvious, or is there something idiosyncratic about how ListView handles observable collections that anyone know how to work around?

EDIT: For reference, the element in the XAML is bound with the following line; the zero-index row contains a TextBlock that I'm using to indicate when the collection has been fully populated. As I said above, though, simply changing the type of the "Library" property from ObservableDictionary<string, StorageFile> to ObservableCollection<string> (and ObservableCollection<StorageFile> is no different) gets everything working without me touching the XAML.

<ListView ItemsSource="{Binding Library, ElementName=page}" Grid.Row="1" />
Sam May
  • 58
  • 5

1 Answers1

0

Looking at your source code, I noticed your ObservableDictionaryBase<TKey, TValue> does not implement IList<TValue>. You need to have an indexer available for the ListView.

Whatever's bound to the ListView.ItemsSource needs to implement both INotifyCollectionChanged and IList. When it's notified of changes in the dictionary, it will try to retrieve the changes through the indexer (ie page.Library[n]).

This will eventually lead you to an implementation similar to KeyedCollection<TKey, TValue>, where you have an ordered list of keyed elements.

Laith
  • 5,821
  • 1
  • 31
  • 52
  • That's done it, thanks! I was assuming that the `IEnumerable` would have been enough. Now I just need to get that working; it's a shame the builtin `OrderedDictionary` doesn't have a generic version (and none of the Nuget packages support .NETStandard). At least C# exposes `LinkedListNode`. – Sam May Apr 09 '17 at 08:28