专栏首页walterlv - 吕毅的博客如何编写 WPF 的标记扩展 MarkupExtension,即便在 ControlTemplate/DataTemplate 中也能生效

如何编写 WPF 的标记扩展 MarkupExtension,即便在 ControlTemplate/DataTemplate 中也能生效

如何编写 WPF 的标记扩展 MarkupExtension,即便在 ControlTemplate/DataTemplate 中也能生效

发布于 2018-05-29 12:56 更新于 2018-05-30 01:34

WPF 的标记扩展为 WPF 带来了强大的扩展性。利用自定义的标记扩展,我们能够为 XAML 中的属性提供各种各样种类的值,而不仅限于自带的那一些。

不过有小伙伴发现在 ControlTemplateDataTemplate 中编写标记扩展有时并不能正常工作,而本文将提供解决方法。


本文并不会详细讲解如何编写 WPF 的标记扩展,如果你想了解相关的知识,建议阅读官网:Markup Extensions and WPF XAML - Microsoft Docs

编写简单的标记扩展

一个简单的标记扩展会是像这样:

using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;

namespace Walterlv.Demo
{
    public class RevealBorderBrushExtension : MarkupExtension
    {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return Brushes.White;
        }
    }
}

这样的标记扩展如此简单,以至于你可以在任意的 XAML 中用。只要赋值的那个属性接受 Brush 类型,就不会出错。

然而……有小伙伴写了更加复杂的标记扩展,在标记扩展中还通过 serviceProvider 拿到了目标控件的一些属性。本来一直好好工作的,结果有一天这个标记扩展被用到了 ControlTemplate 上,然后就挂了……挂了……

编写能在 ControlTemplate 中使用的标记扩展

ControlTemplate 中,XAML 标记扩展也是立即执行的,这就意味着当标记扩展中的 ProvideValue 执行时,还没有根据模板创建控件呢,那创建的是什么呢?

是一个名为 System.Windows.SharedDp 的对象,不明白是什么?没关系,微软把这个类设置为 internal 了,就是不想让你明白。所以,如果我们的标记扩展需要用到实际控件的一些功能(例如需要订阅事件、需要绑定、需要获取布局……),那么你就需要对 System.Windows.SharedDp 进行判断了。

具体来说,是加上这样的判断:

if (service.TargetObject.GetType().Name.EndsWith("SharedDp"))
{
    return this;
}

更完整一点写出来,就是这样:

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;

namespace Walterlv.Demo
{
    public class RevealBorderBrushExtension : MarkupExtension
    {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            // 如果没有服务,则直接返回。
            if (!(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget service)) return null;
            // MarkupExtension 在样式模板中,返回 this 以延迟提供值。
            if (service.TargetObject.GetType().Name.EndsWith("SharedDp")) return this;
            // 如果不是 FrameworkElement,那么返回 this 以延迟提供值。
            if (!(service.TargetObject is FrameworkElement element)) return this;
            // 如果是设计时,那么返回白色
            if (DesignerProperties.GetIsInDesignMode(element)) return Brushes.White;

            var window = Window.GetWindow(element);
            if (window == null) return this;
            // 这一句是编译不通过的,我只是拿来做示范。
            var brush = CreateBrush(window, element);
            return brush;
        }
    }
}

你可能会觉得这段代码有些熟悉,如果有这种感觉,说明你可能阅读过我的另一篇博客:流畅设计 Fluent Design System 中的光照效果 RevealBrush,WPF 也能模拟实现啦!

本文会经常更新,请阅读原文: https://walterlv.com/post/wpf-markup-extension-in-control-template.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 让你的 Windows 应用程序在任意路径也能够直接通过文件名执行

    我们可以在任何路径下输入 explorer 来启动资源管理器,可以在任何路径中输入 git 来使用 git 相关的命令。我们知道可以通过将一个应用...

    walterlv
  • 制作 Windows 10 安装盘,解决大于 4GB 的 Windows 10 镜像在 UEFI 模式下的安装问题

    2018-02-22 14:14

    walterlv
  • 修复 Windows 10 设置界面里面混乱的语言翻译

    本文介绍的是 Windows 10 的设置界面里面,各种各样的语言文字都很混乱,就像统一错位了一样。本文也会同时介绍其修复方法。

    walterlv
  • 如何提高阅读 SQL 源代码的快感

    今天的小 C 很不在状态。昔日的她,一大早肯定不会愁容满面,似乎像是星巴巴没有喝够的样子,兴奋不起来!11:30 了,很少听到她 HHKB 落键的清脆声,一定是...

    Lenis
  • 一言难尽,Jpa这个功能差点让我丢了工作

    前阵子,有位朋友在微信上问我数据被删了能不能恢复,我问了下原因,居然是因为一个配置项惹的祸。

    猿天地
  • 设计模式:简单工厂模式

    简单工厂模式又称静态工厂方法(Static Factory Method)模式,它不是Gof 所讲的23种设计模式之一,但是它却是我们在编码过程中经常使用的方法...

    王强
  • 本周(4.15-4.21)大数据、AI金融服务和自动驾驶三大方向颇受欢迎 | 投融资汇总

    镁客网
  • body标签中相关标签

    字体标签包含:h1~h6、<font>、<u>、<b>、<strong><em>、<sup>、<sub>

    py3study
  • SpringBoot启动时执行指定任务

    @EventListener({ApplicationReadyEvent.class})

    乐心湖
  • ElasticSearch高版本API的使用姿势

    ElasticSearch之前我没有深入去学过,在上家公司也是简单用了一下,本来是想用来做千万级ip库数据缓存的,后面查询耗时就弃用了,也就没有深入去学习。之前...

    黄泽杰

扫码关注云+社区

领取腾讯云代金券