WPF自定义MarkupExtension绑定失效,求修复方案
自定义MarkupExtension嵌套Binding失效问题修复
使用Binding的正常示例
我有一个UserControl,在MainWindow中这样引用:
<userControls:NoMarkupControl/>
MainWindow的ViewModel包含如下属性:
private string _exampleText = "example"; public string ExampleText { get { return _exampleText; } set { _exampleText = value; OnPropertyChanged(); } }
在该UserControl内部,我将TextBlock绑定到这个属性:
<TextBlock Text="{Binding ExampleText}"/>
启动应用后会显示“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,在MainWindow中这样引用:
<userControls:MarkupControl/>
MainWindow的ViewModel和上述示例一致。在该UserControl内部,我尝试用自定义扩展嵌套Binding来绑定TextBlock的Text属性:
<TextBlock Text="{markupExtensions:ExampleText {Binding ExampleText}}"/>
结果UserControl没有显示任何内容,预期应该显示*"markup: example"*,绑定似乎失效了。
补充信息
以下方式可以正常工作(在UserControl中创建了依赖属性MarkupText):
<userControls:MarkupControl MarkupText={markupExtensions:ExampleText {Binding ExampleText}}/>
<TextBlock Text="{Binding Text, ElementName=MarkupControl}"/>
修复方案
问题出在自定义MarkupExtension的ProvideValue方法中:当前仅在初始化时获取一次值,未监听绑定属性的变化;且自定义ServiceProvider未提供足够服务,导致Binding无法正常解析上下文并建立绑定关系。
修改后的完整代码
public class ExampleTextExtension : MarkupExtension { private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>(); private DependencyObject _targetObject; private DependencyProperty _targetProperty; private DependencyProperty _storageProperty; private readonly object _parameter; public ExampleTextExtension(object parameter) { _parameter = parameter; } public override object ProvideValue(IServiceProvider serviceProvider) { var provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; if (provideValueTarget == null || !(provideValueTarget.TargetObject is DependencyObject targetObject) || !(provideValueTarget.TargetProperty is DependencyProperty targetProperty)) { return this; } _targetObject = targetObject; _targetProperty = targetProperty; // 找到或创建存储属性 _storageProperty = GetOrCreateStorageProperty(); // 监听存储属性值变化 var descriptor = DependencyPropertyDescriptor.FromProperty(_storageProperty, typeof(ExampleTextExtension)); descriptor.AddValueChanged(targetObject, OnStoragePropertyChanged); // 处理嵌套的Binding,使用原始ServiceProvider保证解析正常 if (_parameter is MarkupExtension markupExtension) { var resolvedValue = markupExtension.ProvideValue(serviceProvider); targetObject.SetValue(_storageProperty, resolvedValue); } else { targetObject.SetValue(_storageProperty, _parameter); } // 返回初始处理值 return GetLocalizedText((string)targetObject.GetValue(_storageProperty)); } private void OnStoragePropertyChanged(object sender, EventArgs e) { if (_targetObject == null || _targetProperty == null) return; // 存储属性值变化时,更新目标属性 var value = (string)_targetObject.GetValue(_storageProperty); _targetObject.SetValue(_targetProperty, GetLocalizedText(value)); } private DependencyProperty GetOrCreateStorageProperty() { var property = StorageProperties.FirstOrDefault(p => _targetObject.ReadLocalValue(p) == DependencyProperty.UnsetValue); if (property == null) { property = DependencyProperty.RegisterAttached( $"Storage{StorageProperties.Count}", typeof(object), typeof(ExampleTextExtension), new PropertyMetadata(null)); StorageProperties.Add(property); } return property; } private static string GetLocalizedText(string text) { return text == null ? null : $"markup: {text}"; } }
修改说明
- 新增值变化监听:为存储属性添加变更回调,当绑定的
ExampleText值更新时,自动重新计算并更新目标TextBlock的内容。 - 修复Binding解析逻辑:不再使用自定义
ServiceProvider处理嵌套Binding,直接传入原始serviceProvider,确保Binding能正确识别数据上下文,建立正常绑定关系。 - 记录目标对象与属性:保存目标控件和目标属性的引用,确保存储属性变化时能精准更新目标值。
修改后,在UserControl内部使用<TextBlock Text="{markupExtensions:ExampleText {Binding ExampleText}}"/>即可正常显示*"markup: example"*,且ExampleText属性变化时,显示内容会同步更新。
内容的提问来源于stack exchange,提问作者Sebastian
相关产品推荐
相关产品推荐

