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

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

如果你写了一个 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)的原创方法,因为真的找不到资料呀!不过在找资料的过程中发现了一些没解决的文档或帖子:


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

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

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

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

  1. Java - 得到项目中properties属性文件中定义的属性值

    public static String getPropertiesValue(String fileName, String key) {   return ResourceBundle.getBu ...

  2. Vue中的computed属性

    阅读Vue官网的过程中,对于计算属于与监听器章节的内容有点理解的不清晰:https://cn.vuejs.org/v2/guide/computed.html. 后来上网查询了资料,结合官网的说明,总 ...

  3. 八、Vue中的computed属性

    看了网上很多资料,对vue的computed讲解自己看的都不是很清晰,今天忙里抽闲,和同事们又闲聊起来,对computed这个属性才有了一个稍微比较清晰的认识,下面的文章有一部分是转自: https: ...

  4. Swift中的类型属性(静态变量)

    http://blog.haohtml.com/archives/15098 Swift中的类型属性(静态变量) Posted on 2014/06/13 类型属性语法 在 C 或 Objective ...

  5. Android中View自己定义XML属性具体解释以及R.attr与R.styleable的差别

    为View加入自己定义XML属性 Android中的各种Widget都提供了非常多XML属性,我们能够利用这些XML属性在layout文件里为Widget的属性赋值. 例如以下所看到的: <Te ...

  6. ES6中object对象属性

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

  7. React中的三大属性

    一.前言: 属性1:state 属性2:props 属性3:ref 与事件处理 二.主要内容: 属性1:state 1,认识: 1) state 是组件对象中最重要的属性,值是一个对象(可以包含多个数 ...

  8. 十三、Vue中的computed属性

    以下抄自https://www.cnblogs.com/gunelark/p/8492468.html 看了网上很多资料,对vue的computed讲解自己看的都不是很清晰,今天忙里抽闲,和同事们又闲 ...

  9. swfit 中的类型属性说明

    swift 中不叫做类属性,叫类型属性,因为在swift中,struct 和enum也是可以有这种属性的,叫类属性明显不准. 有以下注意事项: 对于值类型(指结构体和枚举)可以定义存储型和计算型类型属 ...

随机推荐

  1. sass05 数据类型,数据运算

    /*! 数字类型 */ $n1: 1.2; $n2: 12; $n3: 14px; p{ font-size: $n3; } /*! 字符串类型*/ $s1: container; $s2: 'con ...

  2. django 笔记5 外键 ForeignKey

    class UsserGroup(models.Model): uid = models.AutoField(primary_key=True) caption = models.CharField( ...

  3. Autofac依赖注入框架

    最近使用Autofac框架做项目的依赖注入,感觉挺好用的. 没有深入研究,只是拿来用用,具体可以去官网看看:https://autofac.org/. 这里只是贴一下最近项目的配置: public p ...

  4. css五种定位方式介绍

    1.static定位(普通流定位) -------------- 默认定位 2.float定位(浮动定位) 例:float:left; 有两个取值:left(左浮动)和right(右浮动).浮动元素会 ...

  5. python实现决策树C4.5算法(在ID3基础上改进)

    一.概论 C4.5主要是在ID3的基础上改进,ID3选择(属性)树节点是选择信息增益值最大的属性作为节点.而C4.5引入了新概念"信息增益率",C4.5是选择信息增益率最大的属性作 ...

  6. HDU 1789 Doing Homework again【贪心】

    题意:给出n个作业的截止时间,和该作业没有完成会被扣掉的分数.问最少会被扣掉多少分. 第一次做这一题是好久之前,当时不会(不会处理两个关键字关系@_@)---现在还是不会---看了题解---原来是这样 ...

  7. bzoj1270 BeijingWc2008 雷涛的小猫 DP

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1270 比较水的一道dp f1[i]为高度为i的时候的最大值 f2[i]为当前高度在第i棵树 ...

  8. CodeForcesEducationalRound40-D Fight Against Traffic 最短路

    题目链接:http://codeforces.com/contest/954/problem/D 题意 给出n个顶点,m条边,一个起点编号s,一个终点编号t 现准备在这n个顶点中多加一条边,使得st之 ...

  9. [洛谷P2245]星际导航

    题目大意:有一张n点m边的带权无向图,和一些问题,每次询问两个点之间的路径的最大边权最小是多少. 解题思路:同NOIP2013货车运输,只是数据增大,大变成小,小变成大了而已.所以具体思路见货车运输. ...

  10. 结合Vue 的滚动底部加载

    项目手机端分页跳转不理想,自己做了一个滚动加载的一个Demo 核心Dom结构 <body> <div id="Content"> <div> & ...