Files
sharp-basic/examples/03-FormValidation/README.md
T

7.8 KiB
Raw Blame History

03 - 表单验证示例

演示如何使用 Sharp.Wpf 的表单验证功能,包括 DataAnnotations 验证、实时错误提示和自定义验证。

🎯 学习目标

  • 使用 ValidatableViewModelBase 实现表单验证
  • 掌握 DataAnnotations 验证特性
  • 实现实时验证反馈
  • 自定义验证规则
  • 显示验证错误消息

📦 使用的模块

  • Sharp.Wpf - ValidatableViewModelBase, 验证支持

🚀 快速运行

cd examples/03-FormValidation
dotnet run

📝 核心代码讲解

1. UserViewModel.cs - 带验证的 ViewModel

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 - 验证错误显示

<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,可以自定义边框颜色、图标等。

📚 延伸阅读


掌握表单验证,构建高质量的用户输入体验! ✅