基于WPF實(shí)現(xiàn)時(shí)間選擇控件
WPF 實(shí)現(xiàn)時(shí)間選擇控件
- 框架使用
.NET4 至 .NET6; Visual Studio 2022;
實(shí)現(xiàn)代碼
1)代碼TimePicker.cs如下:
TimePicker控件依賴(lài)屬性,SelectedTimeFormatProperty、MaxDropDownHeightProperty、SelectedTimeProperty和IsCurrentTimeProperty。- 在靜態(tài)構(gòu)造函數(shù)中,使用
DefaultStyleKeyProperty.OverrideMetadata方法來(lái)指定控件的默認(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í)、分鐘和秒。
- 代碼中定義了一些依賴(lài)屬性,例如
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等字段,并通過(guò)SetSelectedTime方法設(shè)置SelectedTime屬性的值。 - 最后在
SetTime方法中會(huì)根據(jù)SelectedTime的值來(lái)更新列表框的選擇項(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類(lèi)。scrollViewer是一個(gè)ScrollViewer對(duì)象,用于滾動(dòng) ListBox 中的項(xiàng)。lastIndex用于存儲(chǔ)上次滾動(dòng)時(shí)ListBox中選中項(xiàng)的索引位置。isFirst用于判斷是否為第一次滾動(dòng)。IsItemItsOwnContainerOverride方法重寫(xiě)了ListBox的方法,用于指定 ListBox 的項(xiàng)是否為指定的容器類(lèi)型(TimeSelectorItem)。GetContainerForItemOverride方法重寫(xiě)了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)聽(tīng)其ScrollChanged事件。ScrollListBox_PreviewMouseWheel方法在鼠標(biāo)滾輪滾動(dòng)時(shí)處理滾動(dòng)邏輯,根據(jù)滾動(dòng)方向和選擇項(xiàng)的位置來(lái)調(diào)整ListBox的選中項(xiàng),并防止?jié)L動(dòng)穿透。ScrollViewer_ScrollChanged方法在ScrollViewer的滾動(dòng)位置發(fā)生變化時(shí)更新lastIndex的值。OnSelectionChanged方法重寫(xiě)了ListBox的方法,在選中項(xiàng)發(fā)生改變時(shí)執(zhí)行自定義操作,通過(guò)計(jì)算選中項(xiàng)與上次滾動(dòng)位置之間的索引差來(lái)調(diào)整ScrollViewer的滾動(dòng)位置。FindVisualChild方法用遞歸的方式在Visual樹(shù)中查找指定類(lèi)型的子元素,并返回找到的第一個(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ì)過(guò)程)
這篇文章主要介紹了C#如何創(chuàng)建一個(gè)可快速重復(fù)使用的項(xiàng)目模板今天給大家介紹的是基于官方的cli donet new 命令創(chuàng)建自己的項(xiàng)目模板,需要的朋友可以參考下2024-06-06
C#使用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)過(guò)程,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10
C# JsonHelper 操作輔助類(lèi),拿來(lái)直接用
本文總結(jié)了一些常用的JSON操作輔助類(lèi),包括轉(zhuǎn)換、判斷、Ajax異步等操作,希望能幫到大家。2016-05-05
客戶(hù)端實(shí)現(xiàn)藍(lán)牙接收(C#)知識(shí)總結(jié)
網(wǎng)上有關(guān)藍(lán)牙接收的資料很多,使用起來(lái)也很簡(jiǎn)單,但是我覺(jué)得還是有必要把這些知識(shí)總結(jié)下來(lái),藍(lán)牙開(kāi)發(fā)需要用到一個(gè)第三方的庫(kù)InTheHand.Net.Personal.dll,感興趣的朋友可以了解下,或許對(duì)你有所幫助2013-02-02
C# 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)目的開(kāi)發(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

