From 941246e75cd456c7df63abac78d2bc3993278d4d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 17 Aug 2016 01:23:13 +0200 Subject: [PATCH] Started to implement data validation on TextBox. Not working still - added a failing test to demonstrate it. --- src/Avalonia.Base/DirectProperty.cs | 11 ++- src/Avalonia.Controls/TextBox.cs | 26 +++--- .../TextBoxTests_ValidationState.cs | 84 ++++++++----------- 3 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index cf325ed269..fad6cf983a 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -85,19 +85,26 @@ namespace Avalonia /// The value to use when the property is set to /// /// The default binding mode for the property. + /// + /// Whether the property is interested in data validation. + /// /// The property. public DirectProperty AddOwner( Func getter, Action setter = null, TValue unsetValue = default(TValue), - BindingMode defaultBindingMode = BindingMode.OneWay) + BindingMode defaultBindingMode = BindingMode.OneWay, + bool enableDataValidation = false) where TNewOwner : AvaloniaObject { var result = new DirectProperty( this, getter, setter, - new DirectPropertyMetadata(unsetValue, defaultBindingMode)); + new DirectPropertyMetadata( + unsetValue: unsetValue, + defaultBindingMode: defaultBindingMode, + enableDataValidation: enableDataValidation)); AvaloniaPropertyRegistry.Instance.Register(typeof(TNewOwner), result); return result; diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 58adb8106f..cecb2c9cb7 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -35,6 +35,9 @@ namespace Avalonia.Controls o => o.CaretIndex, (o, v) => o.CaretIndex = v); + public static readonly StyledProperty IsReadOnlyProperty = + AvaloniaProperty.Register(nameof(IsReadOnly)); + public static readonly DirectProperty SelectionStartProperty = AvaloniaProperty.RegisterDirect( nameof(SelectionStart), @@ -51,7 +54,8 @@ namespace Avalonia.Controls TextBlock.TextProperty.AddOwner( o => o.Text, (o, v) => o.Text = v, - defaultBindingMode: BindingMode.TwoWay); + defaultBindingMode: BindingMode.TwoWay, + enableDataValidation: true); public static readonly StyledProperty TextAlignmentProperty = TextBlock.TextAlignmentProperty.AddOwner(); @@ -65,9 +69,6 @@ namespace Avalonia.Controls public static readonly StyledProperty UseFloatingWatermarkProperty = AvaloniaProperty.Register("UseFloatingWatermark"); - public static readonly StyledProperty IsReadOnlyProperty = - AvaloniaProperty.Register(nameof(IsReadOnly)); - struct UndoRedoState : IEquatable { public string Text { get; } @@ -145,6 +146,12 @@ namespace Avalonia.Controls } } + public bool IsReadOnly + { + get { return GetValue(IsReadOnlyProperty); } + set { SetValue(IsReadOnlyProperty, value); } + } + public int SelectionStart { get @@ -198,12 +205,6 @@ namespace Avalonia.Controls set { SetValue(UseFloatingWatermarkProperty, value); } } - public bool IsReadOnly - { - get { return GetValue(IsReadOnlyProperty); } - set { SetValue(IsReadOnlyProperty, value); } - } - public TextWrapping TextWrapping { get { return GetValue(TextWrappingProperty); } @@ -461,6 +462,11 @@ namespace Avalonia.Controls } } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status) + { + ((IPseudoClasses)Classes).Set(":error", status != null && status.ErrorType != BindingErrorType.None); + } + private int CoerceCaretIndex(int value) { var text = Text; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests_ValidationState.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests_ValidationState.cs index 34de11cc3b..84ce2d59dc 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests_ValidationState.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests_ValidationState.cs @@ -5,8 +5,13 @@ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Markup.Xaml.Data; +using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -14,63 +19,44 @@ namespace Avalonia.Controls.UnitTests public class TextBoxTests_ValidationState { [Fact] - public void Setter_Exceptions_Should_Set_ValidationState() + public void Setter_Exceptions_Should_Set_Error_Pseudoclass() { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + using (UnitTestApplication.Start(Services)) { - var target = new TextBox(); - var binding = new Binding(nameof(ExceptionTest.LessThan10)); - binding.Source = new ExceptionTest(); - ////binding.EnableValidation = true; - target.Bind(TextBox.TextProperty, binding); + var target = new TextBox + { + DataContext = new ExceptionTest(), + [!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10), BindingMode.TwoWay), + Template = CreateTemplate(), + }; - Assert.True(false); - //Assert.True(target.ValidationStatus.IsValid); - //target.Text = "20"; - //Assert.False(target.ValidationStatus.IsValid); - //target.Text = "1"; - //Assert.True(target.ValidationStatus.IsValid); - } - } + target.ApplyTemplate(); - [Fact(Skip = "TODO: Not yet passing")] - public void Unconvertable_Value_Should_Set_ValidationState() - { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) - { - var target = new TextBox(); - var binding = new Binding(nameof(ExceptionTest.LessThan10)); - binding.Source = new ExceptionTest(); - ////binding.EnableValidation = true; - target.Bind(TextBox.TextProperty, binding); - - Assert.True(false); - //Assert.True(target.ValidationStatus.IsValid); - //target.Text = "foo"; - //Assert.False(target.ValidationStatus.IsValid); - //target.Text = "1"; - //Assert.True(target.ValidationStatus.IsValid); + Assert.False(target.Classes.Contains(":error")); + target.Text = "20"; + Assert.True(target.Classes.Contains(":error")); + target.Text = "1"; + Assert.False(target.Classes.Contains(":error")); } } - [Fact] - public void Indei_Should_Set_ValidationState() - { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) - { - var target = new TextBox(); - var binding = new Binding(nameof(ExceptionTest.LessThan10)); - binding.Source = new IndeiTest(); - ////binding.EnableValidation = true; - target.Bind(TextBox.TextProperty, binding); + private static TestServices Services => TestServices.MockThreadingInterface.With( + standardCursorFactory: Mock.Of()); - Assert.True(false); - //Assert.True(target.ValidationStatus.IsValid); - //target.Text = "20"; - //Assert.False(target.ValidationStatus.IsValid); - //target.Text = "1"; - //Assert.True(target.ValidationStatus.IsValid); - } + private IControlTemplate CreateTemplate() + { + return new FuncControlTemplate(control => + new TextPresenter + { + Name = "PART_TextPresenter", + [!!TextPresenter.TextProperty] = new Binding + { + Path = "Text", + Mode = BindingMode.TwoWay, + Priority = BindingPriority.TemplatedParent, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + }, + }); } private class ExceptionTest