Binding IObservableMap to an ItemsControl (e.g. ListView)

1

I'm having trouble binding an IObservableMap to a ListView with C++/WinRT. My MainPage.xaml looks like this:

<ListView ItemsSource="{x:Bind TestCollection}">
</ListView>

where TestCollection is a method with the signature winrt::Windows::Foundation::Collections::IObservableMap<hstring, hstring> TestCollection().

However, when running the app it crashes in the XAML setup code when setting the ItemsSource property to TestCollection with HRESULT: 0x80070057 (E_INVALIDARG), which doesn't really tell me much except that it doesn't like the map as a parameter.

I've consulted the docs on this matter and raised an issue with MS docs here because they are contradictory. To summarize:

The ItemsSource property docs say:

The ItemsSource property value must implement one of these interfaces:
IIterable<IInspectable>
IBindableIterable

first of which IObservableMap actually implements according to C++/WinRT headers while the C++/WinRT docs say:

If you want to bind a XAML items control to your collection, then you can. 
But be aware that to correctly set the ItemsControl.ItemsSource property, you need to set it to a value of type IVector of IInspectable (or of an interoperability type such as IBindableObservableVector).

When replacing IObservableMap with IObservableVector everything works as expected.
I've also tried to do the equivalent thing in C# with a (non-observable) Dictionary and it worked just fine, which left me kinda puzzled. How does the CLR accomplish that? Is it converting the Dictionary to an IVector somewhere? It does not work with an IMap in C++, so there must be some sort of conversion going on, right?

Edit: after lots of wasted time and assembly-level debugging into Windows.UI.Xaml.dll, I've found out following things:

  • The ItemsSource method's implementation does a QueryInterface for IIterable<IInspectable>
  • Parameterized interfaces' IIDs are generated based on the inserted type parameter
  • Even if you implement IIterable<IKeyValuePair<K,V>> which is an IInspectable that doesn't mean you implement IIterable<IInspectable>
  • My head hurts

Is there a way to make this work at all?
I've thought about making a custom collection, but that'd mean I need to implement both IIterable<IKeyValuePair<K,V>> and IIterable<IInspectable> which I've tried for a moment, but that confused the compiler due to having two First() methods and it not knowing which one to pick and me not knowing what to do.
Again, how does the CLR solve this?

uwp-xaml
c++-winrt
asked on Stack Overflow Nov 12, 2019 by ArmsOfSorrow • edited Nov 12, 2019 by ArmsOfSorrow

1 Answer

0

Dictionary and it worked just fine, which left me kinda puzzled. How does the CLR accomplish that?

In C# Dictionary<TKey,TValue> implements IEnumerable<KeyValuePair<TKey,TValue>>

For C++/CX there is Platform::Collections::Map this collection also implements all necessary interfaces.

And for C++/WinRT there is winrt::observable_map_base struct. That also implements all necessary interfaces.

Sample implementation from docs:

...
#include <iostream>
using namespace winrt;
using namespace Windows::Foundation::Collections;
...
struct MyObservableMap :
    implements<MyObservableMap, IObservableMap<winrt::hstring, int>, IMap<winrt::hstring, int>, IMapView<winrt::hstring, int>, IIterable<IKeyValuePair<winrt::hstring, int>>>,
    winrt::observable_map_base<MyObservableMap, winrt::hstring, int>
{
    auto& get_container() const noexcept
    {
        return m_values;
    }

    auto& get_container() noexcept
    {
        return m_values;
    }

private:
    std::map<winrt::hstring, int> m_values{
        { L"AliceBlue", 0xfff0f8ff }, { L"AntiqueWhite", 0xfffaebd7 }
    };
};
IObservableMap<winrt::hstring, int> map{ winrt::make<MyObservableMap>() };

for (auto const& el : map)
{
    std::wcout << el.Key().c_str() << L", " << std::hex << el.Value() << std::endl;
}

IIterator<IKeyValuePair<winrt::hstring, int>> it{ map.First() };
while (it.HasCurrent())
{
    std::wcout << it.Current().Key().c_str() << L", " << std::hex << it.Current().Value() << std::endl;
    it.MoveNext();
}
answered on Stack Overflow Nov 14, 2019 by ad1Dima • edited Nov 15, 2019 by IInspectable

User contributions licensed under CC BY-SA 3.0