WPF——自定义RadioButton

news2024/9/20 0:49:59

需求

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

如下所示,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:

<Window
    x: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:CustomRadio
                Content="{Binding TestMethod.One}"
                GroupName="test1"
                Text="{Binding TestName, Mode=TwoWay}" />
            <control:CustomRadio
                Content="{Binding TestMethod.Two}"
                GroupName="test1"
                Text="{Binding TestName, Mode=TwoWay}" />
            <control:CustomRadio
                Content="{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:CustomRadio
                            Margin="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来实现需求。

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

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

相关文章

界面风格选择

风格一 卡通计算机元素加侧边框 风格二 电子科幻元素 风格三 自然景观元素 这个图片有一束从山顶照耀下来的光&#xff0c;很有特色。 登陆注册框样式 上面这个图的光泽感很新颖

大数据新视界--大数据大厂之大数据时代的璀璨导航星:Eureka 原理与实践深度探秘

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

虚幻引擎VR游戏开发 | VR设备和术语

四款Unreal Engine默认配套按键映射的VR设备 IMC按键映射 Oculus Touch (R) Grip Axis: 代表Oculus Rift或Quest设备的右手控制器的抓握轴输入。Valve Index (R) Grip Axis: 代表Valve Index设备的右手控制器的抓握轴输入。Vive (R) Grip: 代表HTC Vive设备的右手控制器的抓握按…

【ESP32】mqtt 发送和接收

一、首先下载MQTT-X软件 二、下载后建立mqtt&#xff0c;设置这些参数 点击连接&#xff0c;然后创建topic主题 三、程序 注释已经写的很清楚了 #include <WiFi.h> // 包含WiFi库&#xff0c;用于连接WiFi网络 #include <PubSubClient.h> // 包含MQTT库&#xf…

【C++八股题整理】内存布局、堆和栈、内存泄露、函数调用栈

C八股题整理 内存布局C中的内存分配情况堆和栈的内存有什么区别&#xff1f; 堆堆内存分配慢如何优化&#xff1f;内存池内存溢出和内存泄漏是什么&#xff1f;如何避免&#xff1f;内存碎片是什么&#xff1f;怎么解决&#xff1f; 栈为什么栈的访问效率比堆高&#xff1f;函数…

奉加微PHY6233进入DTM模式;TX单音信号;

TX单音信号 参考文档"PH62XX射频测试仪器操作说明文档.pdf"进行DTM配置和操作,这里的目的是为了测试频偏: 这里先把原厂给的DTM的ihex固件下载到芯片里面去: 设置好参数后点击start按钮即可打出单音信号: 这时候频谱的信号如下: 接下来调成其他参数可以看到如下频…

一文彻底搞懂Spring, Spring MVC, Spring Boot 和 Spring Cloud 区别

1.定义说明 Spring, Spring MVC, Spring Boot 和 Spring Cloud 是Spring家族中的四个不同的项目&#xff0c;它们有各自的功能&#xff0c;并且可以在Spring应用程序中一起使用。 1&#xff09;Spring Spring是一个开源容器框架&#xff0c;它集成各类型的工具&#xff0c;通…

Android ROM和Linux内核源码在线阅读网站

1&#xff0c; Android在线代码阅读 http://www.aospxref.com/ 支持最新android源码 http://androidxref.com/ 支持到Android9 2&#xff0c; Linux内核在线阅读网站 https://lxr.missinglinkelectronics.com/ 支持在线阅读linux内核&#xff0c;uboot&#xff0c;qemu &am…

JavaScript方法链

前言 在JavaScript中&#xff0c;方法链&#xff08;Chaining Methods&#xff09;是一种编程技术&#xff0c;允许你在一个表达式中连续调用多个方法。这通常通过让每个方法返回对象自身&#xff08;this&#xff09;来实现&#xff0c;从而可以在同一行上依次调用多个方法。…

文件加密软件怎么选呢?五款人气超高的文件加密软件推荐给你

