C# WPF如何实现带多可点击列的时分秒选择ComboBox
WPF单ComboBox实现时分秒三列选择方案
原生WPF无需第三方控件即可实现,网上多数失效方案的核心问题是错误将独立ComboBox嵌入下拉面板,会触发焦点抢占导致下拉自动关闭,以下是可直接运行的实现:
第一步:定义时间数据实体
新建用于绑定选中值的实体类,重写ToString方法控制选中后的显示文本:
public class TimeSelectItem { public int Hour { get; set; } public int Minute { get; set; } public int Second { get; set; } public override string ToString() { return $"{Hour:D2}:{Minute:D2}:{Second:D2}"; } }
第二步:编写ComboBox控件模板
核心是重写ComboBox的下拉弹出面板,用三列ListBox分别承载时、分、秒选项,不要嵌入独立ComboBox控件:
<Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Loaded="Window_Loaded" Title="时间选择Demo" Height="200" Width="400"> <Grid Margin="20"> <ComboBox x:Name="TimeSelectComboBox" Width="120" HorizontalAlignment="Left"> <ComboBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding}" /> </DataTemplate> </ComboBox.ItemTemplate> <ComboBox.Template> <ControlTemplate TargetType="ComboBox"> <Grid> <ToggleButton x:Name="ToggleBtn" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Content="{Binding SelectedItem, RelativeSource={RelativeSource TemplatedParent}}" Width="{TemplateBinding Width}" Padding="4" BorderThickness="1" BorderBrush="#ccc"/> <Popup x:Name="Part_Popup" IsOpen="{Binding IsChecked, ElementName=ToggleBtn}" StaysOpen="False" AllowsTransparency="True" Placement="Bottom"> <Border Background="White" BorderBrush="#ccc" BorderThickness="1" Margin="0 2 0 0" Padding="4"> <Grid Width="180"> <Grid.ColumnDefinitions> <ColumnDefinition Width="60"/> <ColumnDefinition Width="60"/> <ColumnDefinition Width="60"/> </Grid.ColumnDefinitions> <ListBox x:Name="HourList" Grid.Column="0" BorderThickness="0"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Center"/> <EventSetter Event="PreviewMouseLeftButtonDown" Handler="TimeItem_Click"/> </Style> </ListBox.ItemContainerStyle> </ListBox> <ListBox x:Name="MinuteList" Grid.Column="1" BorderThickness="1 0" BorderBrush="#eee"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Center"/> <EventSetter Event="PreviewMouseLeftButtonDown" Handler="TimeItem_Click"/> </Style> </ListBox.ItemContainerStyle> </ListBox> <ListBox x:Name="SecondList" Grid.Column="2" BorderThickness="0"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="HorizontalContentAlignment" Value="Center"/> <EventSetter Event="PreviewMouseLeftButtonDown" Handler="TimeItem_Click"/> </Style> </ListBox.ItemContainerStyle> </ListBox> </Grid> </Border> </Popup> </Grid> </ControlTemplate> </ComboBox.Template> </ComboBox> </Grid> </Window>
第三步:编写后台逻辑
初始化可选值、处理列项点击事件,注意要从模板中找到三列对应的ListBox控件:
public partial class MainWindow : Window { private ListBox HourList, MinuteList, SecondList; public MainWindow() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { // 从控件模板中获取三列选择列表 HourList = TimeSelectComboBox.Template.FindName("HourList", TimeSelectComboBox) as ListBox; MinuteList = TimeSelectComboBox.Template.FindName("MinuteList", TimeSelectComboBox) as ListBox; SecondList = TimeSelectComboBox.Template.FindName("SecondList", TimeSelectComboBox) as ListBox; // 初始化时、分、秒可选范围 for (int i = 0; i < 24; i++) HourList.Items.Add($"{i:D2}"); for (int i = 0; i < 60; i++) { MinuteList.Items.Add($"{i:D2}"); SecondList.Items.Add($"{i:D2}"); } // 设置默认选中值 00:00:00 var defaultTime = new TimeSelectItem { Hour = 0, Minute = 0, Second = 0 }; TimeSelectComboBox.SelectedItem = defaultTime; HourList.SelectedItem = "00"; MinuteList.SelectedItem = "00"; SecondList.SelectedItem = "00"; } private void TimeItem_Click(object sender, MouseButtonEventArgs e) { // 阻止ListBox默认选中逻辑冒泡 e.Handled = true; var clickedValue = (sender as ListBoxItem).Content.ToString(); var parentList = ItemsControl.ItemsControlFromItemContainer(sender as ListBoxItem) as ListBox; var currentTime = TimeSelectComboBox.SelectedItem as TimeSelectItem ?? new TimeSelectItem(); // 根据点击的列更新对应时间字段 if (parentList.Name == nameof(HourList)) { currentTime.Hour = int.Parse(clickedValue); HourList.SelectedItem = clickedValue; } else if (parentList.Name == nameof(MinuteList)) { currentTime.Minute = int.Parse(clickedValue); MinuteList.SelectedItem = clickedValue; } else if (parentList.Name == nameof(SecondList)) { currentTime.Second = int.Parse(clickedValue); SecondList.SelectedItem = clickedValue; } // 刷新ComboBox显示值 TimeSelectComboBox.SelectedItem = currentTime; } }
避坑说明
- 不要在下拉Popup中嵌入独立ComboBox控件:控件获取焦点时会触发Popup的自动关闭逻辑,无法完成连续选择
- 点击列项时必须将
PreviewMouseLeftButtonDown事件标记为已处理,否则ListBox默认选中路由逻辑会干扰时间值更新 - 选中项的显示格式直接修改
TimeSelectItem.ToString()方法即可 - 对接MVVM模式时,直接将
TimeSelectComboBox.SelectedItem绑定到ViewModel对应属性,点击事件中更新绑定值即可,无需额外自定义依赖属性
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

