Binding to Individual Dictionary Items in WinRT
XAML has first class syntax support for binding to indexed properties, such as Dictionary
. It's even possible to use FallbackValue
to handle missing keys in the collection:
<TextBox Text="{Binding Dictionary[MissingKey], FallbackValue='Error'}" />
Of course, real properties still have their advantages over indexed ones, such as full support for implementing INotifyPropertyChanged
. For Dictionary
properties there is no way to raise PropertyChanged
for a single item in the collection. It can only be done for the Dictionary
property which means notification for all items in the collection at once.
private IDictionary<string, string> _dictionary;
public IDictionary<string, string> Dictionary
{
get { return _dictionary; }
set
{
_dictionary = value;
OnPropertyChanged();
}
}
viewModel.Dictionary = new Dictionary<string, string>
{
{"Key", "New Value"},
// ...
};
This brings some performance overhead, except for scenarios where the complete Dictionary
is being reconstructed at the same time, any way.
Unfortunately, while this approach works great and doesn't cause any issues whatsoever in WPF, in WinRT (i.e. Windows Store applications) raising PropertyChanged
for the Dictionary causes problems when there are bindings to keys that are not present in the new Dictionary
. The KeyNotFoundException
thrown by it, when the binding tries to access the non-existent item, remains unhandled and causes the application to crash.
Having FallbackValue
set doesn't help. Obviously the exception gets caught in application level UnhandledException
handler, i.e. you can handle it there:
void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
e.Handled = true;
}
You'll usually want to be more selective when handling exceptions here and even add some type of logging or error reporting for other caught exceptions. Even if you add such a handler, your application will still break in debug mode, unless you add the following conditional compilation symbol to your project: DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
(which can also be seen in the above screenshot).
Because of all this, you might still want to handle the exception in some other way in Windows Store applications. A standard approach would be a custom IValueConverter
:
public class DictionaryConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, string language)
{
var dictionary = value as Dictionary<string, string>;
if (dictionary == null || !(parameter is string))
{
return null;
}
string result;
dictionary.TryGetValue((string)parameter, out result);
return result;
}
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
return null;
}
}
The obvious advantages of it are simplicity and no changes to the view model. The price for this is a slight performance hit caused by the converter and more complex XAML syntax:
<TextBox Text="{Binding Dictionary, Converter={StaticResource DictionaryConverter},
ConverterParameter=MissingKey}" />
Alternatively the Dictionary indexer could be replaced with a different one, returning null
instead of throwing exceptions when the key is not present. The original Dictionary
could be wrapped in a custom IDictionary
implementation:
public class NonThrowingDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> _dictionary;
public NonThrowingDictionary(Dictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary;
}
public TValue this[TKey key]
{
get
{
TValue value;
_dictionary.TryGetValue(key, out value);
return value;
}
set
{
_dictionary[key] = value;
}
}
// Implement other members by passing calls to _dictionary
}
Now this dictionary could be used in the view model instead of the original one. To reduce the amount of extra code, only the indexer property could be implemented:
public class DictionaryWrapper<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> _dictionary;
public DictionaryWrapper(Dictionary<TKey, TValue> dictionary)
{
_dictionary = dictionary;
}
public TValue this[TKey key]
{
get
{
TValue value;
_dictionary.TryGetValue(key, out value);
return value;
}
set
{
_dictionary[key] = value;
}
}
}
Either way requires more code than the IValueConverter
approach and has greater impact on the view model. On the other hand XAML markup remains unchanged and there are some performance benefits since the converter isn't called every time the binding is refreshed.
Depending on your requirements and values you can choose whichever approach best fits your case.