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

WPF——自定义RadioButton

需求

需要做一组单选按钮,只要单选按钮的显示内容与需要匹配的内容一样,则该单选按钮就为选中状态,否则则为不选中状态;且需要将当前选中状态保存,后续再进入此页面时,匹配内容为此次的保存状态。

如下所示,3个单选按钮分别为Test1、Test2、Test3,需要匹配的内容为Test2。那么Test2就为选中状态,其它两个就为非选中状态。

深入分析

通过对需求的了解,可得出下述进一步需求:

单选按钮需要在选中状态下,需要将当前选中内容保存下来,也就是说需要将当前的选中内容作为匹配更新到VM中,在VM中再去实现相应的状态保存。

单选按钮在非选中状态下,可不将当前单选按钮的匹配内容清除(也可以清除),同时要保证这一组单选按钮有一个按钮是选中状态。

根据上述分析,从代码实现上需要考虑以下问题:

匹配内容需要绑定。

首次加载控件时,需要根据匹配内容设置当前控件是否为选中状态。

选中时需要将匹配内容更新,以便VM根据匹配内容的变化以保存它。

取消选中时,可考虑清空匹配内容,若要清空匹配匹配内容,那么在保存匹配内容时需要注意,不要将空值作为匹配内容的一个值保存;或者说空值不需要触发匹配内容的保存功能。

代码实现

