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, 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 =
AvaloniaProperty.Register<TextBox, bool>(nameof(IsReadOnly));
@ -91,6 +96,7 @@ namespace Avalonia.Controls
private TextPresenter _presenter;
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _ignoreTextChanges;
private IEnumerable<Exception> _dataValidationErrors;
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
{
get { return GetValue(IsReadOnlyProperty); }
@ -473,7 +485,18 @@ namespace Avalonia.Controls
{
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="ThemeAccentBrush3">#66119EDA</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="ThemeDisabledOpacity">0.5</sys:Double>

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

@ -36,13 +36,24 @@
Opacity="0.5"
Text="{TemplateBinding Watermark}"
IsVisible="{TemplateBinding Path=Text, Converter={Static StringConverters.NullOrEmpty}}"/>
<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 LastChildFill="True">
<Panel Name="error" DockPanel.Dock="Right">
<ToolTip.Tip>
<ItemsControl Items="{TemplateBinding DataValidationErrors}" MemberSelector="Message"/>
</ToolTip.Tip>
<!-- TODO: Draw as a path -->
<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>
</ScrollViewer>
@ -58,6 +69,15 @@
<Setter Property="BorderBrush" Value="{StyleResource ThemeBorderDarkBrush}"/>
</Style>
<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"/>
</Style>
</Styles>

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

@ -98,7 +98,7 @@
<Compile Include="LayoutTransformControlTests.cs" />
<Compile Include="Presenters\ItemsPresenterTests_Virtualization_Simple.cs" />
<Compile Include="Presenters\ItemsPresenterTests_Virtualization.cs" />
<Compile Include="TextBoxTests_ValidationState.cs" />
<Compile Include="TextBoxTests_DataValidation.cs" />
<Compile Include="UserControlTests.cs" />
<Compile Include="DockPanelTests.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.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
@ -16,7 +17,7 @@ using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class TextBoxTests_ValidationState
public class TextBoxTests_DataValidation
{
[Fact]
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(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());
Loading…
Cancel
Save