WPF 附加属性+控件模板,完成自定义控件。建议观看HandyControl源码
文章目录
- 相关连接
- 前言
- 需要实现的效果
- 附加属性
- 添加附加属性,以Test修改FontSize为例
- 依赖属性使用
- 触发器使用
- 直接操控
- 结论
- 控件模板,在HandyControl的基础上面进行修改
- 参考HandyControl的源码
- 控件模板原型
- 控件模板
- 控件模板触发器
- 完整样式
- 简单使用
- 结论
相关连接
WPF控件模板(6)
WPF 附加属性
WPF教程:附加属性
前言
今天说服了领导用WPF开发前端,原因就是开发相对来说比较方便,写小项目就不用前后端分离什么的了。反正就是有个机会写WPF了,真开心。我已经写了一年的Uniapp了
需要实现的效果
就是想写一个简单的变色控件。

附加属性
如果想知道附加属性,就得先了解依赖属性。详细的可以看我这篇文章
WPF 用户控件依赖属性赋值
添加附加属性,以Test修改FontSize为例
知道了依赖属性之后,我解释一下附加属性是什么意思。附加属性就是为了方便在原有的控件基础上面进行细微的修改。我们先保证编译通过
附加属性的快捷键是propa
简单给TextBox添加一个附加属性
public partial class TextBlockExtension{public static int GetTest(DependencyObject obj){return (int)obj.GetValue(TestProperty);}public static void SetTest(DependencyObject obj, int value){obj.SetValue(TestProperty, value);}// Using a DependencyProperty as the backing store for Test. This enables animation, styling, binding, etc...public static readonly DependencyProperty TestProperty =DependencyProperty.RegisterAttached("Test", typeof(int), typeof(TextBox), new PropertyMetadata(10));}
这样我们就能编译通过了。
<TextBlock Text="用户" wpfEx:TextBlockExtension.Test="2"/>

依赖属性使用
依赖属性有两种使用方法
触发器使用
样式定义
<!--一个简单的FontSize修改--><Style x:Key="UserSelection"TargetType="TextBlock"><!--因为Triggers只有等于判断,所以这里简单写了一下--><Style.Triggers><Trigger Property="wpfEx:TextBlockExtension.Test"Value="10"><Setter Property="FontSize"Value="10" /></Trigger><Trigger Property="wpfEx:TextBlockExtension.Test"Value="20"><Setter Property="FontSize"Value="20" /></Trigger></Style.Triggers></Style>
简单使用
<TextBlock Text="用户"wpfEx:TextBlockExtension.Test="10" Style="{StaticResource UserSelection}"></TextBlock><TextBlock Text="用户"wpfEx:TextBlockExtension.Test="20"Style="{StaticResource UserSelection}"></TextBlock>

直接操控
附加属性修改
//如果想直接操控元素,得在PropertyMetadata进行操控。记得设置初始值
public static readonly DependencyProperty TestProperty =DependencyProperty.RegisterAttached("Test", typeof(int), typeof(TextBox), new PropertyMetadata(10,(s, e) =>{//s是控件本身,var mdp = s as TextBlock;//如果控件是该元素的父组件,类似于Grid和DockPanel,就使用Parent来寻找,这里不展开//var mdpParent = (s as FrameworkElement).Parent as TextBlock;if (mdp != null && e.NewValue != null){mdp.FontSize = (int)e.NewValue;}}));
<!--如果想要预览生效需要重新编译一下-->
<TextBlock Text="用户"wpfEx:TextBlockExtension.Test="15"></TextBlock>
<TextBlock Text="用户"wpfEx:TextBlockExtension.Test="20">
</TextBlock>
<!--因为我们设置了默认值为10,所以这里是10-->
<TextBlock Text="用户">
</TextBlock>
<!--优先级还是依赖属性高,所以这里是30而不是默认值10-->
<TextBlock Text="用户" FontSize="30">
</TextBlock>

结论
附加属性和依赖属性差不多,就是声明麻烦一点。因为Get,Set是需要额外写的。
控件模板,在HandyControl的基础上面进行修改
控件模板一般用于按钮,我们只要会按钮的控件模板就可以了。
WPF控件模板(6)
参考HandyControl的源码
HandyControl 页面
HandyControl的Button有IconButton的样式源码。看一下还是挺有收获的。

参考样式代码
<Style x:Key="ButtonDashedBaseStyle" BasedOn="{StaticResource ButtonBaseStyle}" TargetType="Button"><Setter Property="Background" Value="Transparent"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="Button"><hc:DashedBorder BorderDashArray="3,2" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="Transparent" CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}"><StackPanel Orientation="Horizontal" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}"><Path x:Name="PathMain" Width="{TemplateBinding hc:IconElement.Width}" Height="{TemplateBinding hc:IconElement.Height}" Fill="{TemplateBinding Foreground}" SnapsToDevicePixels="True" Stretch="Uniform" Data="{TemplateBinding hc:IconElement.Geometry}"/><ContentPresenter x:Name="ContentPresenterMain" RecognizesAccessKey="True" VerticalAlignment="Center" Margin="6,0,0,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/></StackPanel></hc:DashedBorder><ControlTemplate.Triggers><Trigger Property="Content" Value="{x:Null}"><Setter Property="Visibility" Value="Collapsed" TargetName="ContentPresenterMain"/></Trigger><Trigger Property="hc:IconElement.Geometry" Value="{x:Null}"><Setter Property="Visibility" Value="Collapsed" TargetName="PathMain"/><Setter Property="Margin" Value="0" TargetName="ContentPresenterMain"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style>

控件模板原型
我们想写一个控件模板,如果不是很熟练,我们就先把控件模板的原型写出来,这样更利于理解。
<DockPanel><!--这里仿照HandyControl,使用Gemotery。IconPacks怎么转Gemotery可以看我的文章--><Border DockPanel.Dock="Top"Width="50"Height="50"CornerRadius="25"Background="DeepSkyBlue"><Path Data="{wpfEx:MaterialGeometry Kind=BellRing}"HorizontalAlignment="Stretch"VerticalAlignment="Stretch"SnapsToDevicePixels="True"Stretch="Uniform"Width="25"Height="25"Fill="White" /></Border><TextBlock Text="TIM登录"HorizontalAlignment="Center" /></DockPanel>

控件模板
<Style x:Key="UserSelection"TargetType="RadioButton"BasedOn="{StaticResource {x:Type RadioButton}}"><Setter Property="Template"><Setter.Value><!--先按照之前的样式粘贴一下--><ControlTemplate TargetType="RadioButton"><DockPanel><Border DockPanel.Dock="Top"Width="50"Height="50"CornerRadius="25"Background="DeepSkyBlue"><Path Data="{wpfEx:MaterialGeometry Kind=BellRing}"HorizontalAlignment="Stretch"VerticalAlignment="Stretch"SnapsToDevicePixels="True"Stretch="Uniform"Width="25"Height="25"Fill="White" /></Border><ContentPresenter x:Name="ContentPresenterMain"RecognizesAccessKey="True"VerticalAlignment="Center"Margin="6,0,0,0"SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /></DockPanel></ControlTemplate></Setter.Value></Setter>
</Style>

然后里面能绑定的就绑定。也是照着HandyControl改的。注意这里的Banding用的是TemplateBinding

修改好的效果
<!--一个简单的FontSize修改-->
<Style x:Key="UserSelection"TargetType="RadioButton"BasedOn="{StaticResource {x:Type RadioButton}}"><Setter Property="Template"><Setter.Value><!--先按照之前的样式粘贴一下--><ControlTemplate TargetType="RadioButton"><DockPanel><Border DockPanel.Dock="Top"Width="{TemplateBinding hc:IconElement.Width}"Height="{TemplateBinding hc:IconElement.Height}"CornerRadius="25"Background="{TemplateBinding Foreground}"><Path Data="{TemplateBinding hc:IconElement.Geometry}"HorizontalAlignment="Stretch"VerticalAlignment="Stretch"SnapsToDevicePixels="True"Stretch="Uniform"Width="25"Height="25"Fill="{TemplateBinding Background}" /></Border><ContentPresenter x:Name="ContentPresenterMain"RecognizesAccessKey="True"VerticalAlignment="Center"Margin="6,0,0,0"SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /></DockPanel></ControlTemplate></Setter.Value></Setter>
</Style>
简单使用
<RadioButton Content="TIM登录"GroupName="UserSelect"Style="{StaticResource UserSelection}"Foreground="DeepSkyBlue"Background="White"hc:IconElement.Geometry="{wpfEx:MaterialGeometry Kind=AbTesting}" />

控件模板触发器
完整样式
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:wpfEx="clr-namespace:BluetoothWPF.WpfExtensions"xmlns:hc="https://handyorg.github.io/handycontrol"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><!--一个简单的FontSize修改--><Style x:Key="UserSelection"TargetType="RadioButton"BasedOn="{StaticResource {x:Type RadioButton}}"><Setter Property="Foreground"Value="Gray" /><Setter Property="Template"><Setter.Value><!--先按照之前的样式粘贴一下--><ControlTemplate TargetType="RadioButton"><DockPanel><Border DockPanel.Dock="Top"Width="70"Height="70"CornerRadius="35"x:Name="Background"><Path Data="{TemplateBinding hc:IconElement.Geometry}"x:Name="Icon"HorizontalAlignment="Stretch"VerticalAlignment="Stretch"SnapsToDevicePixels="True"Stretch="Uniform"Width="35"Height="35"Fill="Gray" /></Border><ContentPresenter x:Name="ContentPresenterMain"RecognizesAccessKey="True"VerticalAlignment="Center"HorizontalAlignment="Center"Margin="6,0,0,0"SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /></DockPanel><ControlTemplate.Triggers><Trigger Property="IsMouseOver"Value="True"><Setter TargetName="Background"Property="Background"Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}" /><Setter TargetName="Icon"Property="Fill"Value="White" /></Trigger><Trigger Property="IsFocused"Value="True"><Setter TargetName="Background"Property="Background"Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}" /><Setter TargetName="Icon"Property="Fill"Value="White" /></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style><Style TargetType="RadioButton"x:Key="UserSelectioin_Admin"BasedOn="{StaticResource UserSelection}"><Setter Property="HorizontalAlignment"Value="Right" /><Setter Property="Margin"Value="0 0 10 0" /><Setter Property="Background"Value="DeepSkyBlue" /><Setter Property="hc:IconElement.Geometry"Value="{wpfEx:MaterialGeometry Kind=AccountLock}" /><Setter Property="Content"Value="管理员登录" /></Style><Style TargetType="RadioButton"x:Key="UserSelectioin_User"BasedOn="{StaticResource UserSelection}"><Setter Property="HorizontalAlignment"Value="Left" /><Setter Property="Margin"Value="10 0 0 0" /><Setter Property="Background"Value="Green" /><Setter Property="hc:IconElement.Geometry"Value="{wpfEx:MaterialGeometry Kind=Account}" /><Setter Property="Content"Value="用户" /></Style>
</ResourceDictionary>
简单使用
<RadioButton Style="{StaticResource UserSelectioin_Admin}" />
<RadioButton Style="{StaticResource UserSelectioin_User}" />


结论
HandyControl的源码看了真的是打开眼界,但是WPF的Xaml有一个无法在内部简单计算的问题。比如我想Witdh=Height = CornerRadius*2。我可能就要写个触发器了。我后面回去测试一下有没有方法可以在Xaml里面简单计算的。
相关文章:
WPF 附加属性+控件模板,完成自定义控件。建议观看HandyControl源码
文章目录 相关连接前言需要实现的效果附加属性添加附加属性,以Test修改FontSize为例依赖属性使用触发器使用直接操控 结论 控件模板,在HandyControl的基础上面进行修改参考HandyControl的源码控件模板原型控件模板 控件模板触发器完整样式简单使用 结论 …...
编程笔记 Golang基础 040 defer、panic 和 recover
编程笔记 Golang基础 040 defer、panic 和 recover 一、defer二、panic三、recover小结 在Go语言中,defer、panic 和 recover 是一组用于错误处理和控制程序流程的关键字。它们之间的交互有助于实现异常处理机制,并确保资源的正确释放。 一、defer defe…...
通过redfish协议实现服务器固件升级、从虚拟光驱启动自检盘并等待完成,最后截图保存
通过redfish协议实现服务器固件升级、从虚拟光驱启动自检盘并等待完成,最后截图保存 版本信息代码新开发的PCIE设备在做服务器适配时,有时需要服务器厂家更新BMC或BIOS固件。同时,我们也希望对PCIE设备做一些检测,最后收集一些信息存档。如果需要处理的服务器很多,通过BMC的界面…...
ARM 版银河麒麟桌面系统下 Qt 开发环境搭建指南
目录 前言安装Linux ARM 版 QtCreator配置 Qt Creator配置构建套件 第一个麒麟 Qt 应用程序小结 前言 在上一篇文章信创ARM架构QT应用开发环境搭建中建议大家使用 Ubuntu X86 系统作为信创 ARM 架构 QT 应用的开发环境,里面使用了交叉编译的方式。这对于自己的 Qt …...
架构面试题汇总:缓存(二)
目录 1. 问题:什么是缓存,以及为什么我们需要缓存?2. 问题:你能解释一下缓存击穿、缓存雪崩和缓存预热是什么吗?3. 问题:如何在Java中实现缓存?4. 问题:你如何决定哪些数据应该被缓存…...
【docker入门】1-
文章目录 参考: Docker – 容器虚拟化平台。 参考: docker入门,这一篇就够了。【零基础入门Docker】Dockerfile中的USER指令以及dockerfile命令详解dockerfile copy命令...
微信小程序-全局配置
个人笔记,仅供参考。 1.entryPagePath 代码: "entryPagePath": "pages/index/index" 具体用法: 2.pages 小程序中新增/减少页面,都需要对 pages 数组进行修改。 代码: "pages": [&…...
【Android】性能优化之内存、网络、布局、卡顿、安装包、启动速度优化
欢迎来到 Android 开发老生常谈的性能优化篇,本文将性能优化划分为内存、网络、布局、卡顿、安装包、启动速度七块,从这七块优化出发,阐述优化的 Application 的方式。 目录 内存优化避免内存泄漏使用内存分析工具优化数据结构和算法数据缓存…...
第3.6章:StarRocks数据导入——DataX StarRocksWriter
一、Datax 1.1 DataX 3.0概述 DataX3.0是一个异构数据源离线同步工具,可以方便的对各种异构数据源进行高效的数据同步。 其github地址为: https://github.com/alibaba/DataX/blob/master/introduction.mdhttps://github.com/alibaba/DataX/blob/mast…...
【非递归版】归并排序算法(2)
目录 MergeSortNonR归并排序 非递归&归并排序VS快速排序 整体思想 图解分析 代码实现 时间复杂度 归并排序在硬盘上的应用(外排序) MergeSortNonR归并排序 前面的快速排序的非递归实现,我们借助栈实现。这里我们能否也借助栈去…...
[C++]C++实现本地TCP通讯的示例代码
这篇文章主要为大家详细介绍了C如何利用TCP技术,实现本地ROS1和ROS2的通讯,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下 概要服务端代码 头文件源代码客户端代码 概要 利用TCP技术,实现本地ROS1和ROS2的通讯。 服务端代码 头文件 #include &…...
Sora - 探索AI视频模型的无限可能
文章目录 每日一句正能量前言技术解析应用场景未来展望伦理与创意用户体验与互动后记 每日一句正能量 . 一个人,如果没有经受过投资失败的痛楚,又怎么会看到绝望之后的海阔天空。很多时候,经历了人生中最艰难的事,反而锻造了最坚强…...
【JavaScript 漫游】【022】事件模型
文章简介 本篇文章为【JavaScript 漫游】专栏的第 022 篇文章,对 JavaScript 中事件模型相关的知识点进行了总结。 监听函数 浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到…...
【加密算法】RSA非对称加密算法简介
目录 前言 工作原理 密钥生成 加密和解密 在Java中使用RSA 生成密钥对 加密和解密数据 加密数据 解密数据 注意事项和最佳实践 结论 前言 RSA(Rivest-Shamir-Adleman)是一种基于数论的非对称加密算法,广泛应用于数字签名、数据加密…...
深入理解 JavaScript 对象原型,解密原型链之谜(上)
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
产品经理学习-产品运营《什么是SOP》
目录 什么是SOP 如何执行SOP 执行SOP的重点 什么是SOP SOP就是项目流程操作的说明书 日常工作中的例行操作: 例行操作是指,在每一天,针对每一个用户,在每个项目之中,都必须完成的操作,这些必须完成的操…...
大数据Hadoop生态圈
存储: HDFS(namenode,datanode) 计算:MapReduce(mapreduce,基于磁盘) 便于用sql操作:Hive(核心 metastore,存储这些结构化的数据),同类的还有Impala,hbase等 基于yaml的资源调度 hive &…...
算法简介:查找与算法运行时间
文章目录 1. 二分查找与简单查找1.1 运行时间 2. 旅行商问题 算法是一组完成任务的指令。任何代码片段都可以视为算法。 1. 二分查找与简单查找 二分查找是一种算法,其输入是一个有序的元素列表,如果要查找的元素包含在列表中,二分查找返回…...
零基础C++开发上位机--基于QT5.15的串口助手(三)
本系列教程本着实践的目的,争取每一节课都带大家做一个小项目,让大家多实践多试验,这样才能知道自己学会与否。 接下来我们这节课,主要学习一下QT的串口编程。做一款自己的串口助手,那么这里默认大家都是具备串口通信…...
Facebook的虚拟社交愿景:元宇宙时代的新起点
在当今数字化时代,社交媒体已经成为人们生活中不可或缺的一部分。而随着科技的不断进步和社会的发展,元宇宙已经成为了人们关注的热点话题之一。作为社交媒体的领军企业之一,Facebook也在积极探索虚拟社交的未来,将其视为元宇宙时…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...
恶补电源:1.电桥
一、元器件的选择 搜索并选择电桥,再multisim中选择FWB,就有各种型号的电桥: 电桥是用来干嘛的呢? 它是一个由四个二极管搭成的“桥梁”形状的电路,用来把交流电(AC)变成直流电(DC)。…...
AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...
