您不应该人为地将其复杂化。只需使用通用数据绑定并使用集合索引器:
Dictionary<string, ItemValueData> Map { get; } = new Dictionary<string, ItemValueData>
{
{ "Key to value1", new ItemValueData() },
{ "Key to value2", new ItemValueData() }
};
<TextBlock Text="{Binding Map[Key to value1].Name}" />
<TextBlock Text="{Binding Map[Key to value2].Quality}" />
Binding Path Syntax
正如您坚持使用MarkupExtension,我可以为您提供定制的DictBindingExtension。
它包装/重新发明了已经提供您需要的一切的默认绑定(参见上面的示例)。
目前还不清楚为什么这不适合你,我会在这里停下来。但是由于我找到了我曾经写过的自定义BindingResolver 的现有类,我将为您提供一个构建在该类之上的简单扩展。您反对使用通用绑定标记扩展的所有论点(在您的使用 IValueConverter/MarkupExtension 部分的理由中)都是不合理的。
您将整个 UI 相关数据存储在 Dictionary 中的方法也是非常错误的。我从未遇到过这样的情况,即我或某人“将字典或表格映射到众多(数百个)单个FrameworkElement/Control/自定义元素”。这样的解决方案如何扩展?
无论您的数据输入是什么,都需要另一个抽象级别来摆脱那些键值对结构化数据。
您通常更喜欢使用视图模型正确构建数据,然后让框架根据数据模板为您动态填充控件。
解决方案 #1
由于MarkupExtension 本身不是动态的,因为它在初始化期间只被调用一次,所以BindingResolver 将挂钩到Binding,例如到Dictionary 并允许在更新原始目标之前对该值应用过滤器,例如TextBlock.Text 带有转换/过滤的值。它基本上是一个(可选)值转换器的封装,允许自定义MarkupExtension 接受动态Binding。
或者通过StaticResource设置扩展的DictBindingExtension.Source属性以摆脱绑定功能。
用法
<TextBox Text="{local:DictBind {Binding DictionaryData}, Key=Key to value2, ValuePropertyName=Quality}" />
DictBindingExtension.cs
class DictBindExtension : MarkupExtension
{
public object Source { get; }
public object Key { get; set; }
public string ValuePropertyName { get; set; }
public DictBindExtension(object source)
{
this.Source = source;
this.Key = null;
this.ValuePropertyName = string.Empty;
}
#region Overrides of MarkupExtension
/// <inheritdoc />
public override object ProvideValue(IServiceProvider serviceProvider)
{
IDictionary sourceDictionary = null;
switch (this.Source)
{
case IDictionary dictionary:
sourceDictionary = dictionary;
break;
case BindingBase binding:
var provideValueTargetService =
serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
object targetObject = provideValueTargetService?.TargetObject;
if (targetObject == null)
{
return this;
}
var bindingResolver = new BindingResolver(
targetObject as FrameworkElement,
provideValueTargetService.TargetProperty as DependencyProperty)
{
ResolvedSourceValueFilter = value => GetValueFromDictionary(value as IDictionary)
};
var filteredBinding = bindingResolver.ResolveBinding(binding as Binding) as BindingBase;
return filteredBinding?.ProvideValue(serviceProvider);
case MarkupExtension markup:
sourceDictionary = markup.ProvideValue(serviceProvider) as IDictionary;
break;
}
return GetValueFromDictionary(sourceDictionary);
}
private object GetValueFromDictionary(IDictionary sourceDictionary)
{
if (sourceDictionary == null)
{
throw new ArgumentNullException(nameof(sourceDictionary), "No source specified");
}
object value = sourceDictionary[this.Key];
PropertyInfo propertyInfo = value?.GetType().GetProperty(this.ValuePropertyName);
return propertyInfo == null ? null : propertyInfo.GetValue(value);
}
#endregion
}
BindingResolver.cs
class BindingResolver : FrameworkElement
{
#region ResolvedValue attached property
public static readonly DependencyProperty ResolvedValueProperty = DependencyProperty.RegisterAttached(
"ResolvedValue", typeof(object), typeof(BindingResolver), new PropertyMetadata(default(object), BindingResolver.OnResolvedValueChanged));
public static void SetResolvedValue(DependencyObject attachingElement, object value) => attachingElement.SetValue(BindingResolver.ResolvedValueProperty, value);
public static object GetResolvedValue(DependencyObject attachingElement) => (object)attachingElement.GetValue(BindingResolver.ResolvedValueProperty);
#endregion ResolvedValue attached property
public DependencyProperty TargetProperty { get; set; }
public WeakReference<DependencyObject> Target { get; set; }
public WeakReference<Binding> OriginalBinding { get; set; }
public Func<object, object> ResolvedSourceValueFilter { get; set; }
public Func<object, object> ResolvedTargetValueFilter { get; set; }
private bool IsUpDating { get; set; }
private static ConditionalWeakTable<DependencyObject, BindingResolver> BindingTargetToBindingResolversMap { get; } = new ConditionalWeakTable<DependencyObject, BindingResolver>();
public BindingResolver(DependencyObject target, DependencyProperty targetProperty)
{
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
if (targetProperty == null)
{
throw new ArgumentNullException(nameof(targetProperty));
}
this.Target = new WeakReference<DependencyObject>(target);
this.TargetProperty = targetProperty;
}
private void AddBindingTargetToLookupTable(DependencyObject target) => BindingResolver.BindingTargetToBindingResolversMap.Add(target, this);
public object ResolveBinding(Binding bindingExpression)
{
if (!this.Target.TryGetTarget(out DependencyObject bindingTarget))
{
throw new InvalidOperationException("Unable to resolve sourceBinding. Binding target is 'null', because the reference has already been garbage collected.");
}
AddBindingTargetToLookupTable(bindingTarget);
Binding binding = bindingExpression;
this.OriginalBinding = new WeakReference<Binding>(binding);
// Listen to data source
Binding sourceBinding = CloneBinding(binding);
BindingOperations.SetBinding(
bindingTarget,
BindingResolver.ResolvedValueProperty,
sourceBinding);
// Delegate data source value to original target of the original Binding
Binding targetBinding = CloneBinding(binding, this);
targetBinding.Path = new PropertyPath(BindingResolver.ResolvedValueProperty);
return targetBinding;
}
private Binding CloneBinding(Binding binding)
{
Binding clonedBinding;
if (!string.IsNullOrWhiteSpace(binding.ElementName))
{
clonedBinding = CloneBinding(binding, binding.ElementName);
}
else if (binding.Source != null)
{
clonedBinding = CloneBinding(binding, binding.Source);
}
else if (binding.RelativeSource != null)
{
clonedBinding = CloneBinding(binding, binding.RelativeSource);
}
else
{
clonedBinding = CloneBindingWithoutSource(binding);
}
return clonedBinding;
}
private Binding CloneBinding(Binding binding, object bindingSource)
{
Binding clonedBinding = CloneBindingWithoutSource(binding);
clonedBinding.Source = bindingSource;
return clonedBinding;
}
private Binding CloneBinding(Binding binding, RelativeSource relativeSource)
{
Binding clonedBinding = CloneBindingWithoutSource(binding);
clonedBinding.RelativeSource = relativeSource;
return clonedBinding;
}
private Binding CloneBinding(Binding binding, string elementName)
{
Binding clonedBinding = CloneBindingWithoutSource(binding);
clonedBinding.ElementName = elementName;
return clonedBinding;
}
private MultiBinding CloneBinding(MultiBinding binding)
{
IEnumerable<BindingBase> bindings = binding.Bindings;
MultiBinding clonedBinding = CloneBindingWithoutSource(binding);
bindings.ToList().ForEach(clonedBinding.Bindings.Add);
return clonedBinding;
}
private PriorityBinding CloneBinding(PriorityBinding binding)
{
IEnumerable<BindingBase> bindings = binding.Bindings;
PriorityBinding clonedBinding = CloneBindingWithoutSource(binding);
bindings.ToList().ForEach(clonedBinding.Bindings.Add);
return clonedBinding;
}
private TBinding CloneBindingWithoutSource<TBinding>(TBinding sourceBinding) where TBinding : BindingBase, new()
{
var clonedBinding = new TBinding();
switch (sourceBinding)
{
case Binding binding:
{
var newBinding = clonedBinding as Binding;
newBinding.AsyncState = binding.AsyncState;
newBinding.BindingGroupName = binding.BindingGroupName;
newBinding.BindsDirectlyToSource = binding.BindsDirectlyToSource;
newBinding.Converter = binding.Converter;
newBinding.ConverterCulture = binding.ConverterCulture;
newBinding.ConverterParameter = binding.ConverterParameter;
newBinding.FallbackValue = binding.FallbackValue;
newBinding.IsAsync = binding.IsAsync;
newBinding.Mode = binding.Mode;
newBinding.NotifyOnSourceUpdated = binding.NotifyOnSourceUpdated;
newBinding.NotifyOnTargetUpdated = binding.NotifyOnTargetUpdated;
newBinding.NotifyOnValidationError = binding.NotifyOnValidationError;
newBinding.Path = binding.Path;
newBinding.StringFormat = binding.StringFormat;
newBinding.TargetNullValue = binding.TargetNullValue;
newBinding.UpdateSourceExceptionFilter = binding.UpdateSourceExceptionFilter;
newBinding.UpdateSourceTrigger = binding.UpdateSourceTrigger;
newBinding.ValidatesOnDataErrors = binding.ValidatesOnDataErrors;
newBinding.ValidatesOnExceptions = binding.ValidatesOnExceptions;
newBinding.XPath = binding.XPath;
newBinding.Delay = binding.Delay;
newBinding.ValidatesOnNotifyDataErrors = binding.ValidatesOnNotifyDataErrors;
binding.ValidationRules.ToList().ForEach(newBinding.ValidationRules.Add);
break;
}
case PriorityBinding priorityBinding:
{
var newBinding = clonedBinding as PriorityBinding;
newBinding.BindingGroupName = priorityBinding.BindingGroupName;
newBinding.FallbackValue = priorityBinding.FallbackValue;
newBinding.StringFormat = priorityBinding.StringFormat;
newBinding.TargetNullValue = priorityBinding.TargetNullValue;
newBinding.Delay = priorityBinding.Delay;
break;
}
case MultiBinding multiBinding:
{
var newBinding = clonedBinding as MultiBinding;
newBinding.BindingGroupName = multiBinding.BindingGroupName;
newBinding.Converter = multiBinding.Converter;
newBinding.ConverterCulture = multiBinding.ConverterCulture;
newBinding.ConverterParameter = multiBinding.ConverterParameter;
newBinding.FallbackValue = multiBinding.FallbackValue;
newBinding.Mode = multiBinding.Mode;
newBinding.NotifyOnSourceUpdated = multiBinding.NotifyOnSourceUpdated;
newBinding.NotifyOnTargetUpdated = multiBinding.NotifyOnTargetUpdated;
newBinding.NotifyOnValidationError = multiBinding.NotifyOnValidationError;
newBinding.StringFormat = multiBinding.StringFormat;
newBinding.TargetNullValue = multiBinding.TargetNullValue;
newBinding.UpdateSourceExceptionFilter = multiBinding.UpdateSourceExceptionFilter;
newBinding.UpdateSourceTrigger = multiBinding.UpdateSourceTrigger;
newBinding.ValidatesOnDataErrors = multiBinding.ValidatesOnDataErrors;
newBinding.ValidatesOnExceptions = multiBinding.ValidatesOnExceptions;
newBinding.Delay = multiBinding.Delay;
newBinding.ValidatesOnNotifyDataErrors = multiBinding.ValidatesOnNotifyDataErrors;
multiBinding.ValidationRules.ToList().ForEach(newBinding.ValidationRules.Add);
break;
}
default: return null;
}
return clonedBinding;
}
private static void OnResolvedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is BindingResolver bindingResolver)
{
if (bindingResolver.IsUpDating)
{
return;
}
bindingResolver.IsUpDating = true;
bindingResolver.UpdateSource();
bindingResolver.IsUpDating = false;
}
else
{
if (BindingResolver.BindingTargetToBindingResolversMap.TryGetValue(d, out bindingResolver))
{
if (bindingResolver.IsUpDating)
{
return;
}
bindingResolver.IsUpDating = true;
bindingResolver.UpdateTarget();
bindingResolver.IsUpDating = false;
}
}
}
private static bool TryClearBindings(DependencyObject bindingTarget, BindingResolver bindingResolver)
{
if (bindingTarget == null)
{
return false;
}
Binding binding = BindingOperations.GetBinding(bindingTarget, bindingResolver.TargetProperty);
if (binding != null && binding.Mode == BindingMode.OneTime)
{
BindingOperations.ClearBinding(bindingTarget, BindingResolver.ResolvedValueProperty);
BindingOperations.ClearBinding(bindingTarget, bindingResolver.TargetProperty);
}
return true;
}
private void UpdateTarget()
{
if (!this.Target.TryGetTarget(out DependencyObject target))
{
return;
}
object resolvedValue = BindingResolver.GetResolvedValue(target);
object value = this.ResolvedSourceValueFilter.Invoke(resolvedValue);
BindingResolver.SetResolvedValue(this,value);
}
private void UpdateSource()
{
if (!this.Target.TryGetTarget(out DependencyObject target))
{
return;
}
object resolvedValue = BindingResolver.GetResolvedValue(this);
object value = this.ResolvedTargetValueFilter.Invoke(resolvedValue);
BindingResolver.SetResolvedValue(target, value);
}
}
解决方案 #2
将相关属性添加到您的 IValueConverter 实现中:
用法
<TextBox>
<TextBox.Text>
<Binding Path="DictionaryData">
<Binding.Converter>
<ItemConverterGeneric Key="Key to value2" PropertyName="Quality" />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
ItemConverterGeneric.cs
public class ItemConverterGeneric : IValueConverter
{
public object Key { get; set; }
public object ValuePropertyName { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Dictionary<string, object> dict) || Key == null || ValuePropertyName == null)
{
return Binding.DoNothing;
}
string key = Key as string;
if (dict.TryGetValue(key, out object dataItem))
{
// Use reflection to pick the right property of dataItem
}
}
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture) => throw new NotSupportedException();
}