绑定下的数据验证
WPF中Binding数据校验、并捕获异常信息的三种方式讲到了三种方式,其中使用ValidatinRule的方式比较推荐,但是如果一个类中有多个属性,要为每个属性都要声明一个ValidatinRule,这样做非常麻烦。可以让类继承自IDataErrorInfo
来解决这个问题。
IDataErrorInfo基本使用
Data类中具有多个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class Data : IDataErrorInfo {
private string _value;
public string Value { get { return _value; } set { _value = value; } }
private string _myVar;
public string MyVar { get { return _myVar; } set { _myVar = value; } }
public string this[string columnName] { get { if (columnName == "Value" && this.Value =="123") { return "出错了~Value~[IDataErrorInfo]"; } if (columnName == "MyVar" && this.MyVar == "123") { return "出错了~MyVar~[IDataErrorInfo]"; } return string.Empty; } }
public string Error => "对象设置了123"; }
|
XAML代码
TextBox利用DataContext绑定属性
绑定数据源的方式有4种:Source、ElementName、DataContext、RelativeSource
1 2 3 4 5 6 7 8 9 10 11 12
| <Window.DataContext> <local:Data/> </Window.DataContext>
<StackPanel> <TextBox Text="{Binding Value,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}" x:Name="tb"/> <TextBlock Text="{Binding Path=(Validation.Errors)[0].ErrorContent,ElementName=tb}"/> <Border BorderThickness="3" BorderBrush="Red"/> <TextBox Text="{Binding MyVar,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}" x:Name="ts"/> <TextBlock Text="{Binding Path=(Validation.Errors)[0].ErrorContent,ElementName=ts}"/> </StackPanel>
|

使用反射与特性
从上面看出,利用索引器和属性名称可以判断是否为某些特性的值,但是这样写仍然太过麻烦,如果一个类中有大量的属性,要为每个属性进行判断,这样会有大量的if
语句,可读性和可维护性都不太好。
既然在索引器中我们已经有了属性名称,我们可以利用反射来简化上面的步骤。
- 自定义一个Attribute特性类
1 2 3 4 5 6
| public class NotValueAttribute : Attribute { public string ValidateValue { get; set; }
public NotValueAttribute(string value) { ValidateValue = value; } }
|
- 在重写索引器中的get方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public string this[string columnName] { get { PropertyInfo? pInfo = this.GetType().GetProperty(columnName, BindingFlags.Public | BindingFlags.Instance);
if (pInfo.IsDefined(typeof(NotValueAttribute), false)) { NotValueAttribute attr = (NotValueAttribute)pInfo.GetCustomAttribute(typeof(NotValueAttribute), false); if (attr == null) { return string.Empty; } else if(pInfo.GetValue(this) == null) { return string.Empty; } else if (pInfo.GetValue(this).ToString() == attr.ValidateValue) { return "字段不能为" + attr.ValidateValue; } } return string.Empty; } }
|
- 分别为属性增加特性
1 2 3 4 5 6 7 8 9 10 11 12 13
| [NotValue("123")] public string Value { get { return _value; } set { _value = value; } }
[NotValue("234")] public string MyVar { get { return _myVar; } set { _myVar = value; } }
|

上面的这种写法可以简化验证所需要的代码,而且有很强的可读性和可维护性,如果直接把自定义的特性直接加到类上,使得该类中所有的属性都应用验证规则可以修改苏索引器,在上面所写的代码中加上
1 2 3 4 5 6 7 8 9 10
| NotValueAttribute? classAttr =(NotValueAttribute?)this.GetType().GetCustomAttribute(typeof(NotValueAttribute), false); if (classAttr !=null) { PropertyInfo? classPInfo = this.GetType().GetProperty(columnName, BindingFlags.Public | BindingFlags.Instance); if (classPInfo.GetValue(this)==null) { return string.Empty; } else if (classPInfo.GetValue(this).ToString() == classAttr.ValidateValue) { return "字段不能为" + classAttr.ValidateValue; } }
|
将自定义特性应用在类上
1 2
| [NotValue("123")] public class Data : IDataErrorInfo
|

