WPF 类似Boostrap表单验证

小占时光 2025-06-24 11:39:06 59

目录

  最近一直在开发WPF项目,也没有多少时间记录笔记。只有等晚上有时间了,整理一下,最近用到了一个表单验证,类似Web表单提交时的验证。如果没有使用UI框架,直接使用WPF的话,需要实现INotifyDataErrorInfo接口。官方文档可能不太容易看,百度一搜,可以搜出很多。自己造轮子比较麻烦,我还是喜欢用现成的,WPF UI 框架已经帮助实现了,拿来就可用了。

ObservableValidator

  查看ObservableValidator源码可以看到,ObservableValidator已经实现ObservableObjectINotifyDataErrorInfoimage.png   使用时,我们只需要让模型类型,继承ObservableValidator即可,提示信息和MVC开发一样,使用特性添加需要验证的信息即可。

Viewmodel

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

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

  默认的验证错误会显示一个红色框,但这个红色框样式上会和输入不协调,所以我们需要写重写一下样式,覆盖错误信息的显示。这个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>

按照上面的内容写好,验证效果如下。 image.png

复杂应用

  上面简单使用基本已经可以了,但实际开发过程遇到的问题,是比较复杂的。

提交按钮绑定是否禁用

  一般验证过程我们希望页面有错误信息,Viewmodel继承ObservableValidator后,会有一个HasErrors属性,我只需要绑定提交按钮的IsEnabled即可。

<ui:Button
    HorizontalAlignment="Right"
    Command="{Binding ViewModel.SubmitCommand}"
    Content="提交"
    Icon="Save24"
    IsEnabled="{Binding ViewModel.HasErrors, Converter={StaticResource InverseBooleanConverter}}" />

页面有错误时,提交按钮即被禁用, 只有验证通过才会启用。 image.png

复杂类型Viewmodel

  很多时候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>

只要有一个有错误,按钮都会被禁用。 image.png

引用其他对象的值

   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

优秀
2
分享
123
奶茶
123
文章版权声明:版权归原作者(小占时光)所有。未经明确书面许可,严禁任何个人或组织以任何形式进行商业性或非商业性的使用、转载或抄袭。
评论