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