让 WPF 的 RadioButton 支持再次点击取消选中的功能

让 WPF 的 RadioButton 支持再次点击取消选中的功能

目录

让 WPF 的 RadioButton 支持再次点击取消选中的功能

零、前言

一、方法一:后台直接处理

二、方法二:提取为自定义控件(用户控件)

三、方法三:附加行为法

独立观察员 2022 年 01 月 16 日

零、前言

众所周知,RadioButton 是一种单选框,一般是放置好几个在同一面板中以组成一组;使用时,初始时可能一个都没被选中,或者是设置了一个默认选中项;然后,用户可以在这一组单选框中切换选择其中一个,不能多选,也不能取消选中(也就是不能重新回到一个都没选的状态)。

最近公司软件中有个界面,UI 给出的样式就是单选框的形式,所以就使用了一组 RadioButton 来实现,初始是一个都没选,之后用户可以在其中选择一项。可是后来需求说选中的项再次点击需要取消选中,摔!这个功能 RadioButton 是办不到的,CheckBox 是可以的,不过如果换成 CheckBox,一方面样式要改,另一方面,只能选择一项这个需求也要写代码实现(CheckBox 好像可以设置为单选?算了,不要在意这些细节),所以还是找找方法,看能不能让 RadioButton 支持取消选中吧。

一、方法一:后台直接处理

网上找到的方法就是在后台新增一个 bool 变量,用来记录上次(或者说点击前)RadioButton 是选中还是未选中,然后在点击事件中进行判断处理:

5b5fb69bbd3572931239681891e41182.png

来看看效果吧(动图):

5992e42c849db16d31ac2b9f4c6dbf26.gif

上面的动图先演示了 RadioButton 默认是不支持取消选中的;然后演示了通过上面代码实现的支持取消选中的 RadioButton。

这样确实是可以的,但是只适用于只有单个 RadioButton 的情况,因为如果有好几个 RadioButton,那么就要为每个 RadioButton 新建一个布尔变量以及一个点击事件方法,最多是把事件方法整合一下,总之是很奇怪的。

当然,这个战略(引入一个布尔变量来记录上次的选择情况)是没问题,只不过战术(直接在后台处理)有点问题。那么我们使用这个战略的话,还能形成什么战术呢?大致可以想到两种方法,接下来容我一一道来。

二、方法二:提取为自定义控件(用户控件)

我们新建一个名为 RadioButtonUncheck 的用户控件(UserControl),将继承关系改为 RadioButton,并把上一节所示的处理逻辑添加进去:

e98f86589c25781d378078ed32ef7a43.png

前台直接改为实例化一个 RadioButton 即可:

95b1d8002b9d7995b2c097d0688b19fd.png

然后在界面上使用这个用户控件:

73e5bc7265bd3c373395a7fd75db1292.png

看看效果(动图):

0a7c1e9a7cdb8de440ca4bbe15d8be5b.gif

很明显,有一些 Bug,这是为什么呢?原因就是,我们新建的那个用来记录上次选中状态的变量,在用户选中其它项,同时 WPF 框架自动取消选中本项时,没有进行记录。

所以我们需要在 Checked 和 Unchecked 这两个事件中分别对 _lastChecked 进行相应的赋值:

742932611ee888f2d0d82723aad9f15e.png

然后,由于触发了 Click 事件后(也有可能是 PreviewMouseDown 后 Click 前的某个事件,比如 PreviewMouseUp),WPF 框架(或者说是 RadioButton 内部)就会把 IsChecked 设为 true(这就是前面的代码中需要另外新建变量来判断的原因),所以需要换为 PreviewMouseDown 事件,并在处理完成后调用 “e.Handled = true;” 阻止事件继续传递:

d666ee3feea56f7cfb5558f0218026f9.png

现在,当 RadioButtonUncheck 控件通过点击由未选切换为选中时,事件执行顺序为 PreviewMouseDown--Checked:

924e993cf89d37d87086a37086d80895.png

或:

fb7caa862a4d65f245c11f18169dc8b6.png

而由选中切换为未选时,事件执行顺序为 PreviewMouseDown--Unchecked:

7bc579cbfaca02a3690bdfe4deb3eab1.png

而如果没有 “e.Handled = true;”,则由未选切换为选中时,事件执行顺序如下:

4d01b3628e5e9d12657e760f7906b0e5.png

或:

9b6a82864a97ba186cdaae6860dd1721.png

由选中切换为未选时(切换失败),事件执行顺序如下:

4028a8dd09911fbccb9f3fc15867fef1.png

至此,用户控件法圆满完成任务(动图):

4edf66ad0dbfc929c28da177454b989d.gif

完整代码:

