让 WPF 的 RadioButton 支持再次点击取消选中的功能
讓 WPF 的 RadioButton 支持再次點(diǎn)擊取消選中的功能
目錄
讓 WPF 的 RadioButton 支持再次點(diǎn)擊取消選中的功能
零、前言
一、方法一:后臺(tái)直接處理
二、方法二:提取為自定義控件(用戶控件)
三、方法三:附加行為法
獨(dú)立觀察員 2022 年 01 月 16 日
零、前言
眾所周知,RadioButton?是一種單選框,一般是放置好幾個(gè)在同一面板中以組成一組;使用時(shí),初始時(shí)可能一個(gè)都沒(méi)被選中,或者是設(shè)置了一個(gè)默認(rèn)選中項(xiàng);然后,用戶可以在這一組單選框中切換選擇其中一個(gè),不能多選,也不能取消選中(也就是不能重新回到一個(gè)都沒(méi)選的狀態(tài))。
最近公司軟件中有個(gè)界面,UI 給出的樣式就是單選框的形式,所以就使用了一組?RadioButton?來(lái)實(shí)現(xiàn),初始是一個(gè)都沒(méi)選,之后用戶可以在其中選擇一項(xiàng)??墒呛髞?lái)需求說(shuō)選中的項(xiàng)再次點(diǎn)擊需要取消選中,摔!這個(gè)功能 RadioButton 是辦不到的,CheckBox 是可以的,不過(guò)如果換成 CheckBox,一方面樣式要改,另一方面,只能選擇一項(xiàng)這個(gè)需求也要寫(xiě)代碼實(shí)現(xiàn)(CheckBox 好像可以設(shè)置為單選?算了,不要在意這些細(xì)節(jié)),所以還是找找方法,看能不能讓 RadioButton 支持取消選中吧。
一、方法一:后臺(tái)直接處理
網(wǎng)上找到的方法就是在后臺(tái)新增一個(gè) bool 變量,用來(lái)記錄上次(或者說(shuō)點(diǎn)擊前)RadioButton 是選中還是未選中,然后在點(diǎn)擊事件中進(jìn)行判斷處理:
來(lái)看看效果吧(動(dòng)圖):
上面的動(dòng)圖先演示了 RadioButton 默認(rèn)是不支持取消選中的;然后演示了通過(guò)上面代碼實(shí)現(xiàn)的支持取消選中的 RadioButton。
這樣確實(shí)是可以的,但是只適用于只有單個(gè) RadioButton 的情況,因?yàn)槿绻泻脦讉€(gè) RadioButton,那么就要為每個(gè) RadioButton 新建一個(gè)布爾變量以及一個(gè)點(diǎn)擊事件方法,最多是把事件方法整合一下,總之是很奇怪的。
當(dāng)然,這個(gè)戰(zhàn)略(引入一個(gè)布爾變量來(lái)記錄上次的選擇情況)是沒(méi)問(wèn)題,只不過(guò)戰(zhàn)術(shù)(直接在后臺(tái)處理)有點(diǎn)問(wèn)題。那么我們使用這個(gè)戰(zhàn)略的話,還能形成什么戰(zhàn)術(shù)呢?大致可以想到兩種方法,接下來(lái)容我一一道來(lái)。
二、方法二:提取為自定義控件(用戶控件)
我們新建一個(gè)名為 RadioButtonUncheck 的用戶控件(UserControl),將繼承關(guān)系改為 RadioButton,并把上一節(jié)所示的處理邏輯添加進(jìn)去:
前臺(tái)直接改為實(shí)例化一個(gè) RadioButton 即可:
然后在界面上使用這個(gè)用戶控件:
看看效果(動(dòng)圖):
很明顯,有一些 Bug,這是為什么呢?原因就是,我們新建的那個(gè)用來(lái)記錄上次選中狀態(tài)的變量,在用戶選中其它項(xiàng),同時(shí)?WPF?框架自動(dòng)取消選中本項(xiàng)時(shí),沒(méi)有進(jìn)行記錄。
所以我們需要在 Checked 和 Unchecked 這兩個(gè)事件中分別對(duì)?_lastChecked 進(jìn)行相應(yīng)的賦值:
然后,由于觸發(fā)了 Click 事件后(也有可能是 PreviewMouseDown 后 Click 前的某個(gè)事件,比如 PreviewMouseUp),WPF 框架(或者說(shuō)是 RadioButton 內(nèi)部)就會(huì)把 IsChecked 設(shè)為 true(這就是前面的代碼中需要另外新建變量來(lái)判斷的原因),所以需要換為 PreviewMouseDown 事件,并在處理完成后調(diào)用 “e.Handled = true;” 阻止事件繼續(xù)傳遞:
現(xiàn)在,當(dāng) RadioButtonUncheck 控件通過(guò)點(diǎn)擊由未選切換為選中時(shí),事件執(zhí)行順序?yàn)?PreviewMouseDown--Checked:
或:
而由選中切換為未選時(shí),事件執(zhí)行順序?yàn)?PreviewMouseDown--Unchecked:
而如果沒(méi)有 “e.Handled = true;”,則由未選切換為選中時(shí),事件執(zhí)行順序如下:
或:
由選中切換為未選時(shí)(切換失敗),事件執(zhí)行順序如下:
至此,用戶控件法圓滿完成任務(wù)(動(dòng)圖):
完整代碼:
using System; using System.Windows; using System.Windows.Controls;namespace WPFPractice.UserControls {/// <summary>/// 支持點(diǎn)擊取消選中的 RadioButton;/// </summary>public partial class RadioButtonUncheck : RadioButton{/// <summary>/// 上次的選中狀態(tài)/// </summary>private bool _lastChecked;/// <summary>/// 內(nèi)容字符串/// </summary>private string ContentStr => Content + "";public RadioButtonUncheck(){InitializeComponent();Click += RadioButtonUncheck_Click; ;PreviewMouseDown += RadioButtonUncheck_PreviewMouseDown; ;Checked += RadioButtonUncheck_Checked;Unchecked += RadioButtonUncheck_Unchecked;}/// <summary>/// 點(diǎn)擊事件處理方法/// </summary>private void RadioButtonUncheck_Click(object sender, RoutedEventArgs e){Console.WriteLine($"[{ContentStr}] 觸發(fā) Click 事件 ");//SwitchStatus();}/// <summary>/// 鼠標(biāo)按下事件處理方法/// </summary>private void RadioButtonUncheck_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e){Console.WriteLine($"[{ContentStr}] 觸發(fā) PreviewMouseDown 事件 ");SwitchStatus();e.Handled = true;}/// <summary>/// 切換狀態(tài)/// </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}] 觸發(fā) Checked 事件 ");_lastChecked = true;}/// <summary>/// 取消選中事件 處理方法/// </summary>private void RadioButtonUncheck_Unchecked(object sender, RoutedEventArgs e){Console.WriteLine($"[{ContentStr}] 觸發(fā) Unchecked 事件 ");_lastChecked = false;}} }三、方法三:附加行為法
關(guān)于附加行為,是通過(guò)附加屬性來(lái)實(shí)現(xiàn)的,可以參考我之前的翻譯文章《【翻譯】WPF 中附加行為的介紹 Introduction to Attached Behaviors in WPF》:
在一個(gè)元素上設(shè)置一個(gè)附加屬性,那么你就可以從暴露這個(gè)附加屬性的類(lèi)中獲得該元素的訪問(wèn)。一旦那個(gè)類(lèi)有權(quán)限訪問(wèn)那個(gè)元素,它就能在其上掛鉤事件,響應(yīng)這些事件的觸發(fā),使該元素做出它本來(lái)不會(huì)做的事情。
下面直接進(jìn)入正題,首先在一個(gè)新建類(lèi) RadioButtonAttached 中添加一個(gè) bool 類(lèi)型的附加屬性 IsCanUncheck,當(dāng)其被設(shè)置為 true 時(shí),會(huì)給設(shè)置的元素附加 PreviewMouseDown、Checked、Unchecked 三個(gè)事件,和上一節(jié)一樣:
注意,附加屬性還需要兩個(gè)包裝方法:
由于附加屬性的變動(dòng)處理方法要求是靜態(tài)方法:
所以導(dǎo)致三個(gè)事件的處理方法也要是靜態(tài)方法,不然就會(huì)報(bào)錯(cuò):
進(jìn)而導(dǎo)致之前引入成員變量?_lastChecked 的方式行不通了:
所以這個(gè)狀態(tài)存儲(chǔ)的地方需要另外尋找。對(duì)于這種情況,我經(jīng)常使用的是元素的 Tag 屬性,這次也是這樣干的,也就是說(shuō)使用單選框的 Tag 來(lái)存儲(chǔ)上次的選中與否狀態(tài)。
Checked 和 Unchecked 中還是換湯不換藥:
主要是 PreviewMouseDown 事件處理方法中,當(dāng)?shù)谝淮吸c(diǎn)擊,Tag 中還沒(méi)有存儲(chǔ)時(shí),bool 會(huì)轉(zhuǎn)換失敗,所以 Tag 中應(yīng)該存儲(chǔ) true 供下次使用;而轉(zhuǎn)換成功則將轉(zhuǎn)換出的值(存在 lastChecked 變量中)取反存入 Tag 中供下次使用。(這樣看來(lái)兩種情況好像都可以直接使用 rb.Tag = !lastChecked; 哈哈,懶得改了)。之后就是依據(jù) lastChecked 來(lái)決定(取反)IsChecked 的值:
完整代碼:
using System.Windows; using System.Windows.Controls;namespace WPFTemplateLib.Attached;/// <summary> /// RadioButton 附加屬性類(lèi) /// </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>/// 是否能取消選中 (啟用此功能會(huì)占用 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 來(lái)存儲(chǔ)上次選中的狀態(tài),之后可以從中獲取來(lái)進(jìn)行判斷;bool parseSuccess = bool.TryParse(rb.Tag + "", out bool lastChecked);if (!parseSuccess){// 轉(zhuǎn)換失敗,說(shuō)明是第一次點(diǎn)擊,也就是本次本勾選了,所以應(yīng)該把 true 存起來(lái);rb.Tag = true;}else{rb.Tag = !lastChecked;}if (lastChecked){rb.IsChecked = false;//lastChecked = false;}else{rb.IsChecked = true;//lastChecked = true;}e.Handled = true;}#endregion }使用時(shí)只需要在普通 RadioButton 元素上加上這個(gè)附加屬性并將值置為 True 即可:
效果和上一節(jié)的一樣(實(shí)際上方法三是先寫(xiě)成的),就不再演示了,來(lái)個(gè)全家福吧:
最后是源碼地址:https://gitee.com/dlgcy/DLGCY_WPFPractice/tree/Blog20220116?
WPF
WPF DataGrid 如何將被選中行帶到視野中
WPF 觸屏事件后觸發(fā)鼠標(biāo)事件的問(wèn)題及 DataGrid 誤觸問(wèn)題
WPF DataGrid 通過(guò)自定義表頭模擬首行固定
WPF ComboBox 使用 ResourceBinding 動(dòng)態(tài)綁定資源鍵并支持語(yǔ)言切換
【翻譯】WPF 中附加行為的介紹 Introduction to Attached Behaviors in WPF
WPF 使用 Expression Design 畫(huà)圖導(dǎo)出及使用 Path 畫(huà)圖
WPF?MVVM?彈框之等待框
解決 WPF 綁定集合后數(shù)據(jù)變動(dòng)界面卻不更新的問(wèn)題(使用 ObservableCollection)
WPF?消息框?TextBox?綁定新數(shù)據(jù)時(shí)讓光標(biāo)和滾動(dòng)條跳到最下面
真?WPF?按鈕拖動(dòng)和調(diào)整大小
WPF?MVVM?模式下的彈窗
WPF?讓一組 Button 實(shí)現(xiàn)?RadioButton?的當(dāng)前樣式效果
WPF?原生綁定和命令功能使用指南
WPF?用戶控件的自定義依賴屬性在?MVVM?模式下的使用備忘
在WPF的MVVM模式中使用OCX組件
總結(jié)
以上是生活随笔為你收集整理的让 WPF 的 RadioButton 支持再次点击取消选中的功能的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .NET6之MiniAPI(八):日志
- 下一篇: ASP.NET Core Web API