案例:实现VS新建项目界面

- 创建一个继承自IDataErrorInfo的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class Data : IDataErrorInfo { public string this[string columnName] { get { var pi = this.GetType().GetProperty(columnName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); if (pi.IsDefined(typeof(RequiredAttribute), false) && (pi.GetValue(this) == null || string.IsNullOrEmpty(pi.GetValue(this).ToString())) ) return "字段为空了~~~[IDataErrorInfo]";
return ""; } }
public string Error => null;
private string _projectName = "新建项目"; [Required] public string ProjectName { get { return _projectName; } set { _projectName = value; } }
private string _solutionName = "新建项目";
[Required] public string SolutionName { get { return _solutionName; } set { _solutionName = value; } }
}
|
- XAML代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| <Window.DataContext> <local:Data/> </Window.DataContext> <Window.Resources> <Style TargetType="TextBlock" > <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="FontSize" Value="12"/> </Style> <ControlTemplate x:Key="TextBoxErrorTemplate"> <AdornedElementPlaceholder/> </ControlTemplate>
<Style TargetType="TextBox"> <Setter Property="Margin" Value="0,5"/> <Setter Property="Width" Value="500"/> <Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <Grid> <Grid.RowDefinitions> <RowDefinition MinHeight="30"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True"> <ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden" VerticalContentAlignment="Center" BorderThickness="0"/> </Border>
<TextBlock Text="{Binding Path=(Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Mode=TemplatedParent},StringFormat=* {0}}" Grid.Row="1" Foreground="Red" Margin="0,3" FontSize="12" Visibility="Collapsed" Name="errorTxt"/> </Grid> <ControlTemplate.Triggers> <Trigger Property="Validation.HasError" Value="True"> <Setter Property="BorderBrush" Value="Red" TargetName="border"/> <Setter Property="Visibility" Value="Visible" TargetName="errorTxt"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Validation.ErrorTemplate" Value="{StaticResource TextBoxErrorTemplate}"/> </Style>
<Style TargetType="ComboBox"> <Setter Property="Height" Value="30"/> <Setter Property="Margin" Value="0,5"/> <Setter Property="Width" Value="500"/> <Setter Property="HorizontalAlignment" Value="Left"/> </Style> <Style TargetType="Button"> <Setter Property="Height" Value="28"/> <Setter Property="Width" Value="80"/> <Setter Property="Margin" Value="5,0"/> </Style> </Window.Resources> <Grid Margin="30"> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="auto"/> <RowDefinition Height="28"/> <RowDefinition Height="auto"/> <RowDefinition Height="28"/> <RowDefinition Height="auto"/> <RowDefinition Height="28"/> <RowDefinition Height="auto"/> <RowDefinition Height="28"/> <RowDefinition Height="auto"/> <RowDefinition Height="28"/> <RowDefinition/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <TextBlock Text="配置新项目" FontSize="22"/> <TextBlock Text="WPF应用程序" Grid.Row="1" FontSize="16" Margin="0,15"/> <TextBlock Text="项目名称" Grid.Row="2"/> <TextBox Grid.Row="3" Text="{Binding ProjectName,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"/> <TextBlock Text="位置" Grid.Row="4"/> <ComboBox Grid.Row="5"/> <TextBlock Text="解决方案" Grid.Row="6"/> <ComboBox Grid.Row="7"/> <TextBlock Text="解决方案名称" Grid.Row="8"/> <TextBox Grid.Row="9" Text="{Binding SolutionName,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"/> <CheckBox Content="将解决方案和项目放在同一个目录中" Grid.Row="10" VerticalAlignment="Center"/> <StackPanel Orientation="Horizontal" Grid.Row="12" HorizontalAlignment="Right"> <Button Content="上一步"/> <Button Content="下一步"/> </StackPanel> </Grid>
|
关注点要设置Validation.ErrorTemplate
,不然整个Template会有一个整体的边框
