当前位置: 首页 > news >正文

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

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

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


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

本文内容

    • 编写简单的标记扩展
    • 编写能在 `ControlTemplate` 中使用的标记扩展

编写简单的标记扩展

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

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://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

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

知识共享许可协议

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

相关文章:

  • PasswordVault —— 在 UWP 应用中安全地保存密码
  • 如何在 MSBuild Target(Exec)中报告编译错误和编译警告
  • 将 .NET Core 项目打一个最简单的 NuGet 源码包,安装此包就像直接把源码放进项目一样
  • 使用 LINQ to XML,.NET 让生成 XML 文件变得和直接写 XML 一样轻松
  • git subtree 不断增加的推送时间,解不玩的冲突!这篇文章应该能救你
  • 阻止某个 NuGet 包意外升级
  • 解读 Microsoft.NET.Sdk 的源码,你能定制各种奇怪而富有创意的编译过程
  • 在 Visual Studio 的解决方案资源管理器中隐藏一些文件
  • 长期支持 LTS(Long-term Support)是怎样的一种支持方式
  • .NET Standard 的管理策略
  • 如何在 .NET/C# 代码中安全地结束掉一个控制台应用程序?通过发送 Ctrl+C 信号来结束
  • Windows 10 应用创建模糊背景窗口的三种方法
  • 使用 PInvoke.net Visual Studio Extension 辅助编写 Win32 函数签名
  • 程序员与英语:即时聊天中的英语缩写 lol / lmao / idk
  • 使用 IFTTT 做 RSS 的邮件订阅服务
  • 【译】JS基础算法脚本:字符串结尾
  • CentOS6 编译安装 redis-3.2.3
  • co模块的前端实现
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • DOM的那些事
  • go append函数以及写入
  • IOS评论框不贴底(ios12新bug)
  • iOS小技巧之UIImagePickerController实现头像选择
  • java多线程
  • mysql常用命令汇总
  • SpiderData 2019年2月25日 DApp数据排行榜
  • Terraform入门 - 3. 变更基础设施
  • Vue2.0 实现互斥
  • 初探 Vue 生命周期和钩子函数
  • 创建一种深思熟虑的文化
  • 动态魔术使用DBMS_SQL
  • 工作中总结前端开发流程--vue项目
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 关于使用markdown的方法(引自CSDN教程)
  • 坑!为什么View.startAnimation不起作用?
  • 数据结构java版之冒泡排序及优化
  • 怎么把视频里的音乐提取出来
  • Android开发者必备:推荐一款助力开发的开源APP
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • #162 (Div. 2)
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • $forceUpdate()函数
  • (C++20) consteval立即函数
  • (ibm)Java 语言的 XPath API
  • (免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐
  • (已解决)什么是vue导航守卫
  • (转)【Hibernate总结系列】使用举例
  • (转)一些感悟
  • *p++,*(p++),*++p,(*p)++区别?
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .bat批处理(七):PC端从手机内复制文件到本地
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .NET Core 控制台程序读 appsettings.json 、注依赖、配日志、设 IOptions