按上述深入分析,那么仅需要自定义一个自定义单选按钮即可实现相应功能。详细代码如下:

    public class CustomRadio : RadioButton{static CustomRadio(){}public string Text{get { return (string)GetValue(TextProperty); }set { SetValue(TextProperty, value); }}public static readonly DependencyProperty TextProperty =DependencyProperty.Register("Text", typeof(string), typeof(CustomRadio), new PropertyMetadata(string.Empty, TextChanged));private static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var radio = (CustomRadio)d;var ntxt = e.NewValue as string;// 获取Content属性的BindingExpression//var bindingExpression = BindingOperations.GetBindingExpression(radio, ContentControl.ContentProperty);//if (bindingExpression != null)//{//    // 强制更新绑定//    bindingExpression.UpdateTarget();//}// 现在可以获取到更新后的Content值var content = radio.Content?.ToString();if (content == ntxt){radio.IsChecked = true;}else{radio.IsChecked = false;}}protected override void OnChecked(RoutedEventArgs e){base.OnChecked(e);// 当RadioButton被选中时,更新Text属性Text = Content == null ? string.Empty : Content.ToString();}protected override void OnUnchecked(RoutedEventArgs e){base.OnUnchecked(e);// 当RadioButton未被选中时,清空Text属性Text = string.Empty;}}

注:上述代码中的Text依赖属性即是用于匹配内容的绑定。

以上为测试时使用的VM:

    internal partial class MainWindowViewModel : ObservableObject{[ObservableProperty]TestMethod testMethod = new TestMethod();[ObservableProperty]string testName = "Test2";[ObservableProperty]ObservableCollection<Student> students = [new(){Id=1,Name="test1",Age=12,Grade=1,Y=50},new(){Id=2,Name="test2",Age=12,Grade=2,Y=100},new(){Id=3,Name="test3",Age=12,Grade=3,Y=150},new(){Id=4,Name="test4",Age=12,Grade=4,Y=200},new(){Id=5,Name="test5",Age=12,Grade=5,Y=250},];Timer timer = new();public MainWindowViewModel(){timer.Start();timer.Interval = 2000;timer.Elapsed += Timer_Elapsed;}[RelayCommand]public void StopTimer(){timer.Stop();}private void Timer_Elapsed(object? sender, ElapsedEventArgs e){Random random = new();var index = random.Next(0, Students.Count);Students[index].Grade = random.Next(1, 101);var order = Students.OrderBy(e => e.Grade);//Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>//{}));int i = 0;foreach (Student student in order){student.Id = ++i;student.OldY = student.Y;student.Y = i * 50;student.IsUp = student.Y > student.OldY;//Students[i - 1] = student;}}}public partial class Student : ObservableObject{[ObservableProperty]private int id;[ObservableProperty]private string name = string.Empty;[ObservableProperty]private int age;[ObservableProperty]private int grade;[ObservableProperty]private int y;[ObservableProperty]private int oldY;[ObservableProperty]private bool isUp;}public partial class TestMethod : ObservableObject{[ObservableProperty]string one = "Test1";[ObservableProperty]string two = "Test2";[ObservableProperty]string three = "Test3";}

以下为测试时使用的WPF UI:

<Windowx:Class="WpfApp1.Window1"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:control="clr-namespace:WpfApp1.Control"xmlns:converter="clr-namespace:WpfApp1.Converter"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:WpfApp1"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:viewmodels="clr-namespace:WpfApp1.VM"x:Uid="Window1Title"Width="800"Height="450"mc:Ignorable="d"><Window.DataContext><viewmodels:MainWindowViewModel /></Window.DataContext><Window.Resources></Window.Resources><Grid><Grid.RowDefinitions><RowDefinition Height="100" /><RowDefinition Height="*" /></Grid.RowDefinitions><StackPanel><control:CustomRadioContent="{Binding TestMethod.One}"GroupName="test1"Text="{Binding TestName, Mode=TwoWay}" /><control:CustomRadioContent="{Binding TestMethod.Two}"GroupName="test1"Text="{Binding TestName, Mode=TwoWay}" /><control:CustomRadioContent="{Binding TestMethod.Three}"GroupName="test1"Text="{Binding TestName, Mode=TwoWay}" /><Button Command="{Binding StopTimerCommand}" Content="Close" /></StackPanel></Grid>
</Window>

更优实现

然在搞定此自定义的RadioButton后,若只是单一的一行RadioButton,那么其实还可以有其它实现方式,比如用集合控件。

比如使用ListBox,这样还不用多次使用自定义的RadioButton,只需要绑定时将VM中的集合绑定于ListBox即可。

以下为ListBox结合CustomRadio的实现:

        <StackPanel><ListBox x:Name="list" ItemsSource="{Binding Methods}"><ListBox.ItemsPanel><ItemsPanelTemplate><StackPanel Orientation="Horizontal" /></ItemsPanelTemplate></ListBox.ItemsPanel><ListBox.ItemTemplate><DataTemplate><control:CustomRadioMargin="5"Content="{Binding}"GroupName="test1"Text="{Binding DataContext.TestName, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" /></DataTemplate></ListBox.ItemTemplate></ListBox><Button Command="{Binding StopTimerCommand}" Content="{x:Static loc:Resources.TextBlock1_TextBlock_Text}" /></StackPanel>

然,上述ListBox的实现并不是最好的,它存在以下问题:

每次ListBox内的单选按钮的选中与取消,会触发CustomRadio中的方法OnChecked(原按钮)与OnUnchecked(现按钮),这有两次调用才能将匹配内容更新;而只要改为ListBox的选中事件,那么只需要调用一次就可以更新匹配内容,也就是说从性能上来说,使用ListBox与CustomRadio的组合不是最优,最好是完全自定义ListBox来实现需求。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 使用pytorch深度学习框架搭建神经网络
  • 密码中的字符的判断,字母,数字,特殊字符等
  • CVE-2018-17066漏洞复现 Dlink命令注入漏洞
  • Spring01——Spring简介、Spring Framework架构、Spring核心概念、IOC入门案例、DI入门案例
  • 类和对象的概述以及this指针的应用
  • 搭建 canal 监控mysql数据到Elasticsearch(总结)
  • 深入理解计算机系统阅读笔记-第四章
  • Study Plan For Algorithms - Part24
  • 第 2 章:AJAX 的使用
  • 第 1 章:原生 AJAX
  • 使用isolation: isolate声明隔离混合模式
  • day50——QT
  • HarmonyOS开发实战( Beta5.0)系统提供的接口实践规范
  • 从知识视角理解软件开发
  • 2023 CCPC(秦皇岛)现场(第二届环球杯.第 2 阶段:秦皇岛)部分题解
  • [ JavaScript ] 数据结构与算法 —— 链表
  • Docker: 容器互访的三种方式
  • ECS应用管理最佳实践
  • ES6 ...操作符
  • Javascript编码规范
  • Linux中的硬链接与软链接
  • Making An Indicator With Pure CSS
  • Spring Cloud中负载均衡器概览
  • Unix命令
  • Vim 折腾记
  • 从0实现一个tiny react(三)生命周期
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 前端知识点整理(待续)
  • 微信如何实现自动跳转到用其他浏览器打开指定页面下载APP
  • 想写好前端,先练好内功
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • 与 ConTeXt MkIV 官方文档的接驳
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 云大使推广中的常见热门问题
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • ​1:1公有云能力整体输出,腾讯云“七剑”下云端
  • ​马来语翻译中文去哪比较好?
  • ​业务双活的数据切换思路设计(下)
  • # AI产品经理的自我修养:既懂用户,更懂技术!
  • #### golang中【堆】的使用及底层 ####
  • #162 (Div. 2)
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • #mysql 8.0 踩坑日记
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • $(selector).each()和$.each()的区别
  • ( 用例图)定义了系统的功能需求,它是从系统的外部看系统功能,并不描述系统内部对功能的具体实现
  • (2)MFC+openGL单文档框架glFrame
  • (AngularJS)Angular 控制器之间通信初探
  • (C++哈希表01)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (js)循环条件满足时终止循环
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (八十八)VFL语言初步 - 实现布局
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (三)elasticsearch 源码之启动流程分析