chore: add avalonia DataValidationErrors demo.

This commit is contained in:
NaBian committed 2025-08-06 22:09:56 +08:00
1 parent 5de152b20e
commit aa67dea033
8 files changed
+246 -162

No files matched your search

@@ -1,4 +1,5 @@
using System.Globalization;
using System.Linq;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
@@ -17,7 +18,7 @@ public class App : Application
public override void OnFrameworkInitializationCompleted()
{
Properties.Langs.Lang.Culture = new CultureInfo("en");
BindingPlugins.DataValidators.RemoveAt(0);
RemoveDefaultDataValidator();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
@@ -26,4 +27,17 @@ public class App : Application
base.OnFrameworkInitializationCompleted();
}
private static void RemoveDefaultDataValidator()
{
// Get an array of plugins to remove
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
// remove each entry found
foreach (var plugin in dataValidationPluginsToRemove)
{
BindingPlugins.DataValidators.Remove(plugin);
}
}
}
@@ -1,12 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="HandyControlDemo.UserControl.TextBoxDemo">
xmlns:vm="clr-namespace:HandyControlDemo.ViewModel"
x:Class="HandyControlDemo.UserControl.TextBoxDemo"
DataContext="{Binding InputElementDemo, Source={x:Static vm:ViewModelLocator.Instance}}">
<ScrollViewer>
<hc:UniformSpacingPanel Spacing="32"
Margin="32"
@@ -32,6 +29,7 @@
Text="This is the content"
IsEnabled="False" />
<TextBox Watermark="please enter email"
Text="{Binding Email1, UpdateSourceTrigger=PropertyChanged}"
hc:TitleElement.Title="This item must be filled in"
Theme="{StaticResource TextBoxExtend}"
hc:InfoElement.Necessary="True"
@@ -44,6 +42,7 @@
Margin="0,32,0,0"
Text="This is the content" />
<TextBox Width="380"
Text="{Binding Text1, UpdateSourceTrigger=PropertyChanged}"
hc:TitleElement.TitleWidth="120"
Watermark="Please enter content"
hc:TitleElement.TitlePlacement="Left"
@@ -75,6 +74,7 @@
Text="This is the content"
IsEnabled="False" />
<TextBox Watermark="please enter email"
Text="{Binding Email2, UpdateSourceTrigger=PropertyChanged}"
hc:TitleElement.Title="This item must be filled in"
Theme="{StaticResource TextBoxExtend.Small}"
hc:InfoElement.Necessary="True"
@@ -87,6 +87,7 @@
Margin="0,32,0,0"
Text="This is the content" />
<TextBox Width="380"
Text="{Binding Text2, UpdateSourceTrigger=PropertyChanged}"
hc:TitleElement.TitleWidth="120"
Watermark="Please enter content"
hc:TitleElement.TitlePlacement="Left"
@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
namespace HandyControlDemo.ViewModel;
public partial class InputElementDemoViewModel : ObservableValidator
{
[Required] [EmailAddress] [ObservableProperty] [NotifyDataErrorInfo]
private string? _email1;
[Required] [EmailAddress] [ObservableProperty] [NotifyDataErrorInfo]
private string? _email2;
[Required] [ObservableProperty] [NotifyDataErrorInfo]
private string? _text1;
[Required] [ObservableProperty] [NotifyDataErrorInfo]
private string? _text2;
}
@@ -3,13 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace HandyControlDemo.ViewModel;
public class DemoViewModelBase<T> : ObservableObject
public partial class DemoViewModelBase<T> : ObservableObject
{
private IList<T> _dataList = [];
public IList<T> DataList
{
get => _dataList;
set => SetProperty(ref _dataList, value);
}
[ObservableProperty] private IList<T> _dataList = [];
}
@@ -19,9 +19,12 @@ public class ViewModelLocator
services.AddSingleton<DataService>();
services.AddTransient<MainViewModel>();
services.AddTransient<InputElementDemoViewModel>();
_serviceProvider = services.BuildServiceProvider();
}
public MainViewModel Main => _serviceProvider.GetService<MainViewModel>()!;
public InputElementDemoViewModel InputElementDemo => _serviceProvider.GetService<InputElementDemoViewModel>()!;
}
@@ -0,0 +1,50 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTheme x:Key="{x:Type DataValidationErrors}"
TargetType="DataValidationErrors">
<Setter Property="Template">
<ControlTemplate>
<DockPanel LastChildFill="True">
<ContentControl x:DataType="DataValidationErrors"
Content="{Binding (DataValidationErrors.Errors)}"
ContentTemplate="{TemplateBinding ErrorTemplate}"
DataContext="{TemplateBinding Owner}"
DockPanel.Dock="Right"
IsVisible="{Binding (DataValidationErrors.HasErrors)}" />
<ContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}" />
</DockPanel>
</ControlTemplate>
</Setter>
<Setter Property="ErrorTemplate">
<DataTemplate>
<Canvas Width="16"
Height="16"
Margin="4,0,1,0"
Background="Transparent">
<Canvas.Styles>
<Style Selector="ToolTip">
<Setter Property="BorderBrush"
Value="{DynamicResource DangerBrush}" />
</Style>
</Canvas.Styles>
<ToolTip.Tip>
<ItemsControl x:DataType="DataValidationErrors"
ItemsSource="{Binding}" />
</ToolTip.Tip>
<Path Data="{StaticResource WarningGeometry}"
Width="16"
Height="16"
Fill="{DynamicResource DangerBrush}"
Stretch="Uniform" />
</Canvas>
</DataTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>
@@ -97,6 +97,11 @@
<Setter Property="BorderBrush"
Value="{DynamicResource PrimaryBrush}" />
</Style>
<Style Selector="^:error /template/ Border#border">
<Setter Property="BorderBrush"
Value="{DynamicResource DangerBrush}" />
</Style>
</ControlTheme>
<ControlTemplate x:Key="TextBoxExtendTopTemplate"
@@ -121,77 +126,78 @@
TextTrimming="CharacterEllipsis"
Text="{Binding Path=(hc:TitleElement.Title), RelativeSource={RelativeSource TemplatedParent}}" />
</DockPanel>
<Grid Grid.Row="1"
ColumnDefinitions="*,Auto">
<Border x:Name="border"
Grid.ColumnSpan="2"
CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" />
<TextBlock Grid.Column="0"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="Stretch"
Theme="{StaticResource TextBlockDefaultThiLight}"
Text="{TemplateBinding Watermark}" />
<ScrollViewer Grid.Column="0"
Name="PART_ScrollViewer"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<TextPresenter Name="PART_TextPresenter"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
CaretBlinkInterval="{TemplateBinding CaretBlinkInterval}"
CaretBrush="{TemplateBinding CaretBrush}"
CaretIndex="{TemplateBinding CaretIndex}"
LineHeight="{TemplateBinding LineHeight}"
LetterSpacing="{TemplateBinding LetterSpacing}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionEnd="{TemplateBinding SelectionEnd}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
SelectionStart="{TemplateBinding SelectionStart}"
Text="{TemplateBinding Text,Mode=TwoWay}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" />
<ScrollViewer.Styles>
<Style Selector="ScrollContentPresenter#PART_ContentPresenter">
<Setter Property="Cursor"
Value="IBeam" />
</Style>
</ScrollViewer.Styles>
</ScrollViewer>
<Button HorizontalContentAlignment="Left"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
hc:IconElement.Width="14"
Command="{Binding $parent[TextBox].Clear}"
Name="ButtonClear"
Focusable="False"
Grid.Column="1"
Theme="{StaticResource ButtonIcon}"
Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ThicknessSplitConverter}, ConverterParameter='0,0,1,0'}"
hc:IconElement.Geometry="{StaticResource DeleteFillCircleGeometry}"
Foreground="{Binding BorderBrush,ElementName=border}">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsPointerOver" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="(hc:InfoElement.ShowClearButton)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsReadOnly"
Converter="{x:Static BoolConverters.Not}" />
</MultiBinding>
</Button.IsVisible>
</Button>
</Grid>
<DataValidationErrors Grid.Row="1">
<Grid ColumnDefinitions="*,Auto">
<Border x:Name="border"
Grid.ColumnSpan="2"
CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" />
<TextBlock Grid.Column="0"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="Stretch"
Theme="{StaticResource TextBlockDefaultThiLight}"
Text="{TemplateBinding Watermark}" />
<ScrollViewer Grid.Column="0"
Name="PART_ScrollViewer"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<TextPresenter Name="PART_TextPresenter"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
CaretBlinkInterval="{TemplateBinding CaretBlinkInterval}"
CaretBrush="{TemplateBinding CaretBrush}"
CaretIndex="{TemplateBinding CaretIndex}"
LineHeight="{TemplateBinding LineHeight}"
LetterSpacing="{TemplateBinding LetterSpacing}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionEnd="{TemplateBinding SelectionEnd}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
SelectionStart="{TemplateBinding SelectionStart}"
Text="{TemplateBinding Text,Mode=TwoWay}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" />
<ScrollViewer.Styles>
<Style Selector="ScrollContentPresenter#PART_ContentPresenter">
<Setter Property="Cursor"
Value="IBeam" />
</Style>
</ScrollViewer.Styles>
</ScrollViewer>
<Button HorizontalContentAlignment="Left"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
hc:IconElement.Width="14"
Command="{Binding $parent[TextBox].Clear}"
Name="ButtonClear"
Focusable="False"
Grid.Column="1"
Theme="{StaticResource ButtonIcon}"
Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ThicknessSplitConverter}, ConverterParameter='0,0,1,0'}"
hc:IconElement.Geometry="{StaticResource DeleteFillCircleGeometry}"
Foreground="{Binding BorderBrush,ElementName=border}">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsPointerOver" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="(hc:InfoElement.ShowClearButton)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsReadOnly"
Converter="{x:Static BoolConverters.Not}" />
</MultiBinding>
</Button.IsVisible>
</Button>
</Grid>
</DataValidationErrors>
</Grid>
</ControlTemplate>
@@ -218,77 +224,78 @@
TextTrimming="CharacterEllipsis"
Text="{Binding Path=(hc:TitleElement.Title), RelativeSource={RelativeSource TemplatedParent}}" />
</DockPanel>
<Grid Grid.Column="1"
ColumnDefinitions="*,Auto">
<Border x:Name="border"
Grid.ColumnSpan="2"
CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" />
<TextBlock Grid.Column="0"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="Stretch"
Theme="{StaticResource TextBlockDefaultThiLight}"
Text="{TemplateBinding Watermark}" />
<ScrollViewer Grid.Column="0"
Name="PART_ScrollViewer"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<TextPresenter Name="PART_TextPresenter"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
CaretBlinkInterval="{TemplateBinding CaretBlinkInterval}"
CaretBrush="{TemplateBinding CaretBrush}"
CaretIndex="{TemplateBinding CaretIndex}"
LineHeight="{TemplateBinding LineHeight}"
LetterSpacing="{TemplateBinding LetterSpacing}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionEnd="{TemplateBinding SelectionEnd}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
SelectionStart="{TemplateBinding SelectionStart}"
Text="{TemplateBinding Text,Mode=TwoWay}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" />
<ScrollViewer.Styles>
<Style Selector="ScrollContentPresenter#PART_ContentPresenter">
<Setter Property="Cursor"
Value="IBeam" />
</Style>
</ScrollViewer.Styles>
</ScrollViewer>
<Button HorizontalContentAlignment="Left"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
hc:IconElement.Width="14"
Command="{Binding $parent[TextBox].Clear}"
Name="ButtonClear"
Focusable="False"
Grid.Column="1"
Theme="{StaticResource ButtonIcon}"
Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ThicknessSplitConverter}, ConverterParameter='0,0,1,0'}"
hc:IconElement.Geometry="{StaticResource DeleteFillCircleGeometry}"
Foreground="{Binding BorderBrush,ElementName=border}">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsPointerOver" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="(hc:InfoElement.ShowClearButton)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsReadOnly"
Converter="{x:Static BoolConverters.Not}" />
</MultiBinding>
</Button.IsVisible>
</Button>
</Grid>
<DataValidationErrors Grid.Column="1">
<Grid ColumnDefinitions="*,Auto">
<Border x:Name="border"
Grid.ColumnSpan="2"
CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" />
<TextBlock Grid.Column="0"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="Stretch"
Theme="{StaticResource TextBlockDefaultThiLight}"
Text="{TemplateBinding Watermark}" />
<ScrollViewer Grid.Column="0"
Name="PART_ScrollViewer"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<TextPresenter Name="PART_TextPresenter"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
CaretBlinkInterval="{TemplateBinding CaretBlinkInterval}"
CaretBrush="{TemplateBinding CaretBrush}"
CaretIndex="{TemplateBinding CaretIndex}"
LineHeight="{TemplateBinding LineHeight}"
LetterSpacing="{TemplateBinding LetterSpacing}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionEnd="{TemplateBinding SelectionEnd}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
SelectionStart="{TemplateBinding SelectionStart}"
Text="{TemplateBinding Text,Mode=TwoWay}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" />
<ScrollViewer.Styles>
<Style Selector="ScrollContentPresenter#PART_ContentPresenter">
<Setter Property="Cursor"
Value="IBeam" />
</Style>
</ScrollViewer.Styles>
</ScrollViewer>
<Button HorizontalContentAlignment="Left"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
hc:IconElement.Width="14"
Command="{Binding $parent[TextBox].Clear}"
Name="ButtonClear"
Focusable="False"
Grid.Column="1"
Theme="{StaticResource ButtonIcon}"
Padding="{Binding Padding, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ThicknessSplitConverter}, ConverterParameter='0,0,1,0'}"
hc:IconElement.Geometry="{StaticResource DeleteFillCircleGeometry}"
Foreground="{Binding BorderBrush,ElementName=border}">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsPointerOver" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="(hc:InfoElement.ShowClearButton)" />
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="IsReadOnly"
Converter="{x:Static BoolConverters.Not}" />
</MultiBinding>
</Button.IsVisible>
</Button>
</Grid>
</DataValidationErrors>
</Grid>
</ControlTemplate>
@@ -15,6 +15,7 @@
<MergeResourceInclude Source="/Themes/Basic/Brushes.axaml" />
<MergeResourceInclude Source="/Themes/Basic/Converters.axaml" />
<MergeResourceInclude Source="/Themes/Styles/DataValidationErrors.axaml" />
<MergeResourceInclude Source="/Themes/Styles/TextBlock.axaml" />
<MergeResourceInclude Source="/Themes/Styles/ContentControl.axaml" />
<MergeResourceInclude Source="/Themes/Styles/UserControl.axaml" />
@@ -39,9 +40,4 @@
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Styles.Resources>
<Style Selector=":is(UserControl)">
<Setter Property="Theme"
Value="{DynamicResource {x:Type UserControl}}" />
</Style>
</Styles>