基于WPF实现时间选择控件
作者:WPF开发者
这篇文章主要介绍了如何基于WPF实现简单的时间选择控件,文中的示例代码讲解详细,对我们的学习或工作有一定帮助,需要的小伙伴可以参考一下
WPF 实现时间选择控件
- 框架使用
.NET4 至 .NET6
; Visual Studio 2022
;
实现代码
1)代码TimePicker.cs
如下:
TimePicker
控件依赖属性,SelectedTimeFormatProperty、MaxDropDownHeightProperty、SelectedTimeProperty
和IsCurrentTimeProperty
。- 在静态构造函数中,使用
DefaultStyleKeyProperty.OverrideMetadata
方法来指定控件的默认样式。 - 控件的模板部分定义了三个重要的部件:
PART_TimeSelector
(列表框用于选择时间)、PART_EditableTextBox
(可编辑的文本框)和PART_Popup
(弹出窗口)。 - 在应用模板时,我们获取并保存了这些模板部件的引用。并且订阅了时间选择器的
SelectedTimeChanged
事件,以及弹出窗口的Opened
事件。 - 当时间选择器的
SelectedTimeChanged
事件发生时,我们会更新文本框中的文本,并将选中的时间赋值给SelectedTime
属性。 - 当弹出窗口的
Opened
事件发生时,我们会调用时间选择器的SetTime
方法,以根据SelectedTime
的值更新列表框的选择项。
using System; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; namespace WPFDevelopers.Controls { [TemplatePart(Name = TimeSelectorTemplateName, Type = typeof(ListBox))] [TemplatePart(Name = EditableTextBoxTemplateName, Type = typeof(TextBox))] [TemplatePart(Name = PopupTemplateName, Type = typeof(Popup))] public class TimePicker : Control { private const string TimeSelectorTemplateName = "PART_TimeSelector"; private const string EditableTextBoxTemplateName = "PART_EditableTextBox"; private const string PopupTemplateName = "PART_Popup"; public static readonly DependencyProperty SelectedTimeFormatProperty = DependencyProperty.Register("SelectedTimeFormat", typeof(string), typeof(TimePicker), new PropertyMetadata("HH:mm:ss")); public static readonly DependencyProperty MaxDropDownHeightProperty = DependencyProperty.Register("MaxDropDownHeight", typeof(double), typeof(TimePicker), new UIPropertyMetadata(SystemParameters.PrimaryScreenHeight / 3.0, OnMaxDropDownHeightChanged)); public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register("SelectedTime", typeof(DateTime?), typeof(TimePicker), new PropertyMetadata(null, OnSelectedTimeChanged)); public static readonly DependencyProperty IsCurrentTimeProperty = DependencyProperty.Register("IsCurrentTime", typeof(bool), typeof(TimePicker), new PropertyMetadata(false)); private DateTime _date; private Popup _popup; private TextBox _textBox; private TimeSelector _timeSelector; static TimePicker() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TimePicker), new FrameworkPropertyMetadata(typeof(TimePicker))); } public string SelectedTimeFormat { get => (string) GetValue(SelectedTimeFormatProperty); set => SetValue(SelectedTimeFormatProperty, value); } public DateTime? SelectedTime { get => (DateTime?) GetValue(SelectedTimeProperty); set => SetValue(SelectedTimeProperty, value); } public bool IsCurrentTime { get => (bool) GetValue(IsCurrentTimeProperty); set => SetValue(IsCurrentTimeProperty, value); } private static void OnMaxDropDownHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctrl = d as TimePicker; if (ctrl != null) ctrl.OnMaxDropDownHeightChanged((double) e.OldValue, (double) e.NewValue); } protected virtual void OnMaxDropDownHeightChanged(double oldValue, double newValue) { } private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctrl = d as TimePicker; if (ctrl != null && e.NewValue != null) { var dateTime = (DateTime) e.NewValue; if (ctrl._timeSelector != null && dateTime > DateTime.MinValue) ctrl._timeSelector.SelectedTime = dateTime; else ctrl._date = dateTime; } } public override void OnApplyTemplate() { base.OnApplyTemplate(); _textBox = GetTemplateChild(EditableTextBoxTemplateName) as TextBox; _timeSelector = GetTemplateChild(TimeSelectorTemplateName) as TimeSelector; if (_timeSelector != null) { _timeSelector.SelectedTimeChanged -= TimeSelector_SelectedTimeChanged; _timeSelector.SelectedTimeChanged += TimeSelector_SelectedTimeChanged; if (!SelectedTime.HasValue && IsCurrentTime) { SelectedTime = DateTime.Now; } else { SelectedTime = null; SelectedTime = _date; } } _popup = GetTemplateChild(PopupTemplateName) as Popup; if (_popup != null) { _popup.Opened -= Popup_Opened; _popup.Opened += Popup_Opened; } } private void Popup_Opened(object sender, EventArgs e) { if (_timeSelector != null) { _timeSelector.SetTime(); } } private void TimeSelector_SelectedTimeChanged(object sender, RoutedPropertyChangedEventArgs<DateTime?> e) { if (_textBox != null && e.NewValue != null) { _textBox.Text = e.NewValue.Value.ToString(SelectedTimeFormat); SelectedTime = e.NewValue; } } } }
2)TimeSelector.cs
代码如下:
- 三个列表框,分别用于选择小时、分钟和秒。
- 代码中定义了一些依赖属性,例如
SelectedTimeProperty
用于获取或设置选中的时间,ItemHeightProperty
用于设置列表项的高度,SelectorMarginProperty
用于设置选择器的边距。还定义了一个SelectedTimeChangedEvent
事件,用于在选中时间发生变化时触发。 - 控件的模板部分包含了三个列表框,分别命名为
PART_ListBoxHour、PART_ListBoxMinute
和PART_ListBoxSecond
。在应用模板时,会将数据源绑定到列表框上,并为每个列表框添加SelectionChanged
事件的处理程序。 - 在列表框的
SelectionChanged
事件处理程序中,会根据选择的项更新_hour、_minute
和_second
等字段,并通过SetSelectedTime
方法设置SelectedTime
属性的值。 - 最后在
SetTime
方法中会根据SelectedTim
e的值来更新列表框的选择项,并调用SetSelectedTime
方法设置SelectedTime
属性的值。 - 小时集合设置为
_listBoxHour
的ItemsSource
。这个集合由上下四个空字符串、从0到23的小时数(格式为两位数)。
"" "" "" "00" "01" "02" ... "21" "22" "23" "" "" ""
using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; namespace WPFDevelopers.Controls { [TemplatePart(Name = ListBoxHourTemplateName, Type = typeof(ListBox))] [TemplatePart(Name = ListBoxMinuteTemplateName, Type = typeof(ListBox))] [TemplatePart(Name = ListBoxSecondTemplateName, Type = typeof(ListBox))] public class TimeSelector : Control { private const string ListBoxHourTemplateName = "PART_ListBoxHour"; private const string ListBoxMinuteTemplateName = "PART_ListBoxMinute"; private const string ListBoxSecondTemplateName = "PART_ListBoxSecond"; public static readonly RoutedEvent SelectedTimeChangedEvent = EventManager.RegisterRoutedEvent("SelectedTimeChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<DateTime?>), typeof(TimeSelector)); public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register("SelectedTime", typeof(DateTime?), typeof(TimeSelector), new PropertyMetadata(null, OnSelectedTimeChanged)); public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(TimeSelector), new PropertyMetadata(0d)); public static readonly DependencyProperty SelectorMarginProperty = DependencyProperty.Register("SelectorMargin", typeof(Thickness), typeof(TimeSelector), new PropertyMetadata(new Thickness(0))); private int _hour, _minute, _second; private ListBox _listBoxHour, _listBoxMinute, _listBoxSecond; static TimeSelector() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TimeSelector), new FrameworkPropertyMetadata(typeof(TimeSelector))); } public DateTime? SelectedTime { get => (DateTime?)GetValue(SelectedTimeProperty); set => SetValue(SelectedTimeProperty, value); } public double ItemHeight { get => (double)GetValue(ItemHeightProperty); set => SetValue(ItemHeightProperty, value); } public Thickness SelectorMargin { get => (Thickness)GetValue(SelectorMarginProperty); set => SetValue(SelectorMarginProperty, value); } public event RoutedPropertyChangedEventHandler<DateTime?> SelectedTimeChanged { add => AddHandler(SelectedTimeChangedEvent, value); remove => RemoveHandler(SelectedTimeChangedEvent, value); } public virtual void OnSelectedTimeChanged(DateTime? oldValue, DateTime? newValue) { var args = new RoutedPropertyChangedEventArgs<DateTime?>(oldValue, newValue, SelectedTimeChangedEvent); RaiseEvent(args); } private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctrl = d as TimeSelector; if (ctrl != null) ctrl.OnSelectedTimeChanged((DateTime?)e.OldValue, (DateTime?)e.NewValue); } private double GetItemHeight(ListBox listBox) { if (listBox.Items.Count > 0) { var listBoxItem = listBox.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem; if (listBoxItem != null) return listBoxItem.ActualHeight; } return 0; } private int GetFirstNonEmptyItemIndex(ListBox listBox) { for (var i = 0; i < listBox.Items.Count; i++) { var item = listBox.Items[i] as string; if (!string.IsNullOrWhiteSpace(item)) return i; } return -1; } public override void OnApplyTemplate() { base.OnApplyTemplate(); var minuteSecondList = Enumerable.Range(0, 60).Select(num => num.ToString("D2")); var emptyData = Enumerable.Repeat(string.Empty, 4); var result = emptyData.Concat(minuteSecondList).Concat(emptyData); _listBoxHour = GetTemplateChild(ListBoxHourTemplateName) as ListBox; if (_listBoxHour != null) { var hours = Enumerable.Range(0, 24).Select(num => num.ToString("D2")); _listBoxHour.SelectionChanged -= ListBoxHour_SelectionChanged; _listBoxHour.SelectionChanged += ListBoxHour_SelectionChanged; _listBoxHour.ItemsSource = emptyData.Concat(hours).Concat(emptyData); _listBoxHour.Loaded += (sender, args) => { var h = GetItemHeight(_listBoxHour); if (h <= 0) return; ItemHeight = h; Height = h * 10; var YAxis = GetFirstNonEmptyItemIndex(_listBoxHour) * h; SelectorMargin = new Thickness(0, YAxis, 0, 0); }; } _listBoxMinute = GetTemplateChild(ListBoxMinuteTemplateName) as ListBox; if (_listBoxMinute != null) { _listBoxMinute.SelectionChanged -= ListBoxMinute_SelectionChanged; _listBoxMinute.SelectionChanged += ListBoxMinute_SelectionChanged; _listBoxMinute.ItemsSource = result; } _listBoxSecond = GetTemplateChild(ListBoxSecondTemplateName) as ListBox; if (_listBoxSecond != null) { _listBoxSecond.SelectionChanged -= ListBoxSecond_SelectionChanged; _listBoxSecond.SelectionChanged += ListBoxSecond_SelectionChanged; _listBoxSecond.ItemsSource = result; } } private void ListBoxSecond_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (string.IsNullOrWhiteSpace(_listBoxSecond.SelectedValue.ToString())) return; _second = Convert.ToInt32(_listBoxSecond.SelectedValue.ToString()); SetSelectedTime(); } private void ListBoxMinute_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (string.IsNullOrWhiteSpace(_listBoxMinute.SelectedValue.ToString())) return; _minute = Convert.ToInt32(_listBoxMinute.SelectedValue.ToString()); SetSelectedTime(); } private void ListBoxHour_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (string.IsNullOrWhiteSpace(_listBoxHour.SelectedValue.ToString())) return; _hour = Convert.ToInt32(_listBoxHour.SelectedValue.ToString()); SetSelectedTime(); } private void SetSelectedTime() { var dt = new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day, _hour, _minute, _second); SelectedTime = dt; } public void SetTime() { if (!SelectedTime.HasValue) return; _hour = SelectedTime.Value.Hour; _minute = SelectedTime.Value.Minute; _second = SelectedTime.Value.Second; _listBoxHour.SelectionChanged -= ListBoxHour_SelectionChanged; var hour = _hour.ToString("D2"); _listBoxHour.SelectedItem = hour; //(_listBoxHour as TimeSelectorListBox).Positioning(); _listBoxHour.SelectionChanged += ListBoxHour_SelectionChanged; _listBoxMinute.SelectionChanged -= ListBoxMinute_SelectionChanged; var minute = _minute.ToString("D2"); _listBoxMinute.SelectedItem = minute; //(_listBoxMinute as TimeSelectorListBox).Positioning(); _listBoxMinute.SelectionChanged += ListBoxMinute_SelectionChanged; _listBoxSecond.SelectionChanged -= ListBoxSecond_SelectionChanged; var second = _second.ToString("D2"); _listBoxSecond.SelectedItem = second; //(_listBoxSecond as TimeSelectorListBox).Positioning(); _listBoxSecond.SelectionChanged += ListBoxSecond_SelectionChanged; SetSelectedTime(); } } }
3)代码TimeSelectorListBox.cs
如下:
TimeSelectorListBox
是一个自定义的ListBox
控件,继承自ListBox
类。scrollViewer
是一个ScrollViewer
对象,用于滚动 ListBox 中的项。lastIndex
用于存储上次滚动时ListBox
中选中项的索引位置。isFirst
用于判断是否为第一次滚动。IsItemItsOwnContainerOverride
方法重写了ListBox
的方法,用于指定 ListBox 的项是否为指定的容器类型(TimeSelectorItem
)。GetContainerForItemOverride
方法重写了ListBox
的方法,用于创建 ListBox 的项所使用的容器(TimeSelectorItem
)。- 构造函数
TimeSelectorListBox
对控件进行初始化,设置Loaded
和PreviewMouseWheel
事件的处理一次滚动一条内容。 TimeSelectorListBox_Loaded
方法用于在控件加载完成后执行一些初始化操作,如获取ScrollViewer
对象并监听其ScrollChanged
事件。ScrollListBox_PreviewMouseWheel
方法在鼠标滚轮滚动时处理滚动逻辑,根据滚动方向和选择项的位置来调整ListBox
的选中项,并防止滚动穿透。ScrollViewer_ScrollChanged
方法在ScrollViewer
的滚动位置发生变化时更新lastIndex
的值。OnSelectionChanged
方法重写了ListBox
的方法,在选中项发生改变时执行自定义操作,通过计算选中项与上次滚动位置之间的索引差来调整ScrollViewer
的滚动位置。FindVisualChild
方法用递归的方式在Visual
树中查找指定类型的子元素,并返回找到的第一个匹配项。
using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using WPFDevelopers.Helpers; namespace WPFDevelopers.Controls { public class TimeSelectorListBox : ListBox { private bool isFirst = true; private double lastIndex = 4; private ScrollViewer scrollViewer; public TimeSelectorListBox() { Loaded += TimeSelectorListBox_Loaded; PreviewMouseWheel -= ScrollListBox_PreviewMouseWheel; PreviewMouseWheel += ScrollListBox_PreviewMouseWheel; } protected override bool IsItemItsOwnContainerOverride(object item) { return item is TimeSelectorItem; } protected override DependencyObject GetContainerForItemOverride() { return new TimeSelectorItem(); } private void ScrollListBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { if (Items != null && Items.Count > 0) { var delta = e.Delta; var scrollCount = delta > 0 ? -1 : 1; ItemPositioning(scrollCount); e.Handled = true; } } void ItemPositioning(int scrollCount) { var itemCount = Items.Count; var newIndex = SelectedIndex + scrollCount; if (newIndex < 4) newIndex = 5; else if (newIndex >= itemCount - 4) newIndex = itemCount; SelectedIndex = newIndex; } void Positioning() { if (SelectedIndex <= 0 || scrollViewer == null) return; var index = SelectedIndex - (int)lastIndex; var offset = scrollViewer.VerticalOffset + index; scrollViewer.ScrollToVerticalOffset(offset); } private void TimeSelectorListBox_Loaded(object sender, RoutedEventArgs e) { scrollViewer = ControlsHelper.FindVisualChild<ScrollViewer>(this); if (scrollViewer != null) { scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged; scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; } } private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { var offset = e.VerticalOffset; if (isFirst == false) lastIndex = offset + 4; else { lastIndex = offset == 0 ? 4 : offset + 4; isFirst = false; } } protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); if (SelectedIndex != -1 && lastIndex != -1) { if (SelectedIndex <= 0) return; Positioning(); } } } }
4)代码TimePicker.xaml
如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:WPFDevelopers.Controls" xmlns:helpers="clr-namespace:WPFDevelopers.Helpers"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Basic/ControlBasic.xaml" /> </ResourceDictionary.MergedDictionaries> <ControlTemplate x:Key="WD.TimePickerToggleButton" TargetType="{x:Type ToggleButton}"> <Border x:Name="PART_Border" Padding="6,0" Background="Transparent" BorderThickness="0" SnapsToDevicePixels="true"> <controls:PathIcon x:Name="PART_PathIcon" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource WD.PlaceholderTextSolidColorBrush}" IsHitTestVisible="False" Kind="Time" /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="PART_PathIcon" Property="Foreground" Value="{DynamicResource WD.PrimaryNormalSolidColorBrush}" /> </Trigger> <Trigger Property="IsChecked" Value="True"> <Setter TargetName="PART_PathIcon" Property="Foreground" Value="{DynamicResource WD.PrimaryNormalSolidColorBrush}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> <Style x:Key="WD.TimeSelectorItem" BasedOn="{StaticResource WD.DefaultListBoxItem}" TargetType="{x:Type controls:TimeSelectorItem}"> <Setter Property="BorderThickness" Value="0" /> <Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type controls:TimeSelectorItem}"> <ControlTemplate.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="Background" Value="Transparent" /> </Trigger> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="{DynamicResource WD.BaseSolidColorBrush}" /> </Trigger> <DataTrigger Binding="{Binding}" Value=""> <Setter Property="IsEnabled" Value="False" /> </DataTrigger> </ControlTemplate.Triggers> <controls:SmallPanel> <Border Name="PART_Border" Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True"> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> </Border> </controls:SmallPanel> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="WD.TimeListStyle" BasedOn="{StaticResource WD.DefaultListBox}" TargetType="{x:Type controls:TimeSelectorListBox}"> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" /> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="False" /> <Setter Property="ItemContainerStyle" Value="{StaticResource WD.TimeSelectorItem}" /> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" /> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" /> </Trigger> </Style.Triggers> </Style> <Style x:Key="WD.TimeSelector" BasedOn="{StaticResource WD.ControlBasicStyle}" TargetType="{x:Type controls:TimeSelector}"> <Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="Padding" Value="{StaticResource WD.DefaultPadding}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type controls:TimeSelector}"> <Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" UseLayoutRounding="{TemplateBinding UseLayoutRounding}"> <controls:SmallPanel SnapsToDevicePixels="True"> <UniformGrid Rows="1"> <controls:TimeSelectorListBox x:Name="PART_ListBoxHour" Style="{StaticResource WD.TimeListStyle}" /> <controls:TimeSelectorListBox x:Name="PART_ListBoxMinute" Style="{StaticResource WD.TimeListStyle}" /> <controls:TimeSelectorListBox x:Name="PART_ListBoxSecond" Style="{StaticResource WD.TimeListStyle}" /> </UniformGrid> <Line /> <Path /> <Border Height="{TemplateBinding ItemHeight}" Margin="{TemplateBinding SelectorMargin}" VerticalAlignment="Top" BorderBrush="{DynamicResource WD.BaseSolidColorBrush}" BorderThickness="0,1" IsHitTestVisible="False" /> </controls:SmallPanel> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="WD.TimePicker" BasedOn="{StaticResource WD.ControlBasicStyle}" TargetType="{x:Type controls:TimePicker}"> <Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="BorderBrush" Value="{DynamicResource WD.BaseSolidColorBrush}" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="Background" Value="{DynamicResource WD.BackgroundSolidColorBrush}" /> <Setter Property="Padding" Value="{StaticResource WD.DefaultPadding}" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type controls:TimePicker}"> <ControlTemplate.Resources> <Storyboard x:Key="OpenStoryboard"> <DoubleAnimation EasingFunction="{StaticResource WD.ExponentialEaseOut}" Storyboard.TargetName="PART_DropDown" Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)" To="1" Duration="00:00:.2" /> </Storyboard> <Storyboard x:Key="CloseStoryboard"> <DoubleAnimation EasingFunction="{StaticResource WD.ExponentialEaseOut}" Storyboard.TargetName="PART_DropDown" Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)" To="0" Duration="00:00:.2" /> </Storyboard> </ControlTemplate.Resources> <controls:SmallPanel SnapsToDevicePixels="True"> <Grid Background="Transparent"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Border Name="PART_Border" Grid.ColumnSpan="2" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}" SnapsToDevicePixels="True" /> <TextBox x:Name="PART_EditableTextBox" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Background="{TemplateBinding Background}" Focusable="True" Foreground="{DynamicResource WD.PrimaryTextSolidColorBrush}" SelectionBrush="{DynamicResource WD.WindowBorderBrushSolidColorBrush}" Style="{x:Null}" Template="{StaticResource WD.ComboBoxTextBox}" /> <TextBlock x:Name="PART_Watermark" Margin="{TemplateBinding Padding}" Padding="1,0" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Background="Transparent" FontSize="{StaticResource WD.NormalFontSize}" Foreground="{DynamicResource WD.RegularTextSolidColorBrush}" IsHitTestVisible="False" Text="{Binding Path=(helpers:ElementHelper.Watermark), RelativeSource={RelativeSource TemplatedParent}}" TextTrimming="CharacterEllipsis" Visibility="Collapsed" /> <ToggleButton x:Name="PART_ToggleButton" Grid.Column="1" Background="{TemplateBinding Background}" ClickMode="Release" Focusable="False" Style="{x:Null}" Template="{StaticResource WD.TimePickerToggleButton}" /> <Popup x:Name="PART_Popup" AllowsTransparency="True" IsOpen="{Binding Path=IsChecked, ElementName=PART_ToggleButton}" Placement="Bottom" PlacementTarget="{Binding ElementName=PART_Border}" StaysOpen="False"> <controls:SmallPanel x:Name="PART_DropDown" MinWidth="{TemplateBinding FrameworkElement.ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}" Margin="24,2,24,24" RenderTransformOrigin=".5,0" SnapsToDevicePixels="True"> <controls:SmallPanel.RenderTransform> <ScaleTransform ScaleY="0" /> </controls:SmallPanel.RenderTransform> <Border Name="PART_DropDownBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}" Effect="{StaticResource WD.PopupShadowDepth}" SnapsToDevicePixels="True" UseLayoutRounding="True" /> <controls:TimeSelector x:Name="PART_TimeSelector" /> </controls:SmallPanel> </Popup> </Grid> </controls:SmallPanel> <ControlTemplate.Triggers> <Trigger SourceName="PART_ToggleButton" Property="IsChecked" Value="True"> <Trigger.EnterActions> <BeginStoryboard x:Name="BeginStoryboardOpenStoryboard" Storyboard="{StaticResource OpenStoryboard}" /> </Trigger.EnterActions> <Trigger.ExitActions> <StopStoryboard BeginStoryboardName="BeginStoryboardOpenStoryboard" /> </Trigger.ExitActions> </Trigger> <Trigger SourceName="PART_ToggleButton" Property="IsChecked" Value="False"> <Trigger.EnterActions> <BeginStoryboard x:Name="BeginStoryboardCloseStoryboard" Storyboard="{StaticResource CloseStoryboard}" /> </Trigger.EnterActions> <Trigger.ExitActions> <StopStoryboard BeginStoryboardName="BeginStoryboardCloseStoryboard" /> </Trigger.ExitActions> </Trigger> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="PART_Border" Property="BorderBrush" Value="{DynamicResource WD.PrimaryNormalSolidColorBrush}" /> </Trigger> <Trigger SourceName="PART_Popup" Property="AllowsTransparency" Value="True"> <Setter TargetName="PART_DropDownBorder" Property="Margin" Value="0,2,0,0" /> </Trigger> <Trigger Property="SelectedTime" Value=""> <Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible" /> </Trigger> <Trigger Property="SelectedTime" Value="{x:Null}"> <Setter TargetName="PART_Watermark" Property="Visibility" Value="Visible" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style BasedOn="{StaticResource WD.TimeSelector}" TargetType="{x:Type controls:TimeSelector}" /> <Style BasedOn="{StaticResource WD.TimePicker}" TargetType="{x:Type controls:TimePicker}" /> </ResourceDictionary>
5)示例代码TimePickerExample.xaml
如下:
<UniformGrid> <wd:TimePicker Width="200" VerticalAlignment="Center" wd:ElementHelper.Watermark="请选择任意时间" /> <wd:TimePicker Width="200" VerticalAlignment="Center" IsCurrentTime="True" /> <wd:TimePicker Width="200" VerticalAlignment="Center" SelectedTime="2023-05-06 23:59:59" /> </UniformGrid>
效果图
以上就是基于WPF实现时间选择控件的详细内容,更多关于WPF时间选择控件的资料请关注脚本之家其它相关文章!