WPF:为什么我的标记扩展绑定不工作?



使用"Binding":

示例我有一个UserControl,我像这样在我的主窗口中使用:

<userControls:NoMarkupControl/>

我的MainWindow的ViewModel包含这个属性:

private string _exampleText = "example";
public string ExampleText
{
get { return _exampleText; }
set
{
_exampleText = value;
OnPropertyChanged();
}
}

在UserControl中,我将ViewModel绑定到这个属性:

<TextBlock Text="{Binding ExampleText}"/>

as a result "example"当我启动应用程序时显示。一切正常。

不工作的例子,自定义标记扩展:

现在我有一个MarkupExtension:
public class ExampleTextExtension : MarkupExtension
{
private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>();
private readonly object _parameter;
private DependencyProperty _dependencyProperty;
public ExampleTextExtension(object parameter)
{
_parameter = parameter;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
if (target?.TargetObject is DependencyObject dependencyObject &&
target.TargetProperty is DependencyProperty)
{
targetObject = dependencyObject;
}
else
{
return this;
}
_dependencyProperty = SetUnusedStorageProperty(targetObject, _parameter);
return GetLocalizedText((string)targetObject.GetValue(_dependencyProperty));
}
private static string GetLocalizedText(string text)
{
return text == null ? null : $"markup: {text}";
}
private static DependencyProperty SetUnusedStorageProperty(DependencyObject obj, object value)
{
var property = StorageProperties.FirstOrDefault(p => obj.ReadLocalValue(p) == DependencyProperty.UnsetValue);
if (property == null)
{
property = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(ExampleTextExtension), new PropertyMetadata());
StorageProperties.Add(property);
}
if (value is MarkupExtension markupExtension)
{
var resolvedValue = markupExtension.ProvideValue(new ServiceProvider(obj, property));
obj.SetValue(property, resolvedValue);
}
else
{
obj.SetValue(property, value);
}
return property;
}
private class ServiceProvider : IServiceProvider, IProvideValueTarget
{
public object TargetObject { get; }
public object TargetProperty { get; }
public ServiceProvider(object targetObject, object targetProperty)
{
TargetObject = targetObject;
TargetProperty = targetProperty;
}
public object GetService(Type serviceType)
{
return serviceType.IsInstanceOfType(this) ? this : null;
}
}
}

我有一个UserControl我像这样在主窗口中使用它:

<userControls:MarkupControl/>

我的主窗口的ViewModel保持和上面一样。

在UserControl中,我绑定到我的TextBlock Text属性,像这样:

<TextBlock Text="{markupExtensions:ExampleText {Binding ExampleText}}"/>

作为结果,我的UserControl什么也不显示。我希望显示"markup: example">

在这种情况下绑定不工作。

有人知道如何解决这个问题吗?

附加信息:

当像这样使用时(依赖属性MarkupText是在用户控件中创建的):

<userControls:MarkupControl MarkupText={markupExtensions:ExampleText {Binding ExampleText}}/>

<TextBlock Text="{Binding Text, ElementName=MarkupControl}"/>

首先,您需要重构扩展以简化实现。这里不需要静态上下文。去掉类上下文将使跟踪已创建的附加属性变得过时。您可以安全地删除相关集合。在您的情况下,将值存储在实例上下文中会更有效。附加属性也是为每个实例存储值的方便解决方案,特别是在static上下文中。

其次,你有一个时间问题。第一次调用扩展时,Binding没有正确初始化:它没有提供Binding.Source的最终值。此外,您当前的实现不支持属性更改。
要解决这个问题,您必须在从Binding.Source发送值(对于默认的BindingMode.OneWay)时跟踪Binding.Target更新。您可以通过监听Binding.TargetUpdated事件(如我之前的评论所述)或使用附加的属性注册属性更改处理程序(建议)来实现这一点。
为了支持双向绑定,您还必须跟踪目标属性(您的MarkupExtension被分配给的属性)。

修改后的版本如下:

public class ExampleTextExtension : MarkupExtension
{
private static DependencyProperty ResolvedBindingSourceValueProperty = DependencyProperty.RegisterAttached(
"ResolvedBindingSourceValue",
typeof(object),
typeof(ExampleTextExtension),
new PropertyMetadata(default(object), OnResolvedBindingSourceValueChanged));  
// Use attached property to store the target object
// for reference from a static context without dealing with class level members that are shared between instances.
private static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
"TargetProperty",
typeof(DependencyProperty),
typeof(ExampleTextExtension),
new PropertyMetadata(default));
private Binding Binding { get; }
// Accept BindingBase to support MultiBinding etc.
public ExampleTextExtension(Binding binding)
{
this.Binding = binding;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTargetService = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (provideValueTargetService?.TargetObject is not DependencyObject targetObject
|| provideValueTargetService?.TargetProperty is not DependencyProperty targetProperty)
{
return this;
}
targetObject.SetValue(ExampleTextExtension.TargetPropertyProperty, targetProperty);
AttachBinding(targetObject);
return string.Empty;
}
private static string GetLocalizedText(string text) 
=> String.IsNullOrWhiteSpace(text) 
? string.Empty 
: $"markup: {text}";
// By now, only supports OneWay binding
private void AttachBinding(DependencyObject targetObject)
{
switch (this.Binding.Mode)
{
case BindingMode.OneWay:
case BindingMode.Default:
HandleOneWayBinding(targetObject); break;
default: throw new NotSupportedException();
}
}
private void HandleOneWayBinding(DependencyObject targetObject)
{
BindingOperations.SetBinding(targetObject, ExampleTextExtension.ResolvedBindingSourceValueProperty, this.Binding);
}
// Property changed handler to update the target of this extension
// with the localized value
private static void OnResolvedBindingSourceValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string localizedText = GetLocalizedText(e.NewValue as string);
var targetProperty = d.GetValue(ExampleTextExtension.TargetPropertyProperty) as DependencyProperty;
d.SetValue(targetProperty, localizedText);
}
}

评论

有更好的解决方案来引入本地化而不损害一般语法或遗留代码。例如,将此MarkupExtension引入现有代码将破坏此代码,因为必须修改所有相关的数据绑定(c#和XAML)。

最常见的方法是使用附属程序集和本地化资源。而不是在数据绑定期间转换文本值,您应该直接本地化值源(以便Binding传输已经本地化的值)。
换句话说,确保数据源是本地化的。让绑定源通过从本地化的存储库中获取文本来公开文本。

最新更新