WPF——自定义RadioButton

2024-09-03 07:36
文章标签 自定义 wpf radiobutton

本文主要是介绍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来实现需求。

这篇关于WPF——自定义RadioButton的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1132421

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

Oracle type (自定义类型的使用)

oracle - type   type定义: oracle中自定义数据类型 oracle中有基本的数据类型,如number,varchar2,date,numeric,float....但有时候我们需要特殊的格式, 如将name定义为(firstname,lastname)的形式,我们想把这个作为一个表的一列看待,这时候就要我们自己定义一个数据类型 格式 :create or repla

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

一步一步将PlantUML类图导出为自定义格式的XMI文件

一步一步将PlantUML类图导出为自定义格式的XMI文件 说明: 首次发表日期:2024-09-08PlantUML官网: https://plantuml.com/zh/PlantUML命令行文档: https://plantuml.com/zh/command-line#6a26f548831e6a8cPlantUML XMI文档: https://plantuml.com/zh/xmi

argodb自定义函数读取hdfs文件的注意点,避免FileSystem已关闭异常

一、问题描述 一位同学反馈,他写的argo存过中调用了一个自定义函数,函数会加载hdfs上的一个文件,但有些节点会报FileSystem closed异常,同时有时任务会成功,有时会失败。 二、问题分析 argodb的计算引擎是基于spark的定制化引擎,对于自定义函数的调用跟hive on spark的是一致的。udf要通过反射生成实例,然后迭代调用evaluate。通过代码分析,udf在

鸿蒙开发中实现自定义弹窗 (CustomDialog)

效果图 #思路 创建带有 @CustomDialog 修饰的组件 ,并且在组件内部定义controller: CustomDialogController 实例化CustomDialogController,加载组件,open()-> 打开对话框 , close() -> 关闭对话框 #定义弹窗 (CustomDialog)是什么? CustomDialog是自定义弹窗,可用于广告、中

mybatis框架基础以及自定义插件开发

文章目录 框架概览框架预览MyBatis框架的核心组件MyBatis框架的工作原理MyBatis框架的配置MyBatis框架的最佳实践 自定义插件开发1. 添加依赖2. 创建插件类3. 配置插件4. 启动类中注册插件5. 测试插件 参考文献 框架概览 MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射,为开发者提供了极大的灵活性和便利性。以下是关于M

vue2实践:第一个非正规的自定义组件-动态表单对话框

前言 vue一个很重要的概念就是组件,作为一个没有经历过前几代前端开发的我来说,不太能理解它所带来的“进步”,但是,将它与后端c++、java类比,我感觉,组件就像是这些语言中的类和对象的概念,通过封装好的组件(类),可以通过挂载的方式,非常方便的调用其提供的功能,而不必重新写一遍实现逻辑。 我们常用的element UI就是由饿了么所提供的组件库,但是在项目开发中,我们可能还需要额外地定义一