Browse Source

Add data validation error support to NumericUpDown control #3591

aguahombre 4 years ago
parent
commit
5d130b5d79

+ 16 - 2
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@@ -91,14 +91,14 @@ namespace Avalonia.Controls
         /// </summary>
         public static readonly DirectProperty<NumericUpDown, string> TextProperty =
             AvaloniaProperty.RegisterDirect<NumericUpDown, string>(nameof(Text), o => o.Text, (o, v) => o.Text = v,
-                defaultBindingMode: BindingMode.TwoWay);
+                defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
 
         /// <summary>
         /// Defines the <see cref="Value"/> property.
         /// </summary>
         public static readonly DirectProperty<NumericUpDown, double> ValueProperty =
             AvaloniaProperty.RegisterDirect<NumericUpDown, double>(nameof(Value), updown => updown.Value,
-                (updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay);
+                (updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
 
         /// <summary>
         /// Defines the <see cref="Watermark"/> property.
@@ -370,6 +370,20 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <summary>
+        /// Called to update the validation state for properties for which data validation is
+        /// enabled.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="value">The new binding value for the property.</param>
+        protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
+        {
+            if (property == TextProperty || property == ValueProperty)
+            {
+                DataValidationErrors.SetError(this, value.Error);
+            }
+        }
+
         /// <summary>
         /// Called when the <see cref="CultureInfo"/> property value changed.
         /// </summary>

+ 95 - 0
tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs

@@ -0,0 +1,95 @@
+using System;
+using System.Linq;
+using System.Reactive.Subjects;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+using Avalonia.Threading;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Controls.UnitTests
+{
+    public class NumericUpDownTests
+    {
+        private static TestServices Services => TestServices.StyledWindow;
+
+        [Fact]
+        public void Text_Validation()
+        {
+            RunTest((control, textbox) =>
+            {
+                var exception = new InvalidCastException("failed validation");
+                var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
+                control.Bind(NumericUpDown.TextProperty, textObservable);
+                Dispatcher.UIThread.RunJobs();
+
+                Assert.True(DataValidationErrors.GetHasErrors(control));
+                Assert.True(DataValidationErrors.GetErrors(control).SequenceEqual(new[] { exception }));
+            });
+        }
+
+        [Fact]
+        public void Value_Validation()
+        {
+            RunTest((control, textbox) =>
+            {
+                var exception = new InvalidCastException("failed validation");
+                var valueObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
+                control.Bind(NumericUpDown.ValueProperty, valueObservable);
+                Dispatcher.UIThread.RunJobs();
+
+                Assert.True(DataValidationErrors.GetHasErrors(control));
+                Assert.True(DataValidationErrors.GetErrors(control).SequenceEqual(new[] { exception }));
+            });
+        }
+
+        private void RunTest(Action<NumericUpDown, TextBox> test)
+        {
+            using (UnitTestApplication.Start(Services))
+            {
+                var control = CreateControl();
+                TextBox textBox = GetTextBox(control);
+                var window = new Window { Content = control };
+                window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
+                Dispatcher.UIThread.RunJobs();
+                test.Invoke(control, textBox);
+            }
+        }
+
+        private NumericUpDown CreateControl()
+        {
+            var control = new NumericUpDown
+            {
+                Template = CreateTemplate()
+            };
+
+            control.ApplyTemplate();
+            return control;
+        }
+        private TextBox GetTextBox(NumericUpDown control)
+        {
+            return control.GetTemplateChildren()
+                          .OfType<ButtonSpinner>()
+                          .Select(b => b.Content)
+                          .OfType<TextBox>()
+                          .First();
+        }
+        private IControlTemplate CreateTemplate()
+        {
+            return new FuncControlTemplate<NumericUpDown>((control, scope) =>
+            {
+                var textBox =
+                    new TextBox
+                    {
+                        Name = "PART_TextBox"
+                    }.RegisterInNameScope(scope);
+                return new ButtonSpinner
+                    {
+                        Name = "PART_Spinner",
+                        Content = textBox,
+                    }.RegisterInNameScope(scope);
+            });
+        }
+    }
+}