小占时光 2025-06-24 11:39:06 59
最近一直在开发WPF项目,也没有多少时间记录笔记。只有等晚上有时间了,整理一下,最近用到了一个表单验证,类似Web表单提交时的验证。如果没有使用UI框架,直接使用WPF的话,需要实现INotifyDataErrorInfo
接口。官方文档可能不太容易看,百度一搜,可以搜出很多。自己造轮子比较麻烦,我还是喜欢用现成的,WPF UI 框架已经帮助实现了,拿来就可用了。
查看ObservableValidator
源码可以看到,ObservableValidator
已经实现ObservableObject
和INotifyDataErrorInfo
。
使用时,我们只需要让模型类型,继承ObservableValidator即可,提示信息和MVC开发一样,使用特性添加需要验证的信息即可。
public partial class DataViewModel : ObservableValidator, INavigationAware
{
[ObservableProperty]
[Required(ErrorMessage = "姓名必填")]
[NotifyDataErrorInfo]
private string name;
[ObservableProperty]
private int? age;
[ObservableProperty]
private int sex;
[RelayCommand]
private void Submit()
{
// 验证数据
ValidateAllProperties();
if (HasErrors)
{
return;
}
}
}
在提交方法中使用ValidateAllProperties();
方法可以主动验证表单内容。
xaml 页面只需正常绑定即可。
<ui:TextBox
Margin="0,15"
PlaceholderText="请输入姓名"
Text="{Binding ViewModel.Name, UpdateSourceTrigger=PropertyChanged}" />
<ui:TextBox
Margin="0,15"
PlaceholderText="请输入年龄"
Text="{Binding ViewModel.Age, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox Margin="0,15" SelectedIndex="{Binding ViewModel.Sex}">
<ComboBoxItem Content="男" />
<ComboBoxItem Content="女" />
</ComboBox>
<ui:Button
HorizontalAlignment="Right"
Command="{Binding ViewModel.SubmitCommand}"
Content="提交"
Icon="Save24" />
默认的验证错误会显示一个红色框,但这个红色框样式上会和输入不协调,所以我们需要写重写一下样式,覆盖错误信息的显示。这个style 可以放在app.xaml中,这样所有需要验证的界面,都会自动应用。
<Style BasedOn="{StaticResource DefaultUiTextBoxStyle}" TargetType="ui:TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<AdornedElementPlaceholder x:Name="adorner" Grid.Row="0" />
<!-- 使用 SymboIcon 显示问号圆圈 -->
<ui:SymbolIcon
x:Name="errorIcon"
Grid.Row="0"
Width="16"
Height="16"
Margin="5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Cursor="Help"
Foreground="Red"
Symbol="QuestionCircle32"
ToolTip="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
<TextBlock
Grid.Row="1"
Foreground="Red"
Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</Grid>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
<Setter Property="ToolTipService.InitialShowDelay" Value="0" />
<Setter Property="ToolTipService.ShowDuration" Value="10000" />
<Setter Property="ToolTipService.VerticalOffset" Value="-10" />
</Trigger>
</Style.Triggers>
</Style>
按照上面的内容写好,验证效果如下。
上面简单使用基本已经可以了,但实际开发过程遇到的问题,是比较复杂的。
一般验证过程我们希望页面有错误信息,Viewmodel
继承ObservableValidator
后,会有一个HasErrors
属性,我只需要绑定提交按钮的IsEnabled
即可。
<ui:Button
HorizontalAlignment="Right"
Command="{Binding ViewModel.SubmitCommand}"
Content="提交"
Icon="Save24"
IsEnabled="{Binding ViewModel.HasErrors, Converter={StaticResource InverseBooleanConverter}}" />
页面有错误时,提交按钮即被禁用, 只有验证通过才会启用。
很多时候Viewmodel
不只是简单的属性,他的属性可能还是一个复杂类,例如:
public partial class DataViewModel : ObservableValidator, INavigationAware
{
[ObservableProperty]
[Required(ErrorMessage ="姓名必填")]
[NotifyDataErrorInfo]
private string name;
[ObservableProperty]
private int? age;
[ObservableProperty]
private int sex;
[ObservableProperty]
private UserInfo info = new UserInfo();
[RelayCommand]
private void Submit()
{
if (HasErrors)
{
return;
}
// 验证数据
ValidateAllProperties();
Info.ValidateProperties();
if (!HasErrors || !Info.HasErrors)
{
System.Windows.MessageBox.Show("数据验证不通过");
return;
}
}
}
UserInfo 类
public partial class UserInfo : ObservableValidator
{
public void ValidateProperties()
{
base.ValidateAllProperties();
}
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "地址必填")]
private string? address;
[ObservableProperty]
[NotifyDataErrorInfo]
[Required(ErrorMessage = "电话必填")]
private string phone;
[ObservableProperty]
private string height = "23";
}
Viewmodel
上可以看出UserInfo
需要公开ValidateAllProperties
方法才行,由于ObservableValidator
中有保护级别,所有需要单独写一个方法将其公开。在viewmodel提交时,手动调用验证。判断是否有错误时,也需要单独的对属性中的复杂类进行判断。
这种情况xaml,页面和平时绑定一样。
<ui:TextBox
Margin="0,15"
PlaceholderText="请输入地址"
Text="{Binding ViewModel.Info.Address, UpdateSourceTrigger=PropertyChanged}" />
<ui:TextBox
Margin="0,15"
PlaceholderText="请输入电话"
Text="{Binding ViewModel.Info.Phone, UpdateSourceTrigger=PropertyChanged}" />
提交按钮的绑定,需要判断两个值(多个值也是一样的),所以添加以一个转换器即可。
public class AllFalseToBoolConverter : IMultiValueConverter
{
// 如果所有参数都是 false,返回 true,按钮启用
// 只要有一个 true,返回 false,按钮禁用
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null || values.Length == 0)
return true;
bool allFalse = values.OfType<bool>().All(b => !b);
return allFalse;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
xaml
<Button Content="提交2" Visibility="Collapsed">
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource AllFalseToBoolConverter}">
<Binding Path="ViewModel.HasErrors" />
<Binding Path="ViewModel.Info.HasErrors" />
</MultiBinding>
</Button.IsEnabled>
</Button>
只要有一个有错误,按钮都会被禁用。
viewmodel
中有时候为了方便,直接使用其他对象的属性来进行输入输出,这样可以避免重复赋值,但验证上就需要手动添加ValidateProperty
。
[Required(ErrorMessage = "实际身高必填")]
public string ActualHeight
{
get => Info.Height;
set => SetProperty(Info.Height, value, v => { Info.Height = v; ValidateProperty(value, nameof(ActualHeight)); });
}
Required
等验证信息必须写在xaml绑定属性上,不是Info的Height上,如果写在Info上,这里的错误信息就显示不出来。
在输入框中,一般是提交了才会触发验证,验证错误时,我们输入正确信息需要马上更新输入框的状态,xaml
页面绑定的地方需要添加UpdateSourceTrigger=PropertyChanged
Viewmodel
属性上需要添加特性[NotifyDataErrorInfo]
,如果没有这个,则只有手动调用ValidateAllProperties
时才会验证。
[ObservableProperty]
[Required(ErrorMessage ="姓名必填")]
[NotifyDataErrorInfo]
private string name;
<ui:TextBox
Margin="0,15"
PlaceholderText="请输入姓名"
Text="{Binding ViewModel.Name, UpdateSourceTrigger=PropertyChanged}" />
最后一次修改 : 2025/7/1 下午2:47:00