Browse Source

Started adding TextBox error adornments.

pull/691/head
Steven Kirk 10 years ago
parent
commit
41ba3b7782
  1. 25
      src/Avalonia.Controls/TextBox.cs
  2. 1
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  3. 34
      src/Avalonia.Themes.Default/TextBox.xaml
  4. 2
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
  5. 26
      tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs

25
src/Avalonia.Controls/TextBox.cs

@ -35,6 +35,11 @@ namespace Avalonia.Controls
o => o.CaretIndex, o => o.CaretIndex,
(o, v) => o.CaretIndex = v); (o, v) => o.CaretIndex = v);
public static readonly DirectProperty<TextBox, IEnumerable<Exception>> DataValidationErrorsProperty =
AvaloniaProperty.RegisterDirect<TextBox, IEnumerable<Exception>>(
nameof(DataValidationErrors),
o => o.DataValidationErrors);
public static readonly StyledProperty<bool> IsReadOnlyProperty = public static readonly StyledProperty<bool> IsReadOnlyProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(IsReadOnly)); AvaloniaProperty.Register<TextBox, bool>(nameof(IsReadOnly));
@ -91,6 +96,7 @@ namespace Avalonia.Controls
private TextPresenter _presenter; private TextPresenter _presenter;
private UndoRedoHelper<UndoRedoState> _undoRedoHelper; private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _ignoreTextChanges; private bool _ignoreTextChanges;
private IEnumerable<Exception> _dataValidationErrors;
static TextBox() static TextBox()
{ {
@ -147,6 +153,12 @@ namespace Avalonia.Controls
} }
} }
public IEnumerable<Exception> DataValidationErrors
{
get { return _dataValidationErrors; }
private set { SetAndRaise(DataValidationErrorsProperty, ref _dataValidationErrors, value); }
}
public bool IsReadOnly public bool IsReadOnly
{ {
get { return GetValue(IsReadOnlyProperty); } get { return GetValue(IsReadOnlyProperty); }
@ -473,7 +485,18 @@ namespace Avalonia.Controls
{ {
if (property == TextProperty) if (property == TextProperty)
{ {
((IPseudoClasses)Classes).Set(":error", status.ErrorType != BindingErrorType.None); var classes = (IPseudoClasses)Classes;
if (status.ErrorType == BindingErrorType.None)
{
classes.Remove(":error");
DataValidationErrors = null;
}
else
{
classes.Add(":error");
DataValidationErrors = new[] { status.Error };
}
} }
} }

1
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -18,6 +18,7 @@
<SolidColorBrush x:Key="ThemeAccentBrush2">#99119EDA</SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush2">#99119EDA</SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush3">#66119EDA</SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush3">#66119EDA</SolidColorBrush>
<SolidColorBrush x:Key="ThemeAccentBrush4">#33119EDA</SolidColorBrush> <SolidColorBrush x:Key="ThemeAccentBrush4">#33119EDA</SolidColorBrush>
<SolidColorBrush x:Key="ErrorBrush">Red</SolidColorBrush>
<sys:Double x:Key="ThemeBorderThickness">2</sys:Double> <sys:Double x:Key="ThemeBorderThickness">2</sys:Double>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double> <sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>

34
src/Avalonia.Themes.Default/TextBox.xaml

@ -36,13 +36,24 @@
Opacity="0.5" Opacity="0.5"
Text="{TemplateBinding Watermark}" Text="{TemplateBinding Watermark}"
IsVisible="{TemplateBinding Path=Text, Converter={Static StringConverters.NullOrEmpty}}"/> IsVisible="{TemplateBinding Path=Text, Converter={Static StringConverters.NullOrEmpty}}"/>
<TextPresenter Name="PART_TextPresenter" <DockPanel LastChildFill="True">
CaretIndex="{TemplateBinding CaretIndex}" <Panel Name="error" DockPanel.Dock="Right">
SelectionStart="{TemplateBinding SelectionStart}" <ToolTip.Tip>
SelectionEnd="{TemplateBinding SelectionEnd}" <ItemsControl Items="{TemplateBinding DataValidationErrors}" MemberSelector="Message"/>
Text="{TemplateBinding Text, Mode=TwoWay}" </ToolTip.Tip>
TextAlignment="{TemplateBinding TextAlignment}" <!-- TODO: Draw as a path -->
TextWrapping="{TemplateBinding TextWrapping}"/> <Ellipse Width="14" Height="14" Stroke="{StyleResource ErrorBrush}" StrokeThickness="1"/>
<Path Data="M7,4l0,5" Stroke="{StyleResource ErrorBrush}" StrokeThickness="2"/>
<Path Data="M7,10l0,2" Stroke="{StyleResource ErrorBrush}" StrokeThickness="2"/>
</Panel>
<TextPresenter Name="PART_TextPresenter"
CaretIndex="{TemplateBinding CaretIndex}"
SelectionStart="{TemplateBinding SelectionStart}"
SelectionEnd="{TemplateBinding SelectionEnd}"
Text="{TemplateBinding Text, Mode=TwoWay}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"/>
</DockPanel>
</Panel> </Panel>
</ScrollViewer> </ScrollViewer>
@ -58,6 +69,15 @@
<Setter Property="BorderBrush" Value="{StyleResource ThemeBorderDarkBrush}"/> <Setter Property="BorderBrush" Value="{StyleResource ThemeBorderDarkBrush}"/>
</Style> </Style>
<Style Selector="TextBox:error /template/ Border#border"> <Style Selector="TextBox:error /template/ Border#border">
<Setter Property="BorderBrush" Value="{StyleResource ErrorBrush}"/>
</Style>
<Style Selector="TextBox /template/ Panel#error">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="TextBox:error /template/ Panel#error">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="TextBox /template/ ToolTip">
<Setter Property="BorderBrush" Value="Red"/> <Setter Property="BorderBrush" Value="Red"/>
</Style> </Style>
</Styles> </Styles>

2
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@ -98,7 +98,7 @@
<Compile Include="LayoutTransformControlTests.cs" /> <Compile Include="LayoutTransformControlTests.cs" />
<Compile Include="Presenters\ItemsPresenterTests_Virtualization_Simple.cs" /> <Compile Include="Presenters\ItemsPresenterTests_Virtualization_Simple.cs" />
<Compile Include="Presenters\ItemsPresenterTests_Virtualization.cs" /> <Compile Include="Presenters\ItemsPresenterTests_Virtualization.cs" />
<Compile Include="TextBoxTests_ValidationState.cs" /> <Compile Include="TextBoxTests_DataValidation.cs" />
<Compile Include="UserControlTests.cs" /> <Compile Include="UserControlTests.cs" />
<Compile Include="DockPanelTests.cs" /> <Compile Include="DockPanelTests.cs" />
<Compile Include="EnumerableExtensions.cs" /> <Compile Include="EnumerableExtensions.cs" />

26
tests/Avalonia.Controls.UnitTests/TextBoxTests_ValidationState.cs → tests/Avalonia.Controls.UnitTests/TextBoxTests_DataValidation.cs

@ -5,6 +5,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
@ -16,7 +17,7 @@ using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
{ {
public class TextBoxTests_ValidationState public class TextBoxTests_DataValidation
{ {
[Fact] [Fact]
public void Setter_Exceptions_Should_Set_Error_Pseudoclass() public void Setter_Exceptions_Should_Set_Error_Pseudoclass()
@ -40,6 +41,29 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Setter_Exceptions_Should_Set_DataValidationErrors()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
DataContext = new ExceptionTest(),
[!TextBox.TextProperty] = new Binding(nameof(ExceptionTest.LessThan10), BindingMode.TwoWay),
Template = CreateTemplate(),
};
target.ApplyTemplate();
Assert.Null(target.DataValidationErrors);
target.Text = "20";
Assert.Equal(1, target.DataValidationErrors.Count());
Assert.IsType<InvalidOperationException>(target.DataValidationErrors.Single());
target.Text = "1";
Assert.Null(target.DataValidationErrors);
}
}
private static TestServices Services => TestServices.MockThreadingInterface.With( private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>()); standardCursorFactory: Mock.Of<IStandardCursorFactory>());
Loading…
Cancel
Save