using System;
using System.Windows;
using System.Windows.Controls;namespace WPFPractice.UserControls
{/// <summary>/// 支持点击取消选中的 RadioButton;/// </summary>public partial class RadioButtonUncheck : RadioButton{/// <summary>/// 上次的选中状态/// </summary>private bool _lastChecked;/// <summary>/// 内容字符串/// </summary>private string ContentStr => Content + "";public RadioButtonUncheck(){InitializeComponent();Click += RadioButtonUncheck_Click; ;PreviewMouseDown += RadioButtonUncheck_PreviewMouseDown; ;Checked += RadioButtonUncheck_Checked;Unchecked += RadioButtonUncheck_Unchecked;}/// <summary>/// 点击事件处理方法/// </summary>private void RadioButtonUncheck_Click(object sender, RoutedEventArgs e){Console.WriteLine($"[{ContentStr}] 触发 Click 事件 ");//SwitchStatus();}/// <summary>/// 鼠标按下事件处理方法/// </summary>private void RadioButtonUncheck_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e){Console.WriteLine($"[{ContentStr}] 触发 PreviewMouseDown 事件 ");SwitchStatus();e.Handled = true;}/// <summary>/// 切换状态/// </summary>private void SwitchStatus(){if (_lastChecked){IsChecked = false;//_lastChecked = false;}else{IsChecked = true;//_lastChecked = true;}}/// <summary>/// 选中事件 处理方法/// </summary>private void RadioButtonUncheck_Checked(object sender, RoutedEventArgs e){Console.WriteLine($"[{ContentStr}] 触发 Checked 事件 ");_lastChecked = true;}/// <summary>/// 取消选中事件 处理方法/// </summary>private void RadioButtonUncheck_Unchecked(object sender, RoutedEventArgs e){Console.WriteLine($"[{ContentStr}] 触发 Unchecked 事件 ");_lastChecked = false;}}
}

三、方法三:附加行为法

关于附加行为,是通过附加属性来实现的,可以参考我之前的翻译文章《【翻译】WPF 中附加行为的介绍 Introduction to Attached Behaviors in WPF》:

在一个元素上设置一个附加属性,那么你就可以从暴露这个附加属性的类中获得该元素的访问。一旦那个类有权限访问那个元素,它就能在其上挂钩事件,响应这些事件的触发,使该元素做出它本来不会做的事情。

下面直接进入正题,首先在一个新建类 RadioButtonAttached 中添加一个 bool 类型的附加属性 IsCanUncheck,当其被设置为 true 时,会给设置的元素附加 PreviewMouseDown、Checked、Unchecked 三个事件,和上一节一样:

d5c9847b309198cf67b810a0d441dd3d.png

注意,附加属性还需要两个包装方法:

e624519487cf46e1e94f72c41951405e.png

由于附加属性的变动处理方法要求是静态方法:

1641d0081eef713ce72cd580395f73d0.png

所以导致三个事件的处理方法也要是静态方法,不然就会报错:

5bc2e17d7a6cb376b77f5b95d6ac98e9.png

进而导致之前引入成员变量 _lastChecked 的方式行不通了:

7244f1cedf86037c36ee3039eb2790dc.png

所以这个状态存储的地方需要另外寻找。对于这种情况,我经常使用的是元素的 Tag 属性,这次也是这样干的,也就是说使用单选框的 Tag 来存储上次的选中与否状态。

Checked 和 Unchecked 中还是换汤不换药:

c98c29ea34647a6b9d27115143b976b0.png

主要是 PreviewMouseDown 事件处理方法中,当第一次点击,Tag 中还没有存储时,bool 会转换失败,所以 Tag 中应该存储 true 供下次使用;而转换成功则将转换出的值(存在 lastChecked 变量中)取反存入 Tag 中供下次使用。(这样看来两种情况好像都可以直接使用 rb.Tag = !lastChecked; 哈哈,懒得改了)。之后就是依据 lastChecked 来决定(取反)IsChecked 的值:

3d95100be1075e2286b92dce41948d2b.png

完整代码:

