如果你写了一个 MarkupExtension 在 XAML 当中使用,你会发现你在 MarkupExtension 中定时的属性是无法使用 XAML 绑定的,因为 MarkupExtension 不是一个 DependencyObject

本文将给出解决方案,让你能够在任意的类型中写出支持 XAML 绑定的属性;而不一定要依赖对象(DependencyObject)和依赖属性(DependencyProperty)。


本文内容

  • 问题
  • 解决
  • 方案
    • 参考资料

问题

下面是一个很简单的 MarkupExtension,用户设置了什么值,就返回什么值。拿这么简单的类型只是为了避免额外引入复杂的理解难度。

public class WalterlvExtension : MarkupExtension
{private object _value;public object Value{get => _value;set => _value = value;}public override object ProvideValue(IServiceProvider serviceProvider){return Value;}
}

可以在 XAML 中直接赋值:

<Button Content="{local:Walterlv Value=walterlv.com" />

但不能绑定:

<TextBox x:Name="SourceTextBox" Text="walterlv.com" />
<Button Content="{local:Walterlv Value={Binding Text, Source={x:Reference SourceTextBox}}}" />

因为运行时会报错,提示绑定必须被设置到依赖对象的依赖属性中。在设计器中也可以看到提示不能绑定。

解决

实际上这个问题是能够解决的(不过也花了我一些时间思考解决方案)。

既然绑定需要一个依赖属性,那么我们就定义一个依赖属性。非依赖对象中不能定义依赖属性,于是我们定义附加属性。

// 注意:这一段代码实际上是无效的。public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached("Value", typeof(object), typeof(WalterlvExtension), new PropertyMetadata(default(object)));public object Value
{get => ???.GetValue(ValueProperty);set => ???.SetValue(ValueProperty, value);
}

这里问题来了,获取和设置附加属性是需要一个依赖对象的,那么我们哪里去找依赖对象呢?直接定义一个新的就好了。

于是我们定义一个新的依赖对象:

// 注意:这一段代码实际上是无效的。public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached("Value", typeof(object), typeof(WalterlvExtension), new PropertyMetadata(default(object)));public object Value
{get => _dependencyObject.GetValue(ValueProperty);set => _dependencyObject.SetValue(ValueProperty, value);
}private readonly DependencyObject _dependencyObject = new DependencyObject();

现在虽然可以编译通过,但是我们会遇到两个问题:

  1. ValueProperty 的变更通知的回调函数中,我们只能找到 _dependencyObject 的实例,而无法找到外面的类型 WalterlvExtension 的实例;这几乎使得 Value 的变更通知完全失效。
  2. Valueset 方法中得到的 value 值是一个 Binding 对象,而不是正常依赖属性中得到的绑定的结果;这意味着我们无法直接使用 Value 的值。

为了解决这两个问题,我必须自己写一个代理的依赖对象,用于帮助做属性的变更通知,以及处理绑定产生的 Binding 对象。在正常的依赖对象和依赖属性中,这些本来都不需要我们自己来处理。

方案

于是我写了一个代理的依赖对象,我把它命名为 ClrBindingExchanger,意思是将 CLR 属性和依赖属性的绑定进行交换。

代码如下:

public class ClrBindingExchanger : DependencyObject
{private readonly object _owner;private readonly DependencyProperty _attachedProperty;private readonly Action<object, object> _valueChangeCallback;public ClrBindingExchanger(object owner, DependencyProperty attachedProperty,Action<object, object> valueChangeCallback = null){_owner = owner;_attachedProperty = attachedProperty;_valueChangeCallback = valueChangeCallback;}public object GetValue(){return GetValue(_attachedProperty);}public void SetValue(object value){if (value is Binding binding){BindingOperations.SetBinding(this, _attachedProperty, binding);}else{SetValue(_attachedProperty, value);}}public static void ValueChangeCallback(DependencyObject d, DependencyPropertyChangedEventArgs e){((ClrBindingExchanger) d)._valueChangeCallback?.Invoke(e.OldValue, e.NewValue);}
}

