From 561554f2bf5c31a9e56ab7f333e768fd0e338a0d Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 4 Sep 2024 02:18:39 +0800 Subject: [PATCH] fix: fix various timepicker sync issue. --- demo/Ursa.Demo/Pages/TimePickerDemo.axaml | 19 +++++++++++ .../ViewModels/TimePickerDemoViewModel.cs | 16 +++++++-- .../Controls/TimePicker.axaml | 3 +- .../Controls/TimeRangePicker.axaml | 4 +-- .../DateTimePicker/TimeChangedEventArgs.cs | 16 +++++++++ .../Controls/DateTimePicker/TimePicker.cs | 33 ++++++++++++++----- .../DateTimePicker/TimePickerPresenter.cs | 22 ++++++++++--- .../DateTimePicker/TimeRangePicker.cs | 26 +++++++++++++-- 8 files changed, 116 insertions(+), 23 deletions(-) create mode 100644 src/Ursa/Controls/DateTimePicker/TimeChangedEventArgs.cs diff --git a/demo/Ursa.Demo/Pages/TimePickerDemo.axaml b/demo/Ursa.Demo/Pages/TimePickerDemo.axaml index 7a8c8bf..3a271a0 100644 --- a/demo/Ursa.Demo/Pages/TimePickerDemo.axaml +++ b/demo/Ursa.Demo/Pages/TimePickerDemo.axaml @@ -5,12 +5,16 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:u="https://irihi.tech/ursa" + xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels" d:DesignHeight="450" d:DesignWidth="800" + x:DataType="viewModels:TimePickerDemoViewModel" mc:Ignorable="d"> + + + + + diff --git a/demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs index f115429..7b93e1a 100644 --- a/demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/TimePickerDemoViewModel.cs @@ -1,8 +1,18 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using System; +using CommunityToolkit.Mvvm.ComponentModel; namespace Ursa.Demo.ViewModels; -public class TimePickerDemoViewModel: ObservableObject +public partial class TimePickerDemoViewModel: ObservableObject { - + [ObservableProperty] private TimeSpan? _time; + [ObservableProperty] private TimeSpan? _startTime; + [ObservableProperty] private TimeSpan? _endTime; + + public TimePickerDemoViewModel() + { + Time = new TimeSpan(12, 20, 0); + StartTime = new TimeSpan(8, 21, 0); + EndTime = new TimeSpan(18, 22, 0); + } } \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/TimePicker.axaml b/src/Ursa.Themes.Semi/Controls/TimePicker.axaml index 04e065b..61661c2 100644 --- a/src/Ursa.Themes.Semi/Controls/TimePicker.axaml +++ b/src/Ursa.Themes.Semi/Controls/TimePicker.axaml @@ -193,8 +193,7 @@ + PanelFormat="{TemplateBinding PanelFormat}"/> diff --git a/src/Ursa.Themes.Semi/Controls/TimeRangePicker.axaml b/src/Ursa.Themes.Semi/Controls/TimeRangePicker.axaml index c797a27..f08f682 100644 --- a/src/Ursa.Themes.Semi/Controls/TimeRangePicker.axaml +++ b/src/Ursa.Themes.Semi/Controls/TimeRangePicker.axaml @@ -141,7 +141,7 @@ Grid.Column="0" NeedsConfirmation="{TemplateBinding NeedConfirmation}" PanelFormat="{TemplateBinding PanelFormat}" - Time="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=StartTime, Mode=OneWayToSource}" /> + /> + /> diff --git a/src/Ursa/Controls/DateTimePicker/TimeChangedEventArgs.cs b/src/Ursa/Controls/DateTimePicker/TimeChangedEventArgs.cs new file mode 100644 index 0000000..52aed13 --- /dev/null +++ b/src/Ursa/Controls/DateTimePicker/TimeChangedEventArgs.cs @@ -0,0 +1,16 @@ +using Avalonia.Interactivity; + +namespace Ursa.Controls; + +public class TimeChangedEventArgs:RoutedEventArgs +{ + public TimeSpan? OldTime { get; } + + public TimeSpan? NewTime { get; } + + public TimeChangedEventArgs(TimeSpan? oldTime, TimeSpan? newTime) + { + this.OldTime = oldTime; + this.NewTime = newTime; + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/DateTimePicker/TimePicker.cs b/src/Ursa/Controls/DateTimePicker/TimePicker.cs index 73db3b8..5528bdb 100644 --- a/src/Ursa/Controls/DateTimePicker/TimePicker.cs +++ b/src/Ursa/Controls/DateTimePicker/TimePicker.cs @@ -29,6 +29,8 @@ public class TimePicker : TimePickerBase, IClearControl public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register( nameof(Watermark)); + private bool _suppressTextPresenterEvent; + private Button? _button; private TimePickerPresenter? _presenter; private TextBox? _textBox; @@ -44,11 +46,7 @@ public class TimePicker : TimePickerBase, IClearControl private void OnDisplayFormatChanged(AvaloniaPropertyChangedEventArgs _) { if (_textBox is null) return; - var time = SelectedTime; - if (time is null) return; - var date = new DateTime( 1, 1, 1, time.Value.Hours, time.Value.Minutes, time.Value.Seconds); - var text = date.ToString(DisplayFormat); - _textBox.Text = text; + SyncTimeToText(SelectedTime); } public string? Watermark @@ -77,6 +75,7 @@ public class TimePicker : TimePickerBase, IClearControl TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox); PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox); Button.ClickEvent.RemoveHandler(OnButtonClick, _button); + TimePickerPresenter.SelectedTimeChangedEvent.RemoveHandler(OnPresenterTimeChanged, _presenter); _textBox = e.NameScope.Find(PART_TextBox); e.NameScope.Find(PartNames.PART_Popup); @@ -87,8 +86,17 @@ public class TimePicker : TimePickerBase, IClearControl TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox); PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox); Button.ClickEvent.AddHandler(OnButtonClick, _button); + TimePickerPresenter.SelectedTimeChangedEvent.AddHandler(OnPresenterTimeChanged, _presenter); - SetCurrentValue(SelectedTimeProperty, DateTime.Now.TimeOfDay); + // SetCurrentValue(SelectedTimeProperty, DateTime.Now.TimeOfDay); + _presenter?.SetValue(TimePickerPresenter.TimeProperty, SelectedTime); + SyncTimeToText(SelectedTime); + } + + private void OnPresenterTimeChanged(object sender, TimeChangedEventArgs e) + { + if (_suppressTextPresenterEvent) return; + SetCurrentValue(SelectedTimeProperty, e.NewTime); } private void OnButtonClick(object? sender, RoutedEventArgs e) @@ -104,7 +112,7 @@ public class TimePicker : TimePickerBase, IClearControl private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e) { - SetCurrentValue(IsDropdownOpenProperty, true); + // SetCurrentValue(IsDropdownOpenProperty, true); } protected override void OnKeyDown(KeyEventArgs e) @@ -154,13 +162,20 @@ public class TimePicker : TimePickerBase, IClearControl private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs args) { if (_textBox is null) return; - var time = args.NewValue.Value; + _suppressTextPresenterEvent = true; + _presenter?.SetValue(TimePickerPresenter.TimeProperty, args.NewValue.Value); + SyncTimeToText(args.NewValue.Value); + _suppressTextPresenterEvent = false; + } + + private void SyncTimeToText(TimeSpan? time) + { + if (_textBox is null) return; if (time is null) { _textBox.Text = null; return; } - var date = new DateTime(1, 1, 1, time.Value.Hours, time.Value.Minutes, time.Value.Seconds); var text = date.ToString(DisplayFormat); _textBox.Text = text; diff --git a/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs b/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs index a3bd900..c755111 100644 --- a/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs +++ b/src/Ursa/Controls/DateTimePicker/TimePickerPresenter.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; using Irihi.Avalonia.Shared.Helpers; namespace Ursa.Controls; @@ -83,7 +84,7 @@ public class TimePickerPresenter : TemplatedControl public TimePickerPresenter() { - SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay); + // SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay); } public bool NeedsConfirmation @@ -115,16 +116,27 @@ public class TimePickerPresenter : TemplatedControl get => GetValue(PanelFormatProperty); set => SetValue(PanelFormatProperty, value); } - - public event EventHandler? SelectedTimeChanged; + + public static readonly RoutedEvent SelectedTimeChangedEvent = + RoutedEvent.Register( + nameof(SelectedTimeChanged), RoutingStrategies.Bubble); + + public event EventHandler SelectedTimeChanged + { + add => AddHandler(SelectedTimeChangedEvent, value); + remove => RemoveHandler(SelectedTimeChangedEvent, value); + } private void OnTimeChanged(AvaloniaPropertyChangedEventArgs args) { _updateFromTimeChange = true; UpdatePanelsFromSelectedTime(args.NewValue.Value); _updateFromTimeChange = false; - SelectedTimeChanged?.Invoke(this, - new TimePickerSelectedValueChangedEventArgs(args.OldValue.Value, args.NewValue.Value)); + if (args.OldValue.Value != args.NewValue.Value) + { + RaiseEvent(new TimeChangedEventArgs(args.OldValue.Value, args.NewValue.Value) + { RoutedEvent = SelectedTimeChangedEvent, Source = this }); + } } private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs args) diff --git a/src/Ursa/Controls/DateTimePicker/TimeRangePicker.cs b/src/Ursa/Controls/DateTimePicker/TimeRangePicker.cs index 62ab178..0c8c41c 100644 --- a/src/Ursa/Controls/DateTimePicker/TimeRangePicker.cs +++ b/src/Ursa/Controls/DateTimePicker/TimeRangePicker.cs @@ -48,6 +48,7 @@ public class TimeRangePicker : TimePickerBase, IClearControl private TimePickerPresenter? _startPresenter; private TextBox? _startTextBox; + private bool _suppressTextPresenterEvent; static TimeRangePicker() @@ -90,10 +91,17 @@ public class TimeRangePicker : TimePickerBase, IClearControl } private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs args, bool start = true) + { + SyncTimeToText(args.NewValue.Value, start); + _suppressTextPresenterEvent = true; + TimePickerPresenter.TimeProperty.SetValue(args.NewValue.Value, start ? _startPresenter : _endPresenter); + _suppressTextPresenterEvent = false; + } + + private void SyncTimeToText(TimeSpan? time, bool start = true) { var textBox = start ? _startTextBox : _endTextBox; if (textBox is null) return; - var time = args.NewValue.Value; if (time is null) { textBox.Text = null; @@ -112,6 +120,8 @@ public class TimeRangePicker : TimePickerBase, IClearControl GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _startTextBox, _endTextBox); PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _startTextBox, _endTextBox); Button.ClickEvent.RemoveHandler(OnButtonClick, _button); + TimePickerPresenter.SelectedTimeChangedEvent.RemoveHandler(OnPresenterTimeChanged, _startPresenter, + _endPresenter); e.NameScope.Find(PartNames.PART_Popup); _startTextBox = e.NameScope.Find(PART_StartTextBox); @@ -124,6 +134,18 @@ public class TimeRangePicker : TimePickerBase, IClearControl PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _startTextBox, _endTextBox); Button.ClickEvent.AddHandler(OnButtonClick, _button); + TimePickerPresenter.SelectedTimeChangedEvent.AddHandler(OnPresenterTimeChanged, _startPresenter, _endPresenter); + + _startPresenter?.SetValue(TimePickerPresenter.TimeProperty, StartTime); + _endPresenter?.SetValue(TimePickerPresenter.TimeProperty, EndTime); + SyncTimeToText(StartTime); + SyncTimeToText(EndTime, false); + } + + private void OnPresenterTimeChanged(object sender, TimeChangedEventArgs e) + { + if (_suppressTextPresenterEvent) return; + SetCurrentValue(Equals(sender, _startPresenter) ? StartTimeProperty : EndTimeProperty, e.NewTime); } private void OnButtonClick(object? sender, RoutedEventArgs e) @@ -174,7 +196,7 @@ public class TimeRangePicker : TimePickerBase, IClearControl SetCurrentValue(IsDropdownOpenProperty, false); Focus(); } - + public void Dismiss() { SetCurrentValue(IsDropdownOpenProperty, false);