在閱讀本文前,最好先看看WPF——自定義RadioButton
背景
WPF中實現單選功能通常有兩種方案:
- RadioButton組:傳統方案,但代碼冗余
- ListBox定制:通過樣式改造,兼顧數據綁定和UI靈活性
需求
一組選項中,選中某個選項(選項需要橫向排列,同時選中效果與未選中效果要能明確顯示),就將這個選項的值寫入到后端。
設計選型
RadioButton方案
通過RadioButton來實現,是肯定可行的,
但是對于一組RadioButton,需要設置組名;
同時每個RadioButton在Checked時都要觸發事件,也需要為它們設置相應的事件,不利于后臺綁定:也就是說要將選中值寫入VM比較麻煩,因為所有的都需要實現;
并且還存在一個問題,所有RadioButton需要手動去設置相應的顯示內容,不能使用一個集合以便于管理。
ListBox方案
通過ListBox也是可行的,只是它就需要進行一些改造了,因為ListBox默認的是縱向排列,同時它沒有一個明確的選中與未選中效果;
但是它可以綁定集合,同時通過自定義,可以比較輕松的將選中值寫入到VM中;或者通過item的選中事件即可將選中值寫入到VM中。
方案對比
方案 | 代碼量 | 數據綁定 | 布局靈活性 | 維護成本 |
---|---|---|---|---|
RadioButton組 | 高 | 困難 | 低 | 高 |
ListBox定制 | 中 | 簡單 | 高 | 低 ? |
后續基于ListBox進行實現。
實現
樣式定義
<Style x:Key="RadioListBoxStyle" TargetType="{x:Type ListBox}"><!-- 基礎樣式繼承原有ListBoxStyle1 --><Setter Property="Background" Value="{StaticResource ListBox.Static.Background}" /><Setter Property="BorderBrush" Value="{StaticResource ListBox.Static.Border}" /><Setter Property="BorderThickness" Value="1" /><Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /><Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" /><Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" /><Setter Property="ScrollViewer.CanContentScroll" Value="true" /><Setter Property="ScrollViewer.PanningMode" Value="Both" /><Setter Property="Stylus.IsFlicksEnabled" Value="False" /><Setter Property="VerticalContentAlignment" Value="Center" /><!-- 關鍵修改1:禁用默認選中效果 --><Setter Property="ItemContainerStyle"><Setter.Value><Style TargetType="{x:Type ListBoxItem}"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type ListBoxItem}"><Border Background="Transparent"><ContentPresenter /></Border></ControlTemplate></Setter.Value></Setter></Style></Setter.Value></Setter><!-- ItemsPanel為水平布局 --><Setter Property="ItemsPanel"><Setter.Value><ItemsPanelTemplate> <!-- 改為水平排列 --><StackPanel Orientation="Horizontal" /></ItemsPanelTemplate></Setter.Value></Setter><!-- 自定義ItemTemplate模擬RadioButton --><Setter Property="ItemTemplate"><Setter.Value><DataTemplate><DockPanel LastChildFill="True"><!-- RadioButton部分 --><RadioButtonMargin="5,0,10,0"VerticalAlignment="Center"DockPanel.Dock="Left"Focusable="False"IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" /><!-- 文本部分 --><TextBlockMargin="0,0,5,0"VerticalAlignment="Center"Text="{Binding}" /></DockPanel></DataTemplate></Setter.Value></Setter><!-- 模板 --><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type ListBox}"><Borderx:Name="Bd"Padding="1"Background="{TemplateBinding Background}"BorderBrush="{TemplateBinding BorderBrush}"BorderThickness="{TemplateBinding BorderThickness}"SnapsToDevicePixels="true"><ScrollViewer Padding="{TemplateBinding Padding}" Focusable="false"><ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /></ScrollViewer></Border><ControlTemplate.Triggers><Trigger Property="IsEnabled" Value="false"><Setter TargetName="Bd" Property="Background" Value="{StaticResource ListBox.Disabled.Background}" /><Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource ListBox.Disabled.Border}" /></Trigger><MultiTrigger><MultiTrigger.Conditions><Condition Property="IsGrouping" Value="true" /><Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" /></MultiTrigger.Conditions><Setter Property="ScrollViewer.CanContentScroll" Value="false" /></MultiTrigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style>
數據綁定
<Window.DataContext><local:Tests /></Window.DataContext>
Tests如下,以下可以根據需要自行實現通知屬性。
public class Tests:INotifyPropertyChanged {readonly List<string> testDatas = ["test0","test1","test2","test3","test4",];private string _selectedValue;public string SelectedValue{get => _selectedValue;set { _selectedValue = value; OnPropertyChanged(); }}public List<string> TestDatas { get; set; }public Tests(){TestDatas = testDatas;}//INotifyPropertyChanged實現 }
ListBox如下:
<ListBoxMargin="0,183,462,192"ItemsSource="{Binding TestDatas}"SelectedItem="{Binding SelectedValue, Mode=TwoWay}"Style="{DynamicResource RadioListBoxStyle}" />
效果展示
圖:橫向排列的單選ListBox,左側為RadioButton,右側為文本