这段代码的意思是这样的:

  1. 构造函数中的 owner 参数完全没有用,我只是拿来备用,你可以删掉。
  2. 构造函数中的 attachedProperty 参数是需要定义的附加属性。
    • 因为前面我们说过,有一个附加属性才可以编译通过,所以附加属性是一定要定义的
    • 既然一定要定义附加属性,那么就可以用起来,接下来会用
  3. 构造函数中的 valueChangeCallback 参数是为了指定变更通知的,因为前面我们说变更通知不好做,于是就这样代理做变更通知。
  4. GetValueSetValue 这两个方法是用来代替 DependencyObject 自带的 GetValueSetValue 的,目的是执行我们希望特别执行的方法。
  5. SetValue 中我们需要自己考虑绑定对象,如果发现是绑定,那么就真的进行一次绑定。
  6. ValueChangeCallback 是给附加属性用的,因为用我的这种方法定义附加属性时,只能写出相同的代码,所以干脆就提取出来。

而用法是这样的:

public class WalterlvExtension : MarkupExtension
{public WalterlvExtension(){_valueExchanger = new ClrBindingExchanger(this, ValueProperty, OnValueChanged);}private readonly ClrBindingExchanger _valueExchanger;public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached("Value", typeof(object), typeof(WalterlvExtension),new PropertyMetadata(null, ClrBindingExchanger.ValueChangeCallback));public object Value{get => _valueExchanger.GetValue();set => _valueExchanger.SetValue(value);}private void OnValueChanged(object oldValue, object newValue){// 在这里可以处理 Value 属性值改变的变更通知。}public override object ProvideValue(IServiceProvider serviceProvider){return Value;}
}

对于一个属性来说,代码确实多了些,这实在是让人难受。可是,这可以达成目的呀!

解释一下:

  1. 定义一个 _valueExchanger,就是在使用我们刚刚写的那个新类。
  2. 在构造函数中对 _valueExchanger 进行初始化,因为要传入 this 和一个实例方法 OnValueChanged,所以只能在构造函数中初始化。
  3. 定义一个附加属性(前面我们说了,一定要有依赖属性才可以编译通过哦)。
    • 注意属性的变更通知方法,需要固定写成 ClrBindingExchanger.ValueChangeCallback
  4. 定义普通的 CLR 属性 Value
    • GetValue 方法要换成我们自定义的 GetValue
    • SetValue 方法也要换成我们自定义的 SetValue 哦,这样绑定才可以生效
  5. OnValueChanged 就是我们实际的变更通知,这里得到的 oldValuenewValue 就是你期望的值,而不是我面前面奇怪的绑定实例。

于是,绑定就这么在一个普通的类型和一个普通的 CLR 属性中生效了,而且还获得了变更通知。

参考资料

本文没有任何参考资料,所有方法都是我(walterlv)的原创方法,因为真的找不到资料呀!不过在找资料的过程中发现了一些没解决的文档或帖子:

  • How to use CLR property as binding target?
  • CLR Object Binding In WPF
  • wpf - MarkupExtension with binding parameters - Stack Overflow
  • c# - Binding to dependency and regular properties in WPF - Stack Overflow
  • c# - XAML bind to DependencyProperty instance held in a CLR property - Stack Overflow
  • Tore Senneseth’s blog » Custom Markup Extension with bindable properties
  • Markup Extensions for XAML Overview - Microsoft Docs
  • Service Contexts Available to Type Converters and Markup Extensions - Microsoft Docs

我的博客会首发于 https://walterlv.com/,而 CSDN 和博客园仅从其中摘选发布,而且一旦发布了就不再更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

WPF 让普通 CLR 属性支持 XAML 绑定(非依赖属性),这样 MarkupExtension 中定义的属性也能使用绑定了相关推荐

  1. python私有属性怎么定义_Python中定义私有属性的方法是()。

    [判断题]请假条带有请求的性质,所以一般来说篇幅要尽量长一些,以示郑重;更多要用煽情性语句,以打动对方.( ) [单选题]关于类和对象的关系,下列描述正确的是(). [选择]Трудоспособны ...

