Files
qhmes/yy-admin-master/YY.Admin/Controls/DateTimeListPicker.xaml.cs

273 lines
9.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace YY.Admin.Controls;
/// <summary>
/// 自定义日期时间选择器。
/// 弹出层布局:左侧 <see cref="Calendar"/> 日历 + 右侧时/分/秒 三列 <see cref="ListBox"/>
/// 底部「此刻 / 确定」按钮,用于替代 HandyControl 默认的「圆盘表盘 + 上午下午」风格 DateTimePicker。
/// </summary>
public partial class DateTimeListPicker : UserControl
{
public DateTimeListPicker()
{
InitializeComponent();
Loaded += (_, _) => SyncDisplay();
}
#region
/// <summary>选择的日期时间,双向绑定。</summary>
public static readonly DependencyProperty SelectedDateTimeProperty =
DependencyProperty.Register(
nameof(SelectedDateTime), typeof(DateTime?), typeof(DateTimeListPicker),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnSelectedDateTimeChanged));
public DateTime? SelectedDateTime
{
get => (DateTime?)GetValue(SelectedDateTimeProperty);
set => SetValue(SelectedDateTimeProperty, value);
}
/// <summary>占位符(空值时显示在 TextBox 上的提示文字)。</summary>
public static readonly DependencyProperty PlaceholderProperty =
DependencyProperty.Register(
nameof(Placeholder), typeof(string), typeof(DateTimeListPicker),
new PropertyMetadata("请选择日期时间"));
public string Placeholder
{
get => (string)GetValue(PlaceholderProperty);
set => SetValue(PlaceholderProperty, value);
}
/// <summary>显示格式,默认 yyyy-MM-dd HH:mm:ss。</summary>
public static readonly DependencyProperty DateTimeFormatProperty =
DependencyProperty.Register(
nameof(DateTimeFormat), typeof(string), typeof(DateTimeListPicker),
new PropertyMetadata("yyyy-MM-dd HH:mm:ss", OnDateTimeFormatChanged));
public string DateTimeFormat
{
get => (string)GetValue(DateTimeFormatProperty);
set => SetValue(DateTimeFormatProperty, value);
}
#endregion
#region
/// <summary>弹出层是否打开。</summary>
public static readonly DependencyProperty IsDropDownOpenProperty =
DependencyProperty.Register(
nameof(IsDropDownOpen), typeof(bool), typeof(DateTimeListPicker),
new PropertyMetadata(false, OnIsDropDownOpenChanged));
public bool IsDropDownOpen
{
get => (bool)GetValue(IsDropDownOpenProperty);
set => SetValue(IsDropDownOpenProperty, value);
}
/// <summary>TextBox 当前显示的文字。用户可手工编辑,回车/失焦时尝试解析。</summary>
public static readonly DependencyProperty DisplayTextProperty =
DependencyProperty.Register(
nameof(DisplayText), typeof(string), typeof(DateTimeListPicker),
new PropertyMetadata(string.Empty));
public string DisplayText
{
get => (string)GetValue(DisplayTextProperty);
set => SetValue(DisplayTextProperty, value);
}
/// <summary>弹出层内未确认的日期值。</summary>
public static readonly DependencyProperty PendingDateProperty =
DependencyProperty.Register(
nameof(PendingDate), typeof(DateTime?), typeof(DateTimeListPicker),
new PropertyMetadata(null));
public DateTime? PendingDate
{
get => (DateTime?)GetValue(PendingDateProperty);
set => SetValue(PendingDateProperty, value);
}
public static readonly DependencyProperty PendingHourProperty =
DependencyProperty.Register(nameof(PendingHour), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
public int PendingHour
{
get => (int)GetValue(PendingHourProperty);
set => SetValue(PendingHourProperty, value);
}
public static readonly DependencyProperty PendingMinuteProperty =
DependencyProperty.Register(nameof(PendingMinute), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
public int PendingMinute
{
get => (int)GetValue(PendingMinuteProperty);
set => SetValue(PendingMinuteProperty, value);
}
public static readonly DependencyProperty PendingSecondProperty =
DependencyProperty.Register(nameof(PendingSecond), typeof(int), typeof(DateTimeListPicker), new PropertyMetadata(0));
public int PendingSecond
{
get => (int)GetValue(PendingSecondProperty);
set => SetValue(PendingSecondProperty, value);
}
#endregion
// 注意:这三个集合必须用字段初始化器赋值,而不是放到构造函数体内。
// 原因XAML 中通过 {Binding Hours, ElementName=Root} 绑定到普通 CLR 只读属性,
// 绑定在 InitializeComponent() 执行时就建立。若赋值滞后到构造函数体内,
// 绑定第一次求值时 Hours 仍为 null且不会再有变更通知导致弹出层右侧三列为空。
/// <summary>0-23 小时列表。</summary>
public List<int> Hours { get; } = Enumerable.Range(0, 24).ToList();
/// <summary>0-59 分钟列表。</summary>
public List<int> Minutes { get; } = Enumerable.Range(0, 60).ToList();
/// <summary>0-59 秒列表。</summary>
public List<int> Seconds { get; } = Enumerable.Range(0, 60).ToList();
private bool _suppressTextSync;
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DateTimeListPicker)d).SyncDisplay();
}
private static void OnDateTimeFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DateTimeListPicker)d).SyncDisplay();
}
private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var picker = (DateTimeListPicker)d;
if ((bool)e.NewValue)
{
picker.SyncPendingFromSelected();
picker.Dispatcher.BeginInvoke(new Action(picker.ScrollListBoxesToSelection),
System.Windows.Threading.DispatcherPriority.Loaded);
}
}
/// <summary>将 <see cref="SelectedDateTime"/> 同步到 TextBox 显示文本。</summary>
private void SyncDisplay()
{
_suppressTextSync = true;
try
{
DisplayText = SelectedDateTime.HasValue
? SelectedDateTime.Value.ToString(DateTimeFormat, CultureInfo.InvariantCulture)
: string.Empty;
}
finally
{
_suppressTextSync = false;
}
}
/// <summary>弹出层打开时,把当前选中值(或当前时间)拷贝到 Pending 字段。</summary>
private void SyncPendingFromSelected()
{
var dt = SelectedDateTime ?? DateTime.Now;
PendingDate = dt.Date;
PendingHour = dt.Hour;
PendingMinute = dt.Minute;
PendingSecond = dt.Second;
}
/// <summary>将三列时间 ListBox 滚动到当前选中项。</summary>
private void ScrollListBoxesToSelection()
{
PART_HourList?.ScrollIntoView(PendingHour);
PART_MinuteList?.ScrollIntoView(PendingMinute);
PART_SecondList?.ScrollIntoView(PendingSecond);
}
private void OnNowClick(object sender, RoutedEventArgs e)
{
var now = DateTime.Now;
PendingDate = now.Date;
PendingHour = now.Hour;
PendingMinute = now.Minute;
PendingSecond = now.Second;
ScrollListBoxesToSelection();
}
private void OnConfirmClick(object sender, RoutedEventArgs e)
{
var date = PendingDate ?? DateTime.Today;
SelectedDateTime = new DateTime(
date.Year, date.Month, date.Day,
ClampTime(PendingHour, 23),
ClampTime(PendingMinute, 59),
ClampTime(PendingSecond, 59));
IsDropDownOpen = false;
}
private static int ClampTime(int value, int max)
{
if (value < 0) return 0;
if (value > max) return max;
return value;
}
private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TryParseDisplayText();
e.Handled = true;
}
}
private void OnTextBoxLostFocus(object sender, RoutedEventArgs e)
{
TryParseDisplayText();
}
/// <summary>解析当前 TextBox 文本到 <see cref="SelectedDateTime"/>,失败则回滚显示。</summary>
private void TryParseDisplayText()
{
if (_suppressTextSync) return;
if (string.IsNullOrWhiteSpace(DisplayText))
{
SelectedDateTime = null;
return;
}
if (DateTime.TryParseExact(DisplayText, DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact))
{
SelectedDateTime = exact;
}
else if (DateTime.TryParse(DisplayText, CultureInfo.CurrentCulture, DateTimeStyles.None, out var fallback))
{
SelectedDateTime = fallback;
}
else
{
// 输入非法,恢复为原值
SyncDisplay();
}
}
}