U:调整调用方式和兼容性

This commit is contained in:
jimmy committed 2025-12-15 13:37:11 +08:00
1 parent 433e206d45
commit 4ec76b7855
50 files changed
+5592 -47

No files matched your search

+233
View File
@@ -0,0 +1,233 @@
# 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)
---
**掌握表单验证,构建高质量的用户输入体验!** ✅