diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index e5914c90ee..8eda9e1f0b 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -44,7 +44,7 @@ namespace Avalonia.Controls /// public static readonly StyledProperty SelectedTimeProperty = AvaloniaProperty.Register(nameof(SelectedTime), - defaultBindingMode: BindingMode.TwoWay); + defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); // Template Items private TimePickerPresenter? _presenter; @@ -290,5 +290,13 @@ namespace Avalonia.Controls { SetCurrentValue(SelectedTimeProperty, null); } + + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error) + { + base.UpdateDataValidation(property, state, error); + + if (property == SelectedTimeProperty) + DataValidationErrors.SetError(this, error); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs index 793c2c6b86..f84a6a5791 100644 --- a/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TimePickerTests.cs @@ -1,9 +1,12 @@ using System; using System.Linq; +using System.Reactive.Subjects; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Headless; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; using Moq; @@ -133,6 +136,48 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void SelectedTime_EnableDataValidation() + { + using (UnitTestApplication.Start(Services)) + { + var handled = false; + var timePicker = new TimePicker(); + + timePicker.SelectedTimeChanged += (s, e) => + { + var minTime = new TimeSpan(10, 0, 0); + var maxTime = new TimeSpan(15, 0, 0); + + if (e.NewTime < minTime) + throw new DataValidationException($"time is less than {maxTime}"); + + if (e.NewTime > maxTime) + throw new DataValidationException($"time is over {maxTime}"); + + handled = true; + }; + + // time is less than + Assert.Throws(() => timePicker.SelectedTime = new TimeSpan(1, 2, 3)); + + // time is over + Assert.Throws(() => timePicker.SelectedTime = new TimeSpan(21, 22, 23)); + + var exception = new DataValidationException("failed validation"); + var observable = + new BehaviorSubject(new BindingNotification(exception, + BindingErrorType.DataValidationError)); + timePicker.Bind(TimePicker.SelectedTimeProperty, observable); + + Assert.True(DataValidationErrors.GetHasErrors(timePicker)); + + Dispatcher.UIThread.RunJobs(); + timePicker.SelectedTime = new TimeSpan(11, 12, 13); + Assert.True(handled); + } + } + private static TestServices Services => TestServices.MockThreadingInterface.With( fontManagerImpl: new HeadlessFontManagerStub(), standardCursorFactory: Mock.Of(),