WPF开发中的第三方库:ValueConverters的使用及属性验证方式
<h1 id="在wpf开发中你会经常遇到一些需要验证填写内容不能为空或者是其他的一些规则比如正则表达式等以下就是一个示例同时提供了很多种方式">在wpf开发中,你会经常遇到一些需要验证填写内容不能为空,或者是其他的一些规则,比如正则表达式等,以下就是一个示例,同时提供了很多种方式。</h1><h2 id="1方式1使用第三方库valueconverters">1.方式1.使用第三方库:ValueConverters</h2>
<h3 id="第一步在项目中nuget引用valueconverters">第一步:在项目中nuget引用ValueConverters</h3>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202506/2212230-20250630222206394-720169609.png"></p>
<h3 id="第二步新建viewvalueconverterview">第二步:新建View:ValueConverterView</h3>
<pre><code><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).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>
</code></pre>
<h3 id="第三步引用命名空间xmlnsconvclr-namespacevalueconvertersassemblyvalueconverters并使用valueconvertergroup定义每个不一样的验证并显示如果按照规则填好就取消错误提示">第三步:引用命名空间:xmlns:conv="clr-namespace:ValueConverters;assembly=ValueConverters",并使用ValueConverterGroup定义每个不一样的验证,并显示,如果按照规则填好,就取消错误提示。</h3>
<pre><code><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).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>
</code></pre>
<h3 id="效果如下">效果如下:</h3>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202506/2212230-20250630223037360-339881398.png"></p>
<p>内容填充完,红色提示消失,</p>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202506/2212230-20250630223252228-228658667.png"></p>
<p>当然,这还不算完善,我们可以使用一些更加完善的方式。如以下的形式</p>
<h2 id="2方式2使用idataerrorinfo直接后台绑定需要提示的内容">2.方式2:使用IDataErrorInfo,直接后台绑定需要提示的内容:</h2>
<h3 id="第一步view页面展示如下">第一步:View页面展示如下:</h3>
<pre><code><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).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>
</code></pre>
<h3 id="新建valueconverterviewmodel继承observableobjectidataerrorinfo类">新建ValueConverterViewModel,继承:ObservableObject,IDataErrorInfo类</h3>
<pre><code> 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,
this,
this
};
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
{
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 Ageis < 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;
}
}
}
public void Register()
{
if (Error.Count() > 0)
{
return;
}
MessageBox.Show("注册成功");
}
}
</code></pre>
<h3 id="效果如下-1">效果如下:</h3>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202506/2212230-20250630231750371-1603413162.png"></p>
<p>验证通过,点击注册,效果如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202506/2212230-20250630231917427-760713331.png"></p>
<h2 id="3fluentvalidation--inotifydataerrorinfo-实现验证推荐使用">3.FluentValidation + INotifyDataErrorInfo 实现验证【推荐使用】</h2>
<h3 id="31-界面view如下">3.1 界面View如下:</h3>
<pre><code><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).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 .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>
</code></pre>
<p>当前的 View 使用了 INotifyDataErrorInfo 搭配 FluentValidation 进行验证,已经是很现代且推荐的方式,但 WPF 的默认控件不直接显示错误提示文本,还需要设置 验证模板 (ValidationTemplate) 或使用 Adorner 来显示错误提示。绑定需要设置 NotifyOnValidationError=True。</p>
<h3 id="32-安装fluentvalidation">3.2 安装FluentValidation</h3>
<p>Install-Package FluentValidation</p>
<h3 id="33-新建viewmodel类valueconverterviewmodel继承observableobject-inotifydataerrorinfo">3.3 新建ViewModel类:ValueConverterViewModel,继承ObservableObject, INotifyDataErrorInfo</h3>
<pre><code>public partial class ValueConverterViewModel : ObservableObject, INotifyDataErrorInfo
{
private readonly IValidator<ValueConverterViewModel> _validator;
private readonly Dictionary<string, List<string>> _errors = new();
public ValueConverterViewModel()
{
_validator = new ValueConverterViewModelValidator();
}
private string? userName;
private int age;
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 = propertyErrors;
OnErrorsChanged(propertyName);
}
}
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
public void Register()
{
MessageBox.Show("注册成功");
}
public bool CanRegister() => !HasErrors;
}
</code></pre>
<h3 id="34-新建类valueconverterviewmodelvalidator这是用于属性跪着提示类">3.4 新建类ValueConverterViewModelValidator,这是用于属性跪着提示类</h3>
<pre><code>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之间");
}
}
</code></pre>
<h3 id="35-效果如下">3.5 效果如下:</h3>
<p><img src="https://img2024.cnblogs.com/blog/2212230/202507/2212230-20250710231955311-771490843.png"></p>
<h2 id="4dataannotationsvalidatorinotifydataerrorinfo-实现验证">4.DataAnnotationsValidator+INotifyDataErrorInfo 实现验证</h2>
<h3 id="41-新建view页如下">4.1 新建View页如下:</h3>
<pre><code><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).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 .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).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>
</code></pre>
<h3 id="42-新建viewmodelvalueconverterviewmodel继承observablevalidator">4.2 新建ViewModel:ValueConverterViewModel,继承ObservableValidator</h3>
<pre><code>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();
private string? userName;
private string? password;
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 = 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));
public void Register()
{
MessageBox.Show("注册成功");
}
public bool CanRegister() => !HasErrors;
}
#endregion
}
</code></pre>
<p>效果如下:<br>
<img src="https://img2024.cnblogs.com/blog/2212230/202507/2212230-20250710235548184-661082059.png"></p>
<p>源代码地址如下:https://gitee.com/chenshibao/wpfdemo.git</p>
<p><strong>如果本文介绍对你有帮助,可以一键四连:点赞+评论+收藏+推荐,谢谢!</strong></p><br><br>
来源:https://www.cnblogs.com/chenshibao/p/18958710
頁:
[1]