
在wpf开发中,你会经常遇到一些需要验证填写内容不能为空,或者是其他的一些规则,比如正则表达式等,以下就是一个示例,同时提供了很多种方式。
1.方式1.使用第三方库:ValueConverters
第一步:在项目中nuget引用ValueConverters
第二步:新建View:ValueConverterView
<Window x:Class="WPFDemoMVVM.View.ValueConverterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:WPFDemoMVVM.View"
xmlns:conv="clr-namespace:ValueConverters;assembly=ValueConverters"
xmlns:vm="clr-namespace:WPFDemoMVVM.ViewModel"
xmlns:hp="clr-namespace:WPFDemoMVVM.Helpers"
WindowStartupLocation="CenterOwner"
WindowStyle="ToolWindow"
ResizeMode="NoResize"
mc:Ignorable="d"
Title="ValueConverterView" Height="600" Width="450">
<Window.Resources>
<conv:BoolToVisibilityConverter x:Key="AgreeConvert" IsInverted="True"/>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="TextBlockShowTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="ErrorStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Margin" Value="0 2"/>
</Style>
<Style x:Key="StackPanelStyle" TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0 10"/>
</Style>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="PasswordBoxStyle" TargetType="PasswordBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
</Window.Resources>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}" >
<TextBlock Text="用户名:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="userName" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBlock Text="年龄:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="age" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBlock Text="密码:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="passord" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<CheckBox Name="agree" Content="我已阅读" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 5"/>
</StackPanel>
<StackPanel Margin="0 10 0 0" HorizontalAlignment="Center">
<TextBlock Style="{StaticResource ErrorStyle}" Text="用户名不能为空" Visibility="{Binding ElementName=userName,Path=Text,Converter={StaticResource userNameToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="年龄需要在18-99" Visibility="{Binding ElementName=age,Path=Text,Converter={StaticResource ageToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="密码长度不能小于8位" Visibility="{Binding ElementName=passord,Path=Text.Length,Converter={StaticResource passwordToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="需要勾选我已阅读" Visibility="{Binding ElementName=agree,Path=IsChecked,Converter={StaticResource AgreeConvert}}"/>
</StackPanel>
<Border BorderThickness="1" BorderBrush="Gray" Margin="0 5"></Border>
</StackPanel>
</Grid>
</Window>
第三步:引用命名空间:xmlns:conv=”clr-namespace:ValueConverters;assembly=ValueConverters”,并使用ValueConverterGroup定义每个不一样的验证,并显示,如果按照规则填好,就取消错误提示。
<Window x:Class="WPFDemoMVVM.View.ValueConverterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:WPFDemoMVVM.View"
xmlns:conv="clr-namespace:ValueConverters;assembly=ValueConverters"
xmlns:vm="clr-namespace:WPFDemoMVVM.ViewModel"
xmlns:hp="clr-namespace:WPFDemoMVVM.Helpers"
WindowStartupLocation="CenterOwner"
WindowStyle="ToolWindow"
ResizeMode="NoResize"
mc:Ignorable="d"
Title="ValueConverterView" Height="600" Width="450">
<Window.Resources>
<conv:BoolToVisibilityConverter x:Key="AgreeConvert" IsInverted="True"/>
<conv:ValueConverterGroup x:Key="userNameToVisibilityConverter">
<conv:StringIsNotNullOrEmptyConverter/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:ValueConverterGroup x:Key="passwordToVisibilityConverter">
<conv:IsInRangeConverter MaxValue="16" MinValue="6"/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:ValueConverterGroup x:Key="ageToVisibilityConverter">
<conv:StringToDecimalConverter/>
<conv:IsInRangeConverter MaxValue="99" MinValue="18"/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:StringIsNotNullOrEmptyConverter x:Key="StringIsNotNullOrEmptyConverter"/>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="TextBlockShowTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="ErrorStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Margin" Value="0 2"/>
</Style>
<Style x:Key="StackPanelStyle" TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0 10"/>
</Style>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="PasswordBoxStyle" TargetType="PasswordBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
</Window.Resources>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}" >
<TextBlock Text="用户名:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="userName" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBlock Text="年龄:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="age" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBlock Text="密码:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="passord" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<CheckBox Name="agree" Content="我已阅读" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 5"/>
</StackPanel>
<StackPanel Margin="0 10 0 0" HorizontalAlignment="Center">
<TextBlock Style="{StaticResource ErrorStyle}" Text="用户名不能为空" Visibility="{Binding ElementName=userName,Path=Text,Converter={StaticResource userNameToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="年龄需要在18-99" Visibility="{Binding ElementName=age,Path=Text,Converter={StaticResource ageToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="密码长度不能小于8位" Visibility="{Binding ElementName=passord,Path=Text.Length,Converter={StaticResource passwordToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="需要勾选我已阅读" Visibility="{Binding ElementName=agree,Path=IsChecked,Converter={StaticResource AgreeConvert}}"/>
</StackPanel>
<Border BorderThickness="1" BorderBrush="Gray" Margin="0 5"></Border>
</StackPanel>
</Grid>
</Window>
效果如下:
内容填充完,红色提示消失,
当然,这还不算完善,我们可以使用一些更加完善的方式。如以下的形式
2.方式2:使用IDataErrorInfo,直接后台绑定需要提示的内容:
第一步:View页面展示如下:
<Window x:Class="WPFDemoMVVM.View.ValueConverterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:WPFDemoMVVM.View"
xmlns:conv="clr-namespace:ValueConverters;assembly=ValueConverters"
xmlns:vm="clr-namespace:WPFDemoMVVM.ViewModel"
xmlns:hp="clr-namespace:WPFDemoMVVM.Helpers"
WindowStartupLocation="CenterOwner"
WindowStyle="ToolWindow"
ResizeMode="NoResize"
mc:Ignorable="d"
Title="ValueConverterView" Height="600" Width="450">
<Window.Resources>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="TextBlockShowTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="ErrorStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Margin" Value="0 2"/>
</Style>
<Style x:Key="StackPanelStyle" TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0 10"/>
</Style>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="PasswordBoxStyle" TargetType="PasswordBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
</Window.Resources>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
</StackPanel>
<StackPanel Grid.Row="1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="用户名:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding UserName,ValidatesOnExceptions=True}" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="密码:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding Password,ValidatesOnDataErrors=True}" Width="100" Height="24"/>
<!--<PasswordBox hp:PasswordBoxHelper.BoundPassword="{Binding Passord,ValidatesOnDataErrors=True}" />-->
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="年龄:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding Age,ValidatesOnDataErrors=True}" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<TextBox Margin="5" Text="{Binding Error,Mode=OneWay}" Foreground="red" Height="80"/>
<Button Content="注册" Command="{Binding RegisterCommand}" Margin="0 20"/>
</StackPanel>
</Grid>
</Window>
新建ValueConverterViewModel,继承:ObservableObject,IDataErrorInfo类
public partial class ValueConverterViewModel:ObservableObject,IDataErrorInfo
{
private string? userName;
public string? UserName
{
get => userName;
set
{
SetProperty(ref userName, value);
OnPropertyChanged(nameof(Error));
}
}
private int age;
public int Age
{
get => age;
set
{
SetProperty(ref age, value);
OnPropertyChanged(nameof(Error));
}
}
public string Error
{
get
{
var errors = new List<string>
{
this[nameof(UserName)],
this[nameof(Age)],
this[nameof(Password)]
};
return string.Join(Environment.NewLine, errors.Where(t => !string.IsNullOrEmpty(t)));
}
}
private string? password;
public string? Password
{
get => password;
set
{
SetProperty(ref password, value);
OnPropertyChanged(nameof(Error));
}
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case nameof(UserName) when string.IsNullOrWhiteSpace(UserName):
return "用户名必须不为空";
case nameof(UserName) when UserName.Length is < 6 or > 10:
return "用户名长度必须在6和10之间";
case nameof(Age) when Age is < 18 or > 110:
return "年龄必须在18和110之间";
case nameof(Password) when string.IsNullOrWhiteSpace(Password):
return "密码必须不为空";
case nameof(Password) when Password.Length is < 8 or > 20:
return "密码长度必须在8和20之间";
default:
return string.Empty;
}
}
}
[RelayCommand]
public void Register()
{
if (Error.Count() > 0)
{
return;
}
MessageBox.Show("注册成功");
}
}
效果如下:
验证通过,点击注册,效果如下:
3.FluentValidation + INotifyDataErrorInfo 实现验证【推荐使用】
3.1 界面View如下:
<Window x:Class="WPFDemoMVVM.View.ValueConverterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:WPFDemoMVVM.View"
xmlns:conv="clr-namespace:ValueConverters;assembly=ValueConverters"
xmlns:vm="clr-namespace:WPFDemoMVVM.ViewModel"
xmlns:hp="clr-namespace:WPFDemoMVVM.Helpers"
WindowStartupLocation="CenterOwner"
WindowStyle="ToolWindow"
ResizeMode="NoResize"
mc:Ignorable="d"
Title="ValueConverterView" Height="600" Width="450">
<Window.Resources>
<conv:BoolToVisibilityConverter x:Key="AgreeConvert" IsInverted="True"/>
<conv:ValueConverterGroup x:Key="userNameToVisibilityConverter">
<conv:StringIsNotNullOrEmptyConverter/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:ValueConverterGroup x:Key="passwordToVisibilityConverter">
<conv:IsInRangeConverter MaxValue="16" MinValue="6"/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:ValueConverterGroup x:Key="ageToVisibilityConverter">
<conv:StringToDecimalConverter/>
<conv:IsInRangeConverter MaxValue="99" MinValue="18"/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:StringIsNotNullOrEmptyConverter x:Key="StringIsNotNullOrEmptyConverter"/>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="TextBlockShowTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="ErrorStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Margin" Value="0 2"/>
</Style>
<Style x:Key="StackPanelStyle" TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0 10"/>
</Style>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="PasswordBoxStyle" TargetType="PasswordBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<ControlTemplate x:Key="ValidationErrorTemplate">
<DockPanel>
<TextBlock Foreground="Red" DockPanel.Dock="Bottom" FontSize="12" Text="{Binding [0].ErrorContent}" />
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
</Window.Resources>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}" >
<TextBlock Text="用户名:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="userName" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBlock Text="年龄:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="age" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBlock Text="密码:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="passord" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<CheckBox Name="agree" Content="我已阅读" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 5"/>
</StackPanel>
<StackPanel Margin="0 10 0 0" HorizontalAlignment="Center">
<TextBlock Style="{StaticResource ErrorStyle}" Text="用户名不能为空" Visibility="{Binding ElementName=userName,Path=Text,Converter={StaticResource userNameToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="年龄需要在18-99" Visibility="{Binding ElementName=age,Path=Text,Converter={StaticResource ageToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="密码长度不能小于8位" Visibility="{Binding ElementName=passord,Path=Text.Length,Converter={StaticResource passwordToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="需要勾选我已阅读" Visibility="{Binding ElementName=agree,Path=IsChecked,Converter={StaticResource AgreeConvert}}"/>
</StackPanel>
<Border BorderThickness="1" BorderBrush="Gray" Margin="0 5"></Border>
</StackPanel>
<StackPanel Grid.Row="1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="用户名:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding UserName,ValidatesOnExceptions=True,NotifyOnValidationError=True,UpdateSourceTrigger=PropertyChanged}" Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="密码:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding Password,ValidatesOnDataErrors=True,NotifyOnValidationError=True,UpdateSourceTrigger=PropertyChanged}" Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="年龄:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding Age,ValidatesOnDataErrors=True,NotifyOnValidationError=True,UpdateSourceTrigger=PropertyChanged}" Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<Button Content="注册" Command="{Binding RegisterCommand}" Margin="0 20"/>
</StackPanel>
</Grid>
</Window>
当前的 View 使用了 INotifyDataErrorInfo 搭配 FluentValidation 进行验证,已经是很现代且推荐的方式,但 WPF 的默认控件不直接显示错误提示文本,还需要设置 验证模板 (ValidationTemplate) 或使用 Adorner 来显示错误提示。绑定需要设置 NotifyOnValidationError=True。
3.2 安装FluentValidation
Install-Package FluentValidation
3.3 新建ViewModel类:ValueConverterViewModel,继承ObservableObject, INotifyDataErrorInfo
public partial class ValueConverterViewModel : ObservableObject, INotifyDataErrorInfo
{
private readonly IValidator<ValueConverterViewModel> _validator;
private readonly Dictionary<string, List<string>> _errors = new();
public ValueConverterViewModel()
{
_validator = new ValueConverterViewModelValidator();
}
[ObservableProperty]
private string? userName;
[ObservableProperty]
private int age;
[ObservableProperty]
private string? password;
partial void OnUserNameChanged(string? value)
{
ValidateProperty(nameof(UserName));
}
partial void OnAgeChanged(int value)
{
ValidateProperty(nameof(Age));
}
partial void OnPasswordChanged(string? value)
{
ValidateProperty(nameof(Password));
}
public bool HasErrors => _errors.Any();
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public IEnumerable GetErrors(string? propertyName)
{
if (propertyName != null && _errors.TryGetValue(propertyName, out var errors))
{
return errors;
}
return Enumerable.Empty<string>();
}
private void ValidateProperty(string propertyName)
{
//var context = new ValidationContext<ValueConverterViewModel>(this)
// .CloneForMember(propertyName);
// 创建只验证特定属性的 ValidatorSelector
var selector = new MemberNameValidatorSelector(new[] { propertyName });
// 正确创建 ValidationContext 并传入 selector
var context = new ValidationContext<ValueConverterViewModel>(this, new PropertyChain(), selector);
var result = _validator.Validate(context);
// 移除旧错误
if (_errors.ContainsKey(propertyName))
{
_errors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
// 添加新错误
var propertyErrors = result.Errors
.Where(e => e.PropertyName == propertyName)
.Select(e => e.ErrorMessage)
.ToList();
if (propertyErrors.Any())
{
_errors[propertyName] = propertyErrors;
OnErrorsChanged(propertyName);
}
}
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
[RelayCommand(CanExecute = nameof(CanRegister))]
public void Register()
{
MessageBox.Show("注册成功");
}
public bool CanRegister() => !HasErrors;
}
3.4 新建类ValueConverterViewModelValidator,这是用于属性跪着提示类
public class ValueConverterViewModelValidator : AbstractValidator<ValueConverterViewModel>
{
public ValueConverterViewModelValidator()
{
RuleFor(x => x.UserName)
.NotEmpty().WithMessage("用户名必须不为空")
.Length(6, 10).WithMessage("用户名长度必须在6和10之间");
RuleFor(x => x.Age)
.InclusiveBetween(18, 110).WithMessage("年龄必须在18和110之间");
RuleFor(x => x.Password)
.NotEmpty().WithMessage("密码必须不为空")
.Length(8, 20).WithMessage("密码长度必须在8和20之间");
}
}
3.5 效果如下:
4.DataAnnotationsValidator+INotifyDataErrorInfo 实现验证
4.1 新建View页如下:
<Window x:Class="WPFDemoMVVM.View.ValueConverterView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:WPFDemoMVVM.View"
xmlns:conv="clr-namespace:ValueConverters;assembly=ValueConverters"
xmlns:vm="clr-namespace:WPFDemoMVVM.ViewModel"
xmlns:hp="clr-namespace:WPFDemoMVVM.Helpers"
WindowStartupLocation="CenterOwner"
WindowStyle="ToolWindow"
ResizeMode="NoResize"
mc:Ignorable="d"
Title="ValueConverterView" Height="600" Width="450">
<Window.Resources>
<conv:BoolToVisibilityConverter x:Key="AgreeConvert" IsInverted="True"/>
<conv:ValueConverterGroup x:Key="userNameToVisibilityConverter">
<conv:StringIsNotNullOrEmptyConverter/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:ValueConverterGroup x:Key="passwordToVisibilityConverter">
<conv:IsInRangeConverter MaxValue="16" MinValue="6"/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:ValueConverterGroup x:Key="ageToVisibilityConverter">
<conv:StringToDecimalConverter/>
<conv:IsInRangeConverter MaxValue="99" MinValue="18"/>
<conv:BoolInverter/>
<conv:BoolToVisibilityConverter/>
</conv:ValueConverterGroup>
<conv:StringIsNotNullOrEmptyConverter x:Key="StringIsNotNullOrEmptyConverter"/>
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="TextBlockShowTextStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="ErrorStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Margin" Value="0 2"/>
</Style>
<Style x:Key="StackPanelStyle" TargetType="StackPanel">
<Setter Property="Orientation" Value="Horizontal"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0 10"/>
</Style>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="PasswordBoxStyle" TargetType="PasswordBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<ControlTemplate x:Key="ValidationErrorTemplate">
<DockPanel>
<TextBlock Foreground="Red" DockPanel.Dock="Bottom" FontSize="12" Text="{Binding [0].ErrorContent}" />
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
<Style x:Key="TextBoxCommonStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="5 0"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationErrorTemplate}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}" >
<TextBlock Text="用户名:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="userName" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBlock Text="年龄:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="age" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<StackPanel Style="{StaticResource StackPanelStyle}">
<TextBlock Text="密码:" Style="{StaticResource TextBlockShowTextStyle}"/>
<TextBox Name="passord" Style="{StaticResource TextBoxStyle}"/>
</StackPanel>
<CheckBox Name="agree" Content="我已阅读" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 5"/>
</StackPanel>
<StackPanel Margin="0 10 0 0" HorizontalAlignment="Center">
<TextBlock Style="{StaticResource ErrorStyle}" Text="用户名不能为空" Visibility="{Binding ElementName=userName,Path=Text,Converter={StaticResource userNameToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="年龄需要在18-99" Visibility="{Binding ElementName=age,Path=Text,Converter={StaticResource ageToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="密码长度不能小于8位" Visibility="{Binding ElementName=passord,Path=Text.Length,Converter={StaticResource passwordToVisibilityConverter}}"/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="需要勾选我已阅读" Visibility="{Binding ElementName=agree,Path=IsChecked,Converter={StaticResource AgreeConvert}}"/>
</StackPanel>
<Border BorderThickness="1" BorderBrush="Gray" Margin="0 5"></Border>
</StackPanel>
<StackPanel Grid.Row="1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="用户名:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding UserName, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TextBoxCommonStyle}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="密码:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding Password, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TextBoxCommonStyle}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0 10">
<TextBlock Text="年龄:" VerticalAlignment="Bottom" FontSize="16"/>
<TextBox Text="{Binding Age, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TextBoxCommonStyle}"/>
</StackPanel>
<Button Content="注册" Command="{Binding RegisterCommand}" Margin="0 20"/>
</StackPanel>
</Grid>
</Window>
4.2 新建ViewModel:ValueConverterViewModel,继承ObservableValidator
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Windows;
namespace WPFDemoMVVM.ViewModel
{
#region INotifyDataErrorInfo + DataAnnotations 实现属性验证
public partial class ValueConverterViewModel : ObservableValidator
{
private readonly Dictionary<string, List<string>> _errors = new();
[ObservableProperty]
[Required(ErrorMessage = "用户名必须不为空")]
[MinLength(6, ErrorMessage = "用户名长度不能少于6位")]
[MaxLength(10, ErrorMessage = "用户名长度不能超过10位")]
private string? userName;
[ObservableProperty]
[Required(ErrorMessage = "密码必须不为空")]
[MinLength(8, ErrorMessage = "密码长度不能少于8位")]
[MaxLength(20, ErrorMessage = "密码长度不能超过20位")]
private string? password;
[ObservableProperty]
[Range(18, 110, ErrorMessage = "年龄必须在18到110之间")]
private int age;
public ValueConverterViewModel()
{
PropertyChanged += (s, e) => ValidateProperty(e.PropertyName!);
}
private void ValidateProperty(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
return;
var propertyInfo = GetType().GetProperty(propertyName);
if (propertyInfo == null)
return;
var context = new ValidationContext(this)
{
MemberName = propertyName
};
var results = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
var propertyValue = propertyInfo.GetValue(this);
Validator.TryValidateProperty(propertyValue, context, results);
if (_errors.ContainsKey(propertyName))
{
_errors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
if (results.Any())
{
_errors[propertyName] = results.Select(r => r.ErrorMessage!).ToList();
OnErrorsChanged(propertyName);
}
}
public bool HasErrors => _errors.Any();
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public IEnumerable GetErrors(string? propertyName)
{
if (string.IsNullOrEmpty(propertyName))
return _errors.SelectMany(e => e.Value);
return _errors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty<string>();
}
private void OnErrorsChanged(string propertyName) =>
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
[RelayCommand(CanExecute = nameof(CanRegister))]
public void Register()
{
MessageBox.Show("注册成功");
}
public bool CanRegister() => !HasErrors;
}
#endregion
}
效果如下:
源代码地址如下:https://gitee.com/chenshibao/wpfdemo.git
如果本文介绍对你有帮助,可以一键四连:点赞+评论+收藏+推荐,谢谢!