  2. java属性定义_在java中定义抽象属性

    抽象关键字通常应用于类和方法,以便将某些行为的实现委托给子类. Java不支持抽象属性,如果您试图将类属性标记为抽象,则会得到编译错误. 在本教程中,将介绍两种定义抽象属性的方法,这些属性由子类设置, ...

  3. python对象属性是与数据有关的项目_python之对象产生,属性(定义的值)的查找原则,对象绑定关系...

    例1:                                 通过外部定义一个函数也可以传值进去 class OldboyStudent: school='oldboy' def choos ...

  4. 如何在自定义指令中获取评估属性

    本文翻译自:How to get evaluated attributes inside a custom directive I'm trying to get an evaluated attri ...

  5. css中会计算的属性,css3中样式计算属性calc()的使用和总结

    calc的介绍 在css3样式中有一个类似与函数的计算属性calc(),它主要用于指定元素的长度,无论是border.margin.pading.font-size和width等属性都可以使用calc ...

  6. 定义一个属性_CocosCreator脚本属性个性化定制——下拉列表属性、滑动条属性

    一.声明属性 要声明属性,需要在 cc.Class 定义的 properties 字段中,填写属性名字和属性参数.在此处声明的属性在Cocos Creator编辑器属性检查器面板中可以读取和编辑.如下 ...

  7. Vue中的computed属性

    1.前言 本篇是Vue中最常用到的API之一computed属性,转载信息如下: 作者:gunelark 链接:https://www.cnblogs.com/gunelark/p/8492468.h ...

  8. ES6中object对象属性

    //es5中定义对象属性要么字面量.要么点.要么[],变量与空格在这些方法中没有得到好的支持 /在es6中可以这么定义: let w='www'; let obj1={w};//obj1={w:'ww ...

  9. Vivado使用技巧(24):HDL/XDC中设置综合属性

    Vivado综合工具支持直接在RTL文件或XDC文件中设置综合属性.如果Vivado识别出设置的属性,会创建与之相关的逻辑电路:如果不能识别设置的属性,会将该属性和值存放在生成的网表中.因为某些属性, ...

最新文章

  1. 系统遇到并发瓶颈时的优化方向
  2. web 服务器-Nginx
  3. 笔记-项目质量管理-七种基本质量工具
  4. react 组件怎么公用_React、Redux与复杂业务组件的复用
  5. 算法设计与分析【第七周】贪心算法 最优装载问题
  6. linux系统下top命令的详细用法、参数详解、以及模式配置
  7. 计算机毕业设计中Python Django 框架目录结构简介
  8. Things3 for mac(Mac任务管理神器)
  9. Linux 查看文件格式和更改文件格式
  10. 超级简单基于spring boot高速公路收费系统的设计与实现.rar(含源码及数据库文件)
  11. GAMES104实录 | 游戏引擎导论(上)
  12. Android带数字拼音与带音标拼音互转工具类
  13. windows 程序设计 第一章
  14. win10安装linux子系统详细教程(非虚拟机方式)
  15. 89c52单片机控制两个步进电机正反转加减速(程序+仿真)
  16. 036-2018-1028 线程 效率对比 数据共享 同步锁死锁递归锁 守护线程 信号量
  17. 计算机系统维护是干嘛,计算机系统维护是什么
  18. 移动联通电信wap和net接入点判断
  19. 工信部网站备案系统升级完成 新增备案用户激增
  20. 关于5G的NSA和SA,看完秒懂

热门文章

  1. Vue大屏实战四:公共组件边框的实现
  2. 【OpenGL】透视投影矩阵推导
  3. “高级感”的设计,从这20款精选Google网页字体开始【免费】
  4. Office Word中横线的增加和删除方法
  5. python怎么识别图片上的字_python如何识别图片中的文字 | 蒲公英网
  6. CCF 201703-1分蛋糕
  7. 激光散斑血流仪的数据处理
  8. C++自创打怪小游戏
  9. latex 长公式换行括号-换行后括号大小不一致
  10. python自动化(四)app自动化:7.专项测试讲解