I have an application that uses a slightly modified ObservableConcurrentDictionary (exposed Count & added constructor for ConcurrentDictionary comparer) for data bound to custom FrameworkElements. The data stored in the dictionary has many properties that need to be displayed or affect rendering.
public class DictionaryData : ObservableConcurrentDictionary<string, ItemValueData>
{ public DictionaryData() : base(StringComparer.InvariantCultureIgnoreCase) { } }
public class ItemValueData
{
// properties
public string Source { get; set; }
public string Name { get; set; }
public int Quality { get; set; }
public double Value { get; set; }
// ... many other properties
// omitted members / constructors / private variable etc.
}
The ObservableConcurrentDictionary data is instantiated as DD a DependencyProperty of the Window/Canvas/Page/Container...
public DictionaryData DD {
get => (DictionaryData)GetValue(DDProperty);
set { SetValue(DDProperty, value); OnPropertyChanged("DDProperty"); }
}
public readonly DependencyProperty DDProperty =
DependencyProperty.Register("DD", typeof(DictionaryData), typeof(MyWindowApp)
, new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
In the working XAML, I'm currently using a different binding converter for each unique property in the ItemValueData class.
<ElementA Value="{av:Binding DD Converter={Converters:ItemConverterName}
, ConverterParameter='Item001Name'}" .../>
<ElementB Value="{av:Binding DD Converter=Converters:ItemConverterQuality}
, ConverterParameter='Item001Name'}" .../>
<ElementC Value="{av:Binding DD Converter=Converters:ItemConverterValue}
, ConverterParameter='Item001Name'}" .../>
<ElementD Value="{av:Binding DD Converter=Converters:ItemConverterSource}
,ConverterParameter='Item001Name'}" .../>
<!-- several hundred FrameWorkElements -->
<ElementA Value="{av:Binding DD Converter={Converters:ItemConverterValue}
, ConverterParameter='Item400Name'}" .../>
Where each converter handles a single property of the ItemValueData class. (.Name maps to ItemConverterName and so on...)
What I want is a converter that will convert any of the properties by passing in the name of the property to convert as well as the key to the dictionary that looks up the data.
[ValueConversion(typeof(DictionaryData), typeof(object))]
public class ItemConverterGeneric : MarkupExtension, IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
try {
if (value != null && parameter != null) {
DictionaryData dict = (DictionaryData)value;
// Would like make the property use a class here
// SomeClass x = parameter as SomeClass;
// string key = x.Key;
// string prop = x.Prop;
string key = parameter as string;
if (dict.ContainsKey(key)) {
// switch(prop) { pick the right property }
return dict[key].Name; // pass as parameter?
}
}
return Binding.DoNothing;
} catch (Exception ex) {
Console.WriteLine(ex.Message);
return Binding.DoNothing;
}
}
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
{ return DependencyProperty.UnsetValue; }
public override object ProvideValue(IServiceProvider serviceProvider)
{ return this; }
}
I've seen answers that used an array to pass several parameters: multiple parameters not truly converted and multiple parameters order matters and a question asking for two parameters . None that I've seen accomplish having a converter able to use multiple named parameters where the parameter is a class (its an object anyway) and have that syntax in the XAML.
<ElementX Value="{av:Binding DD, Converter={Converters:ItemConverterGeneric}
, ConverterParameterKey='ItemXName', ConvertProperty='Name', ConvertSource='DatabaseX'}" .../>
<ElementX Value="{av:Binding DD, Converter={Converters:ItemConverterGeneric}
, ConverterParameterKey='ItemXName', ConvertProperty='Value', ConvertSource='DatabaseX'}" .../>
Justification of using an IValueConverter/MarkupExtension
The dictionary is built dynamically by processing the content of a dynamically loaded XAML that is the source for the container. var tempLoad = XamlReader.Load(Fs); Using a converter gets around issues with nonexistent keys, keys with special characters and having to rely on string parsing the content of Binding b.Path.Path versus Binding b.ConverterParameter since the latter is simply the key.
Surely others have mapped a dictionary or table to numerous (several hundred) single FrameworkElement / Control / Custom Elements and encountered this problem...
Is there a way to make the MarkupExtension extend the XAML syntax and convert the properties?
You shouldn't make it artificially complicated. Simply use common data binding and make use of collection indexers:
Binding Path Syntax
As you insist on
MarkupExtension, I can offer you the customDictBindingExtension.It wraps/reinvents the default binding that already provides everything you need (see example above).
It is still not clear why this does not workout for you and I would have stopped here. But since I found the existing class of the custom
BindingResolverI once wrote, I will provide you with a simple extension that builds on top of this class. All your arguments against using the common Binding markup extension (in your Justification of using an IValueConverter/MarkupExtension section) are not reasonable.Also your approach of storing you entire UI related data in a
Dictionaryis very wrong. I have never encountered such a scenario that I or someone "have mapped a dictionary or table to numerous (several hundred) singleFrameworkElement/Control/ Custom Elements". How does such a solution scale?Whatever your data input is, it needs another level of abstraction to get rid of those key-value pair structured data.
You would typically prefer to structure your data properly using view models and then let the framework populate the controls for you dynamically based on data templates.
Solution #1
Since
MarkupExtensionitself is not dynamic as it is called only once during initialization, theBindingResolverwill hook into aBindinge.g. to aDictionaryand will allow to apply a filter on that value before updating the original target e.g.TextBlock.Textwith the converted/filtered value. It's basically the encapsulation of a (optional) value converter that allows the customMarkupExtensionto accept a dynamicBinding.Alternatively set the
DictBindingExtension.Sourceproperty of the extension viaStaticResourceto get rid of the binding feature.Usage
DictBindingExtension.cs
BindingResolver.cs
Solution #2
Add related properties to your IValueConverter implementation:
Usage
ItemConverterGeneric.cs