using System.Windows;
using System.Windows.Controls;namespace WPFTemplateLib.Attached;/// <summary>
/// RadioButton 附加属性类
/// </summary>
public class RadioButtonAttached : DependencyObject
{#region IsCanUncheckpublic static bool GetIsCanUncheck(FrameworkElement item){return (bool)item.GetValue(IsCanUncheckProperty);}public static void SetIsCanUncheck(FrameworkElement item, bool value){item.SetValue(IsCanUncheckProperty, value);}/// <summary>/// 是否能取消选中 (启用此功能会占用 Tag 属性)/// </summary>public static readonly DependencyProperty IsCanUncheckProperty =DependencyProperty.RegisterAttached("IsCanUncheck",typeof(bool),typeof(RadioButtonAttached),new UIPropertyMetadata(false, OnIsCanUncheckChanged));static void OnIsCanUncheckChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e){FrameworkElement item = depObj as FrameworkElement;if (item == null)return;switch (depObj){case RadioButton radioButton:{if ((bool) e.NewValue){radioButton.PreviewMouseDown += RadioButton_PreviewMouseDown;radioButton.Checked += RadioButton_Checked;radioButton.Unchecked += RadioButton_Unchecked;}else{radioButton.PreviewMouseDown -= RadioButton_PreviewMouseDown;radioButton.Checked -= RadioButton_Checked;radioButton.Unchecked -= RadioButton_Unchecked;}break;}default:break;}}private static void RadioButton_Unchecked(object sender, RoutedEventArgs e){var rb = sender as RadioButton;if (rb == null){return;}rb.Tag = false;}private static void RadioButton_Checked(object sender, RoutedEventArgs e){var rb = sender as RadioButton;if (rb == null){return;}rb.Tag = true;}private static void RadioButton_PreviewMouseDown(object sender, RoutedEventArgs e){var rb = sender as RadioButton;if (rb == null){return;}// 使用 RadioButton 的 Tag 来存储上次选中的状态,之后可以从中获取来进行判断;bool parseSuccess = bool.TryParse(rb.Tag + "", out bool lastChecked);if (!parseSuccess){// 转换失败,说明是第一次点击,也就是本次本勾选了,所以应该把 true 存起来;rb.Tag = true;}else{rb.Tag = !lastChecked;}if (lastChecked){rb.IsChecked = false;//lastChecked = false;}else{rb.IsChecked = true;//lastChecked = true;}e.Handled = true;}#endregion
}

使用时只需要在普通 RadioButton 元素上加上这个附加属性并将值置为 True 即可:

66c287ae80673f67409b14e815f70221.png

效果和上一节的一样(实际上方法三是先写成的),就不再演示了,来个全家福吧:

47f303db5e75a2e83f29972fd28fa5c8.png

最后是源码地址:https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20220116 

WPF

WPF DataGrid 如何将被选中行带到视野中

WPF 触屏事件后触发鼠标事件的问题及 DataGrid 误触问题

WPF DataGrid 通过自定义表头模拟首行固定

WPF ComboBox 使用 ResourceBinding 动态绑定资源键并支持语言切换

【翻译】WPF 中附加行为的介绍 Introduction to Attached Behaviors in WPF

WPF 使用 Expression Design 画图导出及使用 Path 画图

WPF MVVM 弹框之等待框

解决 WPF 绑定集合后数据变动界面却不更新的问题(使用 ObservableCollection)

WPF 消息框 TextBox 绑定新数据时让光标和滚动条跳到最下面

真・WPF 按钮拖动和调整大小

WPF MVVM 模式下的弹窗

WPF 让一组 Button 实现 RadioButton 的当前样式效果

WPF 原生绑定和命令功能使用指南

WPF 用户控件的自定义依赖属性在 MVVM 模式下的使用备忘

在WPF的MVVM模式中使用OCX组件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292524.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java数组转换成string_java面试复习重点:类的管理及常用工具,教你抓住面试重点

java复习&#xff1a; 类的管理及常用工具类包写在程序文件的第一行一个Java 源文件中只能声明一个包&#xff0c;且声明语句只能作为源文件的第一条指令导入类能导入非public类&#xff0c;但是不能用因为在其他包缺省的权限用不了package Testp;import Testpackage.*;public …

ubuntu 新建的用户 table 无法补全命令 解决办法

为什么80%的码农都做不了架构师&#xff1f;>>> vi /etc/passwd 用adduser命令新增了用户之后&#xff0c;发现在该新建用户下的命令终端&#xff0c;使用方向键无法调出历史命令&#xff0c;同时tab键也无法补全输入命令。 找到 你新增的用户xxx 修改/bin/bash…

【Unity3D基础】让物体动起来②--UGUI鼠标点击逐帧移动

背景 上一篇通过鼠标移动的代码很简单&#xff0c;所以看的人也不多&#xff0c;但是还是要感谢“武装三藏”在博客园给出的评论和支持&#xff0c;希望他也能看到第二篇&#xff0c;其实可以很简单&#xff0c;而且是精灵自控制&#xff0c;关键是代码少是我喜欢的方式&#x…

一个有趣的Go项目,3D界面管理k8s集群,真好玩!

大家好&#xff0c;我是小碗汤&#xff0c;今天分享一个用Golang开发&#xff0c;很好玩的工具KubeCraftAdmin[1]&#xff1a;用Minecraft方式管理k8s的工具&#xff0c;感兴趣的兄弟不妨玩一玩。文末有视频&#xff0c;供您鉴赏~Minecraft&#xff1a;我的世界&#xff0c;是微…

java 数组拼接_打印Java数组最优雅的方式是什么?这波操作闪瞎我

