基于WPF實(shí)現(xiàn)時(shí)間選擇控件
WPF 實(shí)現(xiàn)時(shí)間選擇控件
- 框架使用
.NET4 至 .NET6
; Visual Studio 2022
;
實(shí)現(xiàn)代碼
1)代碼TimePicker.cs
如下:
TimePicker
控件依賴屬性,SelectedTimeFormatProperty、MaxDropDownHeightProperty、SelectedTimeProperty
和IsCurrentTimeProperty
。- 在靜態(tài)構(gòu)造函數(shù)中,使用
DefaultStyleKeyProperty.OverrideMetadata
方法來指定控件的默認(rèn)樣式。 - 控件的模板部分定義了三個(gè)重要的部件:
PART_TimeSelector
(列表框用于選擇時(shí)間)、PART_EditableTextBox
(可編輯的文本框)和PART_Popup
(彈出窗口)。 - 在應(yīng)用模板時(shí),我們獲取并保存了這些模板部件的引用。并且訂閱了時(shí)間選擇器的
SelectedTimeChanged
事件,以及彈出窗口的Opened
事件。 - 當(dāng)時(shí)間選擇器的
SelectedTimeChanged
事件發(fā)生時(shí),我們會(huì)更新文本框中的文本,并將選中的時(shí)間賦值給SelectedTime
屬性。 - 當(dāng)彈出窗口的
Opened
事件發(fā)生時(shí),我們會(huì)調(diào)用時(shí)間選擇器的SetTime
方法,以根據(jù)SelectedTime
的值更新列表框的選擇項(xiàng)。
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
代碼如下:
- 三個(gè)列表框,分別用于選擇小時(shí)、分鐘和秒。
- 代碼中定義了一些依賴屬性,例如
SelectedTimeProperty
用于獲取或設(shè)置選中的時(shí)間,ItemHeightProperty
用于設(shè)置列表項(xiàng)的高度,SelectorMarginProperty
用于設(shè)置選擇器的邊距。還定義了一個(gè)SelectedTimeChangedEvent
事件,用于在選中時(shí)間發(fā)生變化時(shí)觸發(fā)。 - 控件的模板部分包含了三個(gè)列表框,分別命名為
PART_ListBoxHour、PART_ListBoxMinute
和PART_ListBoxSecond
。在應(yīng)用模板時(shí),會(huì)將數(shù)據(jù)源綁定到列表框上,并為每個(gè)列表框添加SelectionChanged
事件的處理程序。 - 在列表框的
SelectionChanged
事件處理程序中,會(huì)根據(jù)選擇的項(xiàng)更新_hour、_minute
和_second
等字段,并通過SetSelectedTime
方法設(shè)置SelectedTime
屬性的值。 - 最后在
SetTime
方法中會(huì)根據(jù)SelectedTim
e的值來更新列表框的選擇項(xiàng),并調(diào)用SetSelectedTime
方法設(shè)置SelectedTime
屬性的值。 - 小時(shí)集合設(shè)置為
_listBoxHour
的ItemsSource
。這個(gè)集合由上下四個(gè)空字符串、從0到23的小時(shí)數(shù)(格式為兩位數(shù))。
"" "" "" "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
是一個(gè)自定義的ListBox
控件,繼承自ListBox
類。scrollViewer
是一個(gè)ScrollViewer
對(duì)象,用于滾動(dòng) ListBox 中的項(xiàng)。lastIndex
用于存儲(chǔ)上次滾動(dòng)時(shí)ListBox
中選中項(xiàng)的索引位置。isFirst
用于判斷是否為第一次滾動(dòng)。IsItemItsOwnContainerOverride
方法重寫了ListBox
的方法,用于指定 ListBox 的項(xiàng)是否為指定的容器類型(TimeSelectorItem
)。GetContainerForItemOverride
方法重寫了ListBox
的方法,用于創(chuàng)建 ListBox 的項(xiàng)所使用的容器(TimeSelectorItem
)。- 構(gòu)造函數(shù)
TimeSelectorListBox
對(duì)控件進(jìn)行初始化,設(shè)置Loaded
和PreviewMouseWheel
事件的處理一次滾動(dòng)一條內(nèi)容。 TimeSelectorListBox_Loaded
方法用于在控件加載完成后執(zhí)行一些初始化操作,如獲取ScrollViewer
對(duì)象并監(jiān)聽其ScrollChanged
事件。ScrollListBox_PreviewMouseWheel
方法在鼠標(biāo)滾輪滾動(dòng)時(shí)處理滾動(dòng)邏輯,根據(jù)滾動(dòng)方向和選擇項(xiàng)的位置來調(diào)整ListBox
的選中項(xiàng),并防止?jié)L動(dòng)穿透。ScrollViewer_ScrollChanged
方法在ScrollViewer
的滾動(dòng)位置發(fā)生變化時(shí)更新lastIndex
的值。OnSelectionChanged
方法重寫了ListBox
的方法,在選中項(xiàng)發(fā)生改變時(shí)執(zhí)行自定義操作,通過計(jì)算選中項(xiàng)與上次滾動(dòng)位置之間的索引差來調(diào)整ScrollViewer
的滾動(dòng)位置。FindVisualChild
方法用遞歸的方式在Visual
樹中查找指定類型的子元素,并返回找到的第一個(gè)匹配項(xiàng)。
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="請(qǐng)選擇任意時(shí)間" /> <wd:TimePicker Width="200" VerticalAlignment="Center" IsCurrentTime="True" /> <wd:TimePicker Width="200" VerticalAlignment="Center" SelectedTime="2023-05-06 23:59:59" /> </UniformGrid>
效果圖
以上就是基于WPF實(shí)現(xiàn)時(shí)間選擇控件的詳細(xì)內(nèi)容,更多關(guān)于WPF時(shí)間選擇控件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#創(chuàng)建一個(gè)可快速重復(fù)使用的項(xiàng)目模板(詳細(xì)過程)
這篇文章主要介紹了C#如何創(chuàng)建一個(gè)可快速重復(fù)使用的項(xiàng)目模板今天給大家介紹的是基于官方的cli donet new 命令創(chuàng)建自己的項(xiàng)目模板,需要的朋友可以參考下2024-06-06C#使用Socket發(fā)送和接收TCP數(shù)據(jù)實(shí)例
這篇文章主要介紹了C#使用Socket發(fā)送和接收TCP數(shù)據(jù)的實(shí)現(xiàn)方法,以實(shí)例的形式詳細(xì)講述了C#實(shí)現(xiàn)socket通信的完整實(shí)現(xiàn)過程,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10客戶端實(shí)現(xiàn)藍(lán)牙接收(C#)知識(shí)總結(jié)
網(wǎng)上有關(guān)藍(lán)牙接收的資料很多,使用起來也很簡單,但是我覺得還是有必要把這些知識(shí)總結(jié)下來,藍(lán)牙開發(fā)需要用到一個(gè)第三方的庫InTheHand.Net.Personal.dll,感興趣的朋友可以了解下,或許對(duì)你有所幫助2013-02-02C# Dynamic之:ExpandoObject,DynamicObject,DynamicMetaOb的應(yīng)用(上)
本篇文章對(duì)C#中ExpandoObject,DynamicObject,DynamicMetaOb的應(yīng)用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05利用C#實(shí)現(xiàn)將小數(shù)值四舍五入為整數(shù)
在項(xiàng)目的開發(fā)中,遇到一些除法計(jì)算內(nèi)容會(huì)產(chǎn)生小數(shù)值,但是又需要根據(jù)項(xiàng)目的實(shí)際情況將這些小數(shù)內(nèi)容化為整數(shù),所以本文為大家整理了C#實(shí)現(xiàn)將小數(shù)值四舍五入為整數(shù)的方法,希望對(duì)大家有所幫助2023-07-07