You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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}";
    }
}

修改说明

  1. 新增值变化监听:为存储属性添加变更回调,当绑定的ExampleText值更新时,自动重新计算并更新目标TextBlock的内容。
  2. 修复Binding解析逻辑:不再使用自定义ServiceProvider处理嵌套Binding,直接传入原始serviceProvider,确保Binding能正确识别数据上下文,建立正常绑定关系。
  3. 记录目标对象与属性:保存目标控件和目标属性的引用,确保存储属性变化时能精准更新目标值。

修改后,在UserControl内部使用<TextBlock Text="{markupExtensions:ExampleText {Binding ExampleText}}"/>即可正常显示*"markup: example"*,且ExampleText属性变化时,显示内容会同步更新。


内容的提问来源于stack exchange,提问作者Sebastian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 23:10:31