234 lines
7.8 KiB
Markdown
234 lines
7.8 KiB
Markdown
# 03 - 表单验证示例
|
||
|
||
演示如何使用 Sharp.Wpf 的表单验证功能,包括 DataAnnotations 验证、实时错误提示和自定义验证。
|
||
|
||
## 🎯 学习目标
|
||
|
||
- 使用 `ValidatableViewModelBase` 实现表单验证
|
||
- 掌握 DataAnnotations 验证特性
|
||
- 实现实时验证反馈
|
||
- 自定义验证规则
|
||
- 显示验证错误消息
|
||
|
||
## 📦 使用的模块
|
||
|
||
- **Sharp.Wpf** - ValidatableViewModelBase, 验证支持
|
||
|
||
## 🚀 快速运行
|
||
|
||
```bash
|
||
cd examples/03-FormValidation
|
||
dotnet run
|
||
```
|
||
|
||
## 📝 核心代码讲解
|
||
|
||
### 1. UserViewModel.cs - 带验证的 ViewModel
|
||
|
||
```csharp
|
||
using System.ComponentModel.DataAnnotations;
|
||
using Wpf.Validation;
|
||
|
||
public class UserViewModel : ValidatableViewModelBase
|
||
{
|
||
private string _username = "";
|
||
private string _email = "";
|
||
private string _password = "";
|
||
private string _confirmPassword = "";
|
||
private int _age;
|
||
|
||
[Required(ErrorMessage = "用户名不能为空")]
|
||
[StringLength(20, MinimumLength = 3, ErrorMessage = "用户名长度必须在3-20个字符之间")]
|
||
public string Username
|
||
{
|
||
get => _username;
|
||
set => SetProperty(ref _username, value, validate: true); // 启用验证
|
||
}
|
||
|
||
[Required(ErrorMessage = "邮箱不能为空")]
|
||
[EmailAddress(ErrorMessage = "邮箱格式不正确")]
|
||
public string Email
|
||
{
|
||
get => _email;
|
||
set => SetProperty(ref _email, value, validate: true);
|
||
}
|
||
|
||
[Required(ErrorMessage = "密码不能为空")]
|
||
[StringLength(50, MinimumLength = 6, ErrorMessage = "密码长度必须在6-50个字符之间")]
|
||
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$",
|
||
ErrorMessage = "密码必须包含大小写字母和数字")]
|
||
public string Password
|
||
{
|
||
get => _password;
|
||
set
|
||
{
|
||
SetProperty(ref _password, value, validate: true);
|
||
// 密码变化时,重新验证确认密码
|
||
ValidateProperty(ConfirmPassword, nameof(ConfirmPassword));
|
||
}
|
||
}
|
||
|
||
[Required(ErrorMessage = "请确认密码")]
|
||
public string ConfirmPassword
|
||
{
|
||
get => _confirmPassword;
|
||
set
|
||
{
|
||
SetProperty(ref _confirmPassword, value, validate: false);
|
||
// 自定义验证:确认密码必须与密码一致
|
||
if (value != Password)
|
||
{
|
||
AddError(nameof(ConfirmPassword), "两次密码输入不一致");
|
||
}
|
||
else
|
||
{
|
||
ClearErrors(nameof(ConfirmPassword));
|
||
}
|
||
}
|
||
}
|
||
|
||
[Range(18, 120, ErrorMessage = "年龄必须在18-120之间")]
|
||
public int Age
|
||
{
|
||
get => _age;
|
||
set => SetProperty(ref _age, value, validate: true);
|
||
}
|
||
|
||
// 提交命令
|
||
public ICommand SubmitCommand { get; }
|
||
|
||
public UserViewModel(IDialogService dialogService)
|
||
{
|
||
SubmitCommand = new RelayCommand(() =>
|
||
{
|
||
// 验证所有属性
|
||
if (ValidateAll())
|
||
{
|
||
dialogService.ShowMessage(
|
||
$"注册成功!\n用户名: {Username}\n邮箱: {Email}\n年龄: {Age}",
|
||
"成功");
|
||
}
|
||
else
|
||
{
|
||
dialogService.ShowMessage("请修正表单错误后再提交", "验证失败");
|
||
}
|
||
});
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. MainWindow.xaml - 验证错误显示
|
||
|
||
```xml
|
||
<Window x:Class="FormValidationExample.MainWindow"
|
||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
Title="表单验证示例" Height="500" Width="400">
|
||
<Window.Resources>
|
||
<!-- 错误模板 -->
|
||
<ControlTemplate x:Key="ValidationErrorTemplate">
|
||
<DockPanel>
|
||
<Border BorderBrush="Red" BorderThickness="1" CornerRadius="2">
|
||
<AdornedElementPlaceholder/>
|
||
</Border>
|
||
<TextBlock Foreground="Red" FontSize="12" Margin="5,0,0,0"
|
||
Text="{Binding [0].ErrorContent}"
|
||
DockPanel.Dock="Right"/>
|
||
</DockPanel>
|
||
</ControlTemplate>
|
||
</Window.Resources>
|
||
|
||
<StackPanel Margin="20">
|
||
<TextBlock Text="用户注册表单" FontSize="24" FontWeight="Bold" Margin="0,0,0,20"/>
|
||
|
||
<!-- 用户名 -->
|
||
<TextBlock Text="用户名:"/>
|
||
<TextBox Text="{Binding Username, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
|
||
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
|
||
Margin="0,5,0,15"/>
|
||
|
||
<!-- 邮箱 -->
|
||
<TextBlock Text="邮箱:"/>
|
||
<TextBox Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
|
||
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
|
||
Margin="0,5,0,15"/>
|
||
|
||
<!-- 密码 -->
|
||
<TextBlock Text="密码:"/>
|
||
<PasswordBox x:Name="PasswordBox" Margin="0,5,0,5"/>
|
||
<TextBlock Text="* 密码必须包含大小写字母和数字" FontSize="10" Foreground="Gray" Margin="0,0,0,15"/>
|
||
|
||
<!-- 确认密码 -->
|
||
<TextBlock Text="确认密码:"/>
|
||
<PasswordBox x:Name="ConfirmPasswordBox" Margin="0,5,0,15"/>
|
||
|
||
<!-- 年龄 -->
|
||
<TextBlock Text="年龄:"/>
|
||
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}"
|
||
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}"
|
||
Margin="0,5,0,20"/>
|
||
|
||
<!-- 提交按钮 -->
|
||
<Button Content="提交注册" Command="{Binding SubmitCommand}"
|
||
Height="40" FontSize="16"/>
|
||
|
||
<!-- 验证状态 -->
|
||
<TextBlock Margin="0,10,0,0" Foreground="Red"
|
||
Text="表单包含错误,请修正后提交"
|
||
Visibility="{Binding HasErrors, Converter={StaticResource BoolToVisibilityConverter}}"/>
|
||
</StackPanel>
|
||
</Window>
|
||
```
|
||
|
||
### 3. 关键点说明
|
||
|
||
**ValidatableViewModelBase 的关键特性**:
|
||
1. **实时验证** - `SetProperty(ref field, value, validate: true)`
|
||
2. **验证所有属性** - `ValidateAll()` 返回 bool
|
||
3. **自定义错误** - `AddError(propertyName, errorMessage)`
|
||
4. **清除错误** - `ClearErrors(propertyName)`
|
||
5. **错误状态** - `HasErrors` 属性
|
||
|
||
**XAML 绑定要点**:
|
||
1. **UpdateSourceTrigger=PropertyChanged** - 实时触发验证
|
||
2. **ValidatesOnNotifyDataErrors=True** - 启用错误显示
|
||
3. **Validation.ErrorTemplate** - 自定义错误样式
|
||
|
||
## 🎓 练习建议
|
||
|
||
### 练习1:添加更多验证规则
|
||
添加电话号码字段,使用正则表达式验证手机号格式。
|
||
|
||
### 练习2:异步验证
|
||
实现用户名唯一性检查(模拟调用 API)。
|
||
|
||
### 练习3:条件验证
|
||
当用户勾选"我是学生"时,年龄必须小于25岁。
|
||
|
||
### 练习4:自定义验证特性
|
||
创建 `UniqueUsernameAttribute` 自定义验证特性。
|
||
|
||
## 💡 常见问题
|
||
|
||
**Q: PasswordBox 无法绑定,怎么办?**
|
||
A: PasswordBox 的 Password 属性不是依赖属性,不能直接绑定。可以使用附加属性或在代码隐藏中处理。
|
||
|
||
**Q: 为什么有时验证不触发?**
|
||
A: 确保:
|
||
1. 绑定使用了 `UpdateSourceTrigger=PropertyChanged`
|
||
2. SetProperty 的 validate 参数设为 true
|
||
3. XAML 中设置了 `ValidatesOnNotifyDataErrors=True`
|
||
|
||
**Q: 如何自定义错误消息样式?**
|
||
A: 修改 `Validation.ErrorTemplate`,可以自定义边框颜色、图标等。
|
||
|
||
## 📚 延伸阅读
|
||
|
||
- [DataAnnotations 验证特性](https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations)
|
||
- [INotifyDataErrorInfo 接口](https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.inotifydataerrorinfo)
|
||
- 下一个示例:[04-Navigation](../04-Navigation/README.md)
|
||
|
||
---
|
||
|
||
**掌握表单验证,构建高质量的用户输入体验!** ✅
|