在 Java 中&#xff0c;数组虽然是一个对象&#xff0c;但并未明确的定义这样一个类&#xff0c;因此也就没有覆盖 toString() 方法的机会。如果尝试直接打印数组的话&#xff0c;输出的结果并不是我们预期的结果。那有没有一些简单可行的方式呢&#xff1f;如果大家也被这个问…

AmazeUI基本样式

2019独角兽企业重金招聘Python工程师标准>>> AmazeUI是一个轻量级、Mobile first的前端框架&#xff0c;基于开源社区流行的前端框架编写。 Normalize AmazeUI使用了normalize.css&#xff0c;但做了些调整&#xff1a;html添加了-webkit-font-smoothing:antialiase…

Oracle基础中的基础视频讲座录像(西安)供免费下载

下载地址播放器也在上述目录中。记得那年园博会&#xff0c;培训中间有一天参观&#xff1a;转载于:https://blog.51cto.com/botang/1323099

她13岁自己造飞机,17岁进麻省理工,3篇黑洞论文被霍金引用......

全世界只有3.14 % 的人关注了爆炸吧知识13岁的时候&#xff0c;你在干嘛&#xff1f;我想&#xff0c;绝大多数人都没有萨布丽娜这么硬核——独自组装飞机。之后&#xff0c;16岁的萨布丽娜完成了生涯首飞&#xff0c;历史第一年轻。17岁时&#xff0c;她考上麻省理工&#xff…

Windows 10 2022 年更新来了!

面向 Release Preview 频道的 Windows 10 预览体验成员&#xff0c;微软现已发布 Windows 10 Build 19044.1499。主要修复1.微软修复了阻止某些环绕声音频在 Microsoft Edge 中播放的问题。2.微软修复了使用中文输入法时&#xff0c;一些应用停止工作的意外错误。3.微软修复了在…

当代成年人的生活状态......

1 小喵咪举起了它的狙击枪▼2 简直有毒...▼3 哈哈哈哈哈▼4 电焊既视感▼5 请问需要小猫咪吗&#xff1f;▼6 哈哈哈哈哈▼7 一物降一物▼7 数学能有多好玩&#xff1f;▼你点的每个赞&#xff0c;我都认真当成了喜欢

c++ why can't class template hide its implementation in cpp file?

类似的问题还有&#xff1a; why cant class template use Handle Class Pattern to hide its implementation? || why there are linker problems (undefined reference) to my class template? 我出现问题的源码&#xff08;见main.cpp,Stack.h,Stack.cpp&#xff09;&…

C# using static 声明

许多实际的扩展可以通过扩展方法来实现&#xff0c;并非所有实际的扩展都有可以扩展的类型。对于某些场景&#xff0c;简单的静态方法比较适合。为了更容易调用这些方法&#xff0c;可以使用 using static 声明除去类名。例如&#xff0c;如果打开了 System.Console using stat…

PHP性能追踪及分析工具xhprof的安装与使用

PHP性能追踪及分析工具xhprof的安装与使用 对于本地开发环境来说&#xff0c;进行性能分析xdebug是够用了&#xff0c;但如果是线上环境的话&#xff0c;xdebug消耗较大&#xff0c;配置也不够灵活&#xff0c;因此线上环境建议使用xhprof进行PHP性能追踪及分析。 我们今天就简…

C 语言 int 型乘法溢出问题

2019独角兽企业重金招聘Python工程师标准>>> long l; int a, b; l a*b; 因为 a*b 的结果仍然以 int 型保存, 所以即使 l 为long,仍然会有溢出,并且截去了部分数据.出现问题. 转载于:https://my.oschina.net/simon203/blog/175885

Android插件化开发基础之Java类加载器与双亲委派模型

类加载器 Java虚拟机类加载过程是把Class类文件加载到内存&#xff0c;并对Class文件中的数据进行校验、转换解析和初始化&#xff0c;最终形成可以被虚拟机直接使用的java类型的过程。 在加载阶段&#xff0c;java虚拟机需要完成以下3件事&#xff1a; a.通过一个类的全限定名…

将k8s制作成3D射击游戏,好玩到停不下来,附源码

点击上方蓝字 关注【我的小碗汤】大家好&#xff0c;我是小碗汤&#xff0c;今天演示一个项目&#xff0c;利用Unity做场景、用C#做交互逻辑&#xff0c;将k8s制作成一个3D射击游戏。正好最近在学习Unity&#xff0c;所以利用这个项目开始上手挺合适的。源码、可执行文件可以自…

JavaJVM之ClassLoader源码分析

层次结构和类图 ClassLoader层次结构&#xff1a;UML类图&#xff1a;sun.misc.Launcher.ExtClassLoader sun.misc.Launcher.AppClassLoader 显式加载类 在代码中显式加载某个类&#xff0c;有三种方法&#xff1a;this.getClass().getClassLoader().loadClass()Class.forName(…