文件加密软件怎么选呢&#xff1f;以下是五款人气超高的文件加密软件&#xff0c;包括安企神在内&#xff0c;它们各自具有独特的功能和优势&#xff1a; 安企神 功能特点&#xff1a;安企神是一款功能强大的企业级文件加密软件&#xff0c;提供全方位的数据安全保护。它采用先…

TPAMI 2024|如何在动态世界中捕捉每一个细节?智能视觉识别的突破,开放长尾识别技术详解!

题目&#xff1a;Open Long-Tailed Recognition in a Dynamic World 动态世界中的开放长尾识别 作者&#xff1a;Ziwei Liu; Zhongqi Miao; Xiaohang Zhan; Jiayun Wang; Boqing Gong; Stella X. Yu 源码链接&#xff1a; https://liuziwei7.github.io/projects/LongTail.ht…

Java项目: 基于SpringBoot+mysql网上订餐系统分前后台(含源码+数据库+开题报告+PPT+毕业论文)

一、项目简介 本项目是一套基于SpringBootmysql网上订餐系统分前后台 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单…

Ollydbg提示:xxxxxx可能不是一个 32 位 PE 文件,无论如何都尝试载入吗?

原标题&#xff1a;OD提示C:\Users\XuanRan\Desktop\xxxx.exe’可能不是一个个 32 位 PE 文件,无论如何都尝试载入吗? 它的意思就是告诉你&#xff0c;OD现在只能用于32位软件。 如果要调试64位程序&#xff0c;去使用x64dbg x64dbg下载链接&#xff1a; https://github.com…

代码随想录算法训练营第32天|509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

目录 509. 斐波那契数1、题目描述2、思路3、code4、复杂度分析 70. 爬楼梯1、题目描述2、思路3、code 746. 使用最小花费爬楼梯1、题目描述2、思路3、code4、复杂度分析 509. 斐波那契数 题目链接&#xff1a;link 1、题目描述 斐波那契数 &#xff08;通常用 F(n) 表示&…

503错误

503 Service Temporarily Unavailable 我在学习ES-IK分词器时restart es后发现刷新网页报了503错误 后面发现是由于浏览器缓存或存储的Cookies引起的&#xff0c;需要清除缓存和Cookies 然后在游览器设置中找到 ​​ 然后刷新发现可以了

从初阶到顶尖:腾讯产品经理五级能力模型深度解读

产品经理在互联网企业中扮演着至关重要的角色&#xff0c;他们负责从需求分析到产品落地的整个生命周期。在腾讯的产品经理能力模型中&#xff0c;能力被分为五个等级&#xff08;Level 1 到 Level 5&#xff09;&#xff0c;每个等级代表了产品经理在通用能力、专业知识、专业…

【Python报错已解决】“ModuleNotFoundError: No module named ‘mne‘”

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言&#xff1a;一、问题描述1.1 报错示例&#xff1a;当我们尝试导入MNE-Python库时&#xff0c;可能会看到以下错误…

基础动销方案:开启稳健起步之路

在当今竞争激烈的商业世界中&#xff0c;如何让产品或服务实现有效动销&#xff0c;是企业生存与发展的关键所在。而基础动销方案&#xff0c;正是企业迈出稳健第一步的重要策略。 基础动销到底有多重要呢&#xff1f;它能在产品或服务推出初期&#xff0c;通过一系列营销手段达…

2025毕业季:如何用Java SpringBoot构建医疗就诊平台?掌握最新技术,开启医疗信息化大门

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

Windows 下载安装RabbitMQ

环境描述 windows10 Erlang 26.2.x 版本 RabbitMQ 3.13.7 因为RabbitMQ是Erlang语言开发的&#xff0c;所以必须安装 Erlang RabbitMQ官网链接: https://www.rabbitmq.com/docs/which-erlang 1.下载并安装Erlang 26.2.5 1.1下载Erlang 26.2.5 https://erlang.org/dow…