WPF开发中的第三方库:ValueConverters的使用及属性验证方式


在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

如果本文介绍对你有帮助,可以一键四连:点赞+评论+收藏+推荐,谢谢!