From 7585d86fb540a2b074c6c81198825ab661eb9afe Mon Sep 17 00:00:00 2001 From: Mahdi Hosseini Date: Sat, 11 May 2019 19:17:10 +0430 Subject: [PATCH] add TimePickerList --- .../Controls/TimePickerDemoCtl.xaml | 2 + .../Controls/Time/ItemsControlExtensions.cs | 79 ++++ .../Controls/Time/TimeButton.cs | 42 ++ .../Controls/Time/TimePickerList.cs | 114 ++++++ .../Controls/Time/TimeSelector.cs | 380 ++++++++++++++++++ .../HandyControl_Shared.projitems | 12 + .../Themes/Styles/Style.xaml | 2 + .../Themes/Styles/TimePickerList.xaml | 88 ++++ .../Themes/Styles/TimeSelector.xaml | 89 ++++ 9 files changed, 808 insertions(+) create mode 100644 src/Shared/HandyControl_Shared/Controls/Time/ItemsControlExtensions.cs create mode 100644 src/Shared/HandyControl_Shared/Controls/Time/TimeButton.cs create mode 100644 src/Shared/HandyControl_Shared/Controls/Time/TimePickerList.cs create mode 100644 src/Shared/HandyControl_Shared/Controls/Time/TimeSelector.cs create mode 100644 src/Shared/HandyControl_Shared/Themes/Styles/TimePickerList.xaml create mode 100644 src/Shared/HandyControl_Shared/Themes/Styles/TimeSelector.xaml diff --git a/src/Shared/HandyControlDemo_Shared/UserControl/Controls/TimePickerDemoCtl.xaml b/src/Shared/HandyControlDemo_Shared/UserControl/Controls/TimePickerDemoCtl.xaml index 840acbb5..63143963 100644 --- a/src/Shared/HandyControlDemo_Shared/UserControl/Controls/TimePickerDemoCtl.xaml +++ b/src/Shared/HandyControlDemo_Shared/UserControl/Controls/TimePickerDemoCtl.xaml @@ -7,6 +7,8 @@ + + diff --git a/src/Shared/HandyControl_Shared/Controls/Time/ItemsControlExtensions.cs b/src/Shared/HandyControl_Shared/Controls/Time/ItemsControlExtensions.cs new file mode 100644 index 00000000..8cea4c60 --- /dev/null +++ b/src/Shared/HandyControl_Shared/Controls/Time/ItemsControlExtensions.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace HandyControl.Controls +{ + /// + /// ItemsControl扩展方法 + /// + public static class ItemsControlExtensions + { + /// + /// 平滑滚动至指定元素 + /// + /// + /// + public static void AnimateScrollIntoView(this ItemsControl itemsControl, object item) + { + ScrollViewer scrollViewer = FindVisualChild(itemsControl); + if (scrollViewer == null) + { + return; + } + + UIElement container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as UIElement; + + if (container == null) + { + return; + } + + int index = itemsControl.ItemContainerGenerator.IndexFromContainer(container); + + //平滑滚动到元素所在位置 + //double toValue = scrollViewer.ScrollableHeight * ((double)index / itemsControl.Items.Count); + //平滑滚动,将选中的元素置顶 + double toValue = VisualTreeHelper.GetOffset(container).Y; + + DoubleAnimation verticalAnimation = new DoubleAnimation(); + verticalAnimation.From = scrollViewer.VerticalOffset; + verticalAnimation.To = toValue; + verticalAnimation.DecelerationRatio = .2; + verticalAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(200)); + Storyboard storyboard = new Storyboard(); + storyboard.Children.Add(verticalAnimation); + Storyboard.SetTarget(verticalAnimation, scrollViewer); + Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollViewerBehavior.VerticalOffsetProperty)); + storyboard.Begin(); + } + + /// + /// 查找元素的子元素 + /// + /// 子元素类型 + /// + /// + public static T FindVisualChild(DependencyObject obj) where T : DependencyObject + { + for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) + { + DependencyObject child = VisualTreeHelper.GetChild(obj, i); + if (child != null && child is T) + return (T)child; + else + { + T childOfChild = FindVisualChild(child); + if (childOfChild != null) + return childOfChild; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Controls/Time/TimeButton.cs b/src/Shared/HandyControl_Shared/Controls/Time/TimeButton.cs new file mode 100644 index 00000000..751bb887 --- /dev/null +++ b/src/Shared/HandyControl_Shared/Controls/Time/TimeButton.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace HandyControl.Controls +{ + public class TimeButton : ListBoxItem + { + #region Private属性 + #endregion + + #region 依赖属性定义 + + #endregion + + #region 依赖属性set get + + #endregion + + #region Constructors + static TimeButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TimeButton), new FrameworkPropertyMetadata(typeof(TimeButton))); + } + #endregion + + #region Override方法 + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + } + #endregion + + #region Private方法 + + #endregion + } +} \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Controls/Time/TimePickerList.cs b/src/Shared/HandyControl_Shared/Controls/Time/TimePickerList.cs new file mode 100644 index 00000000..9605dfc0 --- /dev/null +++ b/src/Shared/HandyControl_Shared/Controls/Time/TimePickerList.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; + +namespace HandyControl.Controls +{ + public class TimePickerList : Control + { + #region Private属性 + private TextBox PART_TextBox; + private TimeSelector PART_TimeSelector; + private Popup PART_Popup; + #endregion + + #region 依赖属性定义 + + #endregion + + #region 依赖属性set get + + #region TimeStringFormat + public string TimeStringFormat + { + get { return (string)GetValue(TimeStringFormatProperty); } + set { SetValue(TimeStringFormatProperty, value); } + } + + public static readonly DependencyProperty TimeStringFormatProperty = + DependencyProperty.Register("TimeStringFormat", typeof(string), typeof(TimePickerList), new PropertyMetadata("HH:mm:ss")); + #endregion + + #region SelectedTime + public DateTime? SelectedTime + { + get { return (DateTime?)GetValue(SelectedTimeProperty); } + set { SetValue(SelectedTimeProperty, value); } + } + + public static readonly DependencyProperty SelectedTimeProperty = + DependencyProperty.Register("SelectedTime", typeof(DateTime?), typeof(TimePickerList), new PropertyMetadata(null, SelectedTimeChangedCallback)); + + private static void SelectedTimeChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TimePickerList timePicker = d as TimePickerList; + DateTime dt = (DateTime)e.NewValue; + + timePicker.PART_TimeSelector.SelectedTime = dt; + } + #endregion + + #region DropDownHeight + + public double DropDownHeight + { + get { return (double)GetValue(DropDownHeightProperty); } + set { SetValue(DropDownHeightProperty, value); } + } + + public static readonly DependencyProperty DropDownHeightProperty = + DependencyProperty.Register("DropDownHeight", typeof(double), typeof(TimePickerList), new PropertyMetadata(168d)); + + #endregion + + #endregion + + #region Constructors + static TimePickerList() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TimePickerList), new FrameworkPropertyMetadata(typeof(TimePickerList))); + } + #endregion + + #region Override方法 + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + this.PART_TextBox = this.GetTemplateChild("PART_TextBox") as TextBox; + this.PART_TimeSelector = this.GetTemplateChild("PART_TimeSelector") as TimeSelector; + this.PART_Popup = this.GetTemplateChild("PART_Popup") as Popup; + if (this.PART_TimeSelector != null) + { + this.PART_TimeSelector.Owner = this; + this.PART_TimeSelector.SelectedTimeChanged += PART_TimeSelector_SelectedTimeChanged; + } + if (this.PART_Popup != null) + { + this.PART_Popup.Opened += PART_Popup_Opened; + } + } + + private void PART_Popup_Opened(object sender, EventArgs e) + { + this.PART_TimeSelector.SetButtonSelected(); + } + #endregion + + #region Private方法 + private void PART_TimeSelector_SelectedTimeChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (this.PART_TextBox != null && e.NewValue != null) + { + this.PART_TextBox.Text = e.NewValue.Value.ToString(this.TimeStringFormat); + this.SelectedTime = e.NewValue; + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Controls/Time/TimeSelector.cs b/src/Shared/HandyControl_Shared/Controls/Time/TimeSelector.cs new file mode 100644 index 00000000..e2eb030a --- /dev/null +++ b/src/Shared/HandyControl_Shared/Controls/Time/TimeSelector.cs @@ -0,0 +1,380 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace HandyControl.Controls +{ + public class TimeSelector : Control + { + private ObservableCollection HourButtons = new ObservableCollection(); + private ObservableCollection MinuteButtons = new ObservableCollection(); + private ObservableCollection SecondButtons = new ObservableCollection(); + + #region 控件内部元素 + private ListBox PART_Hour; + private ListBox PART_Minute; + private ListBox PART_Second; + #endregion + + #region 私有属性 + private int Hour = 0; + private int Minute = 0; + private int Second = 0; + #endregion + + #region Fields + public TimePickerList Owner { get; set; } + #endregion + + #region 事件定义 + + #region SelectedTimeChanged + public static readonly RoutedEvent SelectedTimeChangedEvent = EventManager.RegisterRoutedEvent("SelectedTimeChanged", + RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler), typeof(TimeSelector)); + + public event RoutedPropertyChangedEventHandler SelectedTimeChanged + { + add + { + this.AddHandler(SelectedTimeChangedEvent, value); + } + remove + { + this.RemoveHandler(SelectedTimeChangedEvent, value); + } + } + + public virtual void OnSelectedTimeChanged(DateTime? oldValue, DateTime? newValue) + { + RoutedPropertyChangedEventArgs arg = new RoutedPropertyChangedEventArgs(oldValue, newValue, SelectedTimeChangedEvent); + this.RaiseEvent(arg); + } + #endregion + + #endregion + + #region 依赖属性set get + + #region SelectedTime + public DateTime? SelectedTime + { + get { return (DateTime?)GetValue(SelectedTimeProperty); } + set { SetValue(SelectedTimeProperty, value); } + } + + public static readonly DependencyProperty SelectedTimeProperty = + DependencyProperty.Register("SelectedTime", typeof(DateTime?), typeof(TimeSelector), new PropertyMetadata(null, SelectedTimeChangedCallback)); + + private static void SelectedTimeChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TimeSelector timeSelector = d as TimeSelector; + DateTime dt = (DateTime)e.NewValue; + + timeSelector.SetButtonSelected(); + + timeSelector.OnSelectedTimeChanged((DateTime?)e.OldValue, (DateTime?)e.NewValue); + } + + public void SetButtonSelected() + { + if (!this.SelectedTime.HasValue) + { + return; + } + + this.Hour = this.SelectedTime.Value.Hour; + this.Minute = this.SelectedTime.Value.Minute; + this.Second = this.SelectedTime.Value.Second; + + if (this.PART_Hour != null) + { + for (int i = 0; i < this.PART_Hour.Items.Count; i++) + { + TimeButton timeButton = this.PART_Hour.Items[i] as TimeButton; + if (Convert.ToString(timeButton.Content).Equals(Convert.ToString(this.SelectedTime.Value.Hour))) + { + this.PART_Hour.SelectedIndex = i; + this.PART_Hour.AnimateScrollIntoView(timeButton); + break; + } + } + } + + if (this.PART_Minute != null) + { + for (int i = 0; i < this.PART_Minute.Items.Count; i++) + { + TimeButton timeButton = this.PART_Minute.Items[i] as TimeButton; + if (Convert.ToString(timeButton.Content).Equals(Convert.ToString(this.SelectedTime.Value.Minute))) + { + this.PART_Minute.SelectedIndex = i; + this.PART_Minute.AnimateScrollIntoView(timeButton); + break; + } + } + } + + if (this.PART_Second != null) + { + for (int i = 0; i < this.PART_Second.Items.Count; i++) + { + TimeButton timeButton = this.PART_Second.Items[i] as TimeButton; + if (Convert.ToString(timeButton.Content).Equals(Convert.ToString(this.SelectedTime.Value.Second))) + { + this.PART_Second.SelectedIndex = i; + this.PART_Second.AnimateScrollIntoView(timeButton); + break; + } + } + } + } + #endregion + + #region SelectedHour + public int? SelectedHour + { + get { return (int?)GetValue(SelectedHourProperty); } + set { SetValue(SelectedHourProperty, value); } + } + + public static readonly DependencyProperty SelectedHourProperty = + DependencyProperty.Register("SelectedHour", typeof(int?), typeof(TimeSelector), new PropertyMetadata(null, SelectedHourChanged)); + + private static void SelectedHourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TimeSelector timeSelector = d as TimeSelector; + timeSelector.Hour = e.NewValue == null ? 0 : (int)e.NewValue; + timeSelector.SetSelectedTime(); + } + #endregion + + #region SelectedMinute + + public int? SelectedMinute + { + get { return (int?)GetValue(SelectedMinuteProperty); } + set { SetValue(SelectedMinuteProperty, value); } + } + + public static readonly DependencyProperty SelectedMinuteProperty = + DependencyProperty.Register("SelectedMinute", typeof(int?), typeof(TimeSelector), new PropertyMetadata(null, SelectedMinuteChanged)); + + private static void SelectedMinuteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TimeSelector timeSelector = d as TimeSelector; + + timeSelector.Minute = e.NewValue == null ? 0 : (int)e.NewValue; + timeSelector.SetSelectedTime(); + } + + #endregion + + #region SelectedSecond + + public int? SelectedSecond + { + get { return (int?)GetValue(SelectedSecondProperty); } + set { SetValue(SelectedSecondProperty, value); } + } + + public static readonly DependencyProperty SelectedSecondProperty = + DependencyProperty.Register("SelectedSecond", typeof(int?), typeof(TimeSelector), new PropertyMetadata(null, SelectedSecondChanged)); + + private static void SelectedSecondChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + TimeSelector timeSelector = d as TimeSelector; + + timeSelector.Second = e.NewValue == null ? 0 : (int)e.NewValue; + timeSelector.SetSelectedTime(); + } + + #endregion + + #region ItemHeight + public double ItemHeight + { + get { return (double)GetValue(ItemHeightProperty); } + set { SetValue(ItemHeightProperty, value); } + } + + public static readonly DependencyProperty ItemHeightProperty = + DependencyProperty.Register("ItemHeight", typeof(double), typeof(TimeSelector), new PropertyMetadata(28.0)); + #endregion + + #endregion + + #region Constructors + static TimeSelector() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TimeSelector), new FrameworkPropertyMetadata(typeof(TimeSelector))); + } + #endregion + + #region Override方法 + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + this.PART_Hour = this.GetTemplateChild("PART_Hour") as ListBox; + this.PART_Minute = this.GetTemplateChild("PART_Minute") as ListBox; + this.PART_Second = this.GetTemplateChild("PART_Second") as ListBox; + + if (this.PART_Hour != null) + { + this.CreateHourButtons(); + this.PART_Hour.AddHandler(ListBoxItem.MouseLeftButtonDownEvent, new RoutedEventHandler(HourButton_Click), true); + } + + if (this.PART_Minute != null) + { + this.CreateMinuteButtons(); + this.PART_Minute.AddHandler(ListBoxItem.MouseLeftButtonDownEvent, new RoutedEventHandler(MinuteButton_Click), true); + } + + if (this.PART_Second != null) + { + this.CreateSecondButtons(); + this.PART_Second.AddHandler(ListBoxItem.MouseLeftButtonDownEvent, new RoutedEventHandler(SecondButton_Click), true); + } + } + #endregion + + #region Private方法 + + private void MinuteButton_Click(object sender, RoutedEventArgs e) + { + TimeButton selectedItem = this.PART_Minute.SelectedItem as TimeButton; + if (selectedItem == null) + { + return; + } + this.SelectedMinute = Convert.ToInt32(selectedItem.DataContext); + this.PART_Minute.AnimateScrollIntoView(selectedItem); + } + + private void SecondButton_Click(object sender, RoutedEventArgs e) + { + TimeButton selectedItem = this.PART_Second.SelectedItem as TimeButton; + if (selectedItem == null) + { + return; + } + this.SelectedSecond = Convert.ToInt32(selectedItem.DataContext); + this.PART_Second.AnimateScrollIntoView(selectedItem); + } + + private void HourButton_Click(object sender, RoutedEventArgs e) + { + TimeButton selectedItem = this.PART_Hour.SelectedItem as TimeButton; + if (selectedItem == null) + { + return; + } + this.SelectedHour = Convert.ToInt32(selectedItem.DataContext); + this.PART_Hour.AnimateScrollIntoView(selectedItem); + } + + private void CreateHourButtons() + { + this.CreateItems(24, this.HourButtons); + this.CreateExtraItem(this.HourButtons); + this.PART_Hour.ItemsSource = this.HourButtons; + } + + private void CreateMinuteButtons() + { + this.CreateItems(60, this.MinuteButtons); + this.CreateExtraItem(this.MinuteButtons); + this.PART_Minute.ItemsSource = this.MinuteButtons; + } + + private void CreateSecondButtons() + { + this.CreateItems(60, this.SecondButtons); + this.CreateExtraItem(this.SecondButtons); + this.PART_Second.ItemsSource = this.SecondButtons; + } + + private void CreateItems(int itemsCount, ObservableCollection list) + { + for (int i = 0; i < itemsCount; i++) + { + TimeButton timeButton = new TimeButton(); + timeButton.SetValue(TimeButton.HeightProperty, this.ItemHeight); + timeButton.SetValue(TimeButton.DataContextProperty, i); + timeButton.SetValue(TimeButton.ContentProperty, (i < 10) ? "0" + i : i.ToString()); + timeButton.SetValue(TimeButton.IsSelectedProperty, false); + list.Add(timeButton); + } + } + + private void CreateExtraItem(ObservableCollection list) + { + double height = this.ItemHeight; + if (this.Owner != null) + { + height = this.Owner.DropDownHeight; + } + else + { + height = double.IsNaN(this.Height) ? height : this.Height; + } + + for (int i = 0; i < (height - this.ItemHeight) / this.ItemHeight; i++) + { + TimeButton timeButton = new TimeButton(); + timeButton.SetValue(TimeButton.HeightProperty, this.ItemHeight); + timeButton.SetValue(TimeButton.IsEnabledProperty, false); + timeButton.SetValue(TimeButton.IsSelectedProperty, false); + list.Add(timeButton); + } + } + + private DateTime GetDateTime(int hour, int minute, int second) + { + DateTime dt = new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day, hour, minute, second); + return dt; + } + + /// + /// 设置选中的时间 + /// + private void SetSelectedTime() + { + DateTime dt = this.GetDateTime(this.Hour, this.Minute, this.Second); + this.SelectedTime = dt; + } + #endregion + } + public class ScrollViewerBehavior + { + public static DependencyProperty VerticalOffsetProperty = + DependencyProperty.RegisterAttached("VerticalOffset", + typeof(double), + typeof(ScrollViewerBehavior), + new UIPropertyMetadata(0.0, OnVerticalOffsetChanged)); + + public static void SetVerticalOffset(FrameworkElement target, double value) + { + target.SetValue(VerticalOffsetProperty, value); + } + public static double GetVerticalOffset(FrameworkElement target) + { + return (double)target.GetValue(VerticalOffsetProperty); + } + private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) + { + ScrollViewer scrollViewer = target as ScrollViewer; + if (scrollViewer != null) + { + scrollViewer.ScrollToVerticalOffset((double)e.NewValue); + } + } + } +} \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems index 7006f532..801e85b4 100644 --- a/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems +++ b/src/Shared/HandyControl_Shared/HandyControl_Shared.projitems @@ -98,8 +98,12 @@ + + + + @@ -698,6 +702,14 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/src/Shared/HandyControl_Shared/Themes/Styles/Style.xaml b/src/Shared/HandyControl_Shared/Themes/Styles/Style.xaml index 5a971e9d..56d1f56c 100644 --- a/src/Shared/HandyControl_Shared/Themes/Styles/Style.xaml +++ b/src/Shared/HandyControl_Shared/Themes/Styles/Style.xaml @@ -63,6 +63,8 @@ + + \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Themes/Styles/TimePickerList.xaml b/src/Shared/HandyControl_Shared/Themes/Styles/TimePickerList.xaml new file mode 100644 index 00000000..ff291905 --- /dev/null +++ b/src/Shared/HandyControl_Shared/Themes/Styles/TimePickerList.xaml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Shared/HandyControl_Shared/Themes/Styles/TimeSelector.xaml b/src/Shared/HandyControl_Shared/Themes/Styles/TimeSelector.xaml new file mode 100644 index 00000000..3dd43aaa --- /dev/null +++ b/src/Shared/HandyControl_Shared/Themes/Styles/TimeSelector.xaml @@ -0,0 +1,89 @@ + + + + + + + + + \ No newline at end of file