WPF之绑定验证(错误模板使用)

发布于:2024-05-05 ⋅ 阅读:(33) ⋅ 点赞:(0)

1,前言:

         默认情况下,WPF XAML 中使用的绑定并未开启绑定验证,这样导致用户在UI上对绑定的属性进行赋值时即使因不符合规范内部已抛出异常(此情况仅限WPF中的数据绑定操作),也被程序默认忽略,UI层面也无异常提示,无法确定值是否已更改。而这些问题可通过Validation提供的附加属性,附加事件,错误模板进行检测提示,从而有效的解决绑定中产生的异常问题。

例如以下情况:

<TextBox>
    <TextBox.Text>
         <Binding Path="UnitCost"  >                                                       
         </Binding>
    </TextBox.Text>
 </TextBox>
public decimal UnitCost
        {
            get { return unitCost; }
            set
            {
                //测试UI属性绑定异常抛出捕捉
                if (value < 0)
                {
                    throw new ArgumentException("值不能小于0");
                }
                unitCost = value;
                OnPropertyChanged(nameof(UnitCost));
            }
        }

        在UI层面用户通过绑定将当前的 UnitCost 值设置为小于0时(仅限通过绑定输入的值),虽在代码中已产生异常,但运行程序对该绑定中产生的异常默认进行了忽略,不提示异常,导致该值是否已更新,无法确定。

2,数据验证的应用。

         数据绑定进行数据源更新时先进行验证再进行装换,所以对于文本框而言,数据在验证时都是字符串型。

2.1,开启绑定中的验证通知:NotifyOnValidationError="True"

<TextBox  Grid.Row="2" Grid.Column="1">
                    <TextBox.Text>
                        <Binding Path="UnitCost" NotifyOnValidationError="True" ValidatesOnExceptions="True"  >
                            
                        </Binding>
                    </TextBox.Text>
                </TextBox>

2.2,开启异常验证捕捉规则:ValidatesOnExceptions="True",用于捕捉在该绑定中产生的任何异常(可选)。

        此设定与以下绑定  ExceptionValidationRule 异常验证规则等同 :

<TextBox  Grid.Row="2" Grid.Column="1">
                    <TextBox.Text>
                        <Binding Path="UnitCost" NotifyOnValidationError="True"  >
                            <Binding.ValidationRules>
                                <ExceptionValidationRule></ExceptionValidationRule>
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>

2.3,绑定自定义验证规则。

        自定义的验证规则类需要继承自System.Windows.Controls下的抽象类ValidationRule。

 class RangeValidationRule : ValidationRule
    {
        /// <summary>
        /// 范围上限
        /// </summary>
        public decimal MaxNum { get; set; } = 10000;
        /// <summary>
        /// 范围下限
        /// </summary>
        public decimal MinNum { get; set; }
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            string valStr = value.ToString();
            if (string.IsNullOrEmpty(valStr))
            {
                return new ValidationResult(false, "不能为空值");
            }
            decimal val;
            if(!decimal.TryParse(valStr,NumberStyles.Any,cultureInfo,out val))
            {
                return new ValidationResult(false, "输入的内容非法,请输入有效的货币值");
            }
            if(val>MaxNum || val < MinNum)
            {
                return new ValidationResult(false, $"只能是:{MinNum} - {MaxNum}之间的货币值");
            }
            return new ValidationResult(true, "");
        }
    }

2.4,在绑定中添加自定义的验证规则,并设置相应属性。

 <TextBox  Grid.Row="2" Grid.Column="1">
                    <TextBox.Text>
                        <Binding Path="UnitCost" NotifyOnValidationError="True" ValidatesOnExceptions="True"  >
                            <Binding.ValidationRules>
                                <local:RangeValidationRule MinNum="10" MaxNum="10000"></local:RangeValidationRule>
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>

2.5,验证失败时WPF自动将控件使用的模板切换为由Validation.ErrorTemplate附加属性定义的模板。在文本框中新模板将文本框的轮过改成一条细的红色边框(默认错误模板)。

3,使用Validation提供的附加属性,附加事件对异常进行处理。

Validation.HasError 附加属性,验证当前元素是否存在验证错误
Validation.Error 附加事件,当前元素验证错误事件(路由事件)
Validation.Errors 附加属性,当前元素产生的验证错误信息集合
Validation.ErrorTemplate 附加属性,当前的元素的错误模板(模板类型:ControlTemplate)

        Validation.ErrorEvent为附加事件即为路由事件,所以可在其父容器进行注册监听。

3.1,在父容器添加附加事件,监听子元素产生的验证错误。

Validation.Error="Grid_Error"

        示例:

 <Grid Margin="10" DataContext="{Binding ElementName=listBox01, Path=SelectedItem}" Validation.Error="Grid_Error">
                <Grid.RowDefinitions>
                    <RowDefinition ></RowDefinition>
                    <RowDefinition ></RowDefinition>
                    <RowDefinition ></RowDefinition>
                    <RowDefinition ></RowDefinition>
                    <RowDefinition Height="3*"></RowDefinition>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto"></ColumnDefinition>
                    <ColumnDefinition ></ColumnDefinition>
                </Grid.ColumnDefinitions>
               
                <TextBlock Text="Model Number"></TextBlock>
                <TextBox Grid.Column="1" Text="{Binding ModelNumber, TargetNullValue=[Empty]}"></TextBox>
                <TextBlock Grid.Row="1" Text="Model Name"></TextBlock>
                <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ModelName, TargetNullValue=[Empty]}"></TextBox>
                <TextBlock Grid.Row="2" Text="Unit Cost"></TextBlock>
                <TextBox  Grid.Row="2" Grid.Column="1">
                    <TextBox.Text>
                        <Binding Path="UnitCost" NotifyOnValidationError="True" ValidatesOnExceptions="True"  >
                            <Binding.ValidationRules>
                                <local:RangeValidationRule MinNum="10" MaxNum="10000"></local:RangeValidationRule>
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>
                <TextBlock Grid.Row="3" Text="Descriptionz:"></TextBlock>
                <TextBox  Grid.Row="4" Grid.ColumnSpan="2" Style="{x:Null}" Text="{Binding Description}"></TextBox>
            </Grid>
        </Border>
    </Grid>
  private void Grid_Error(object sender, ValidationErrorEventArgs e)
        {
            if (e.Action == ValidationErrorEventAction.Added)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"RoutedEvent:{e.RoutedEvent.Name}");
                sb.AppendLine($"Source:{e.Source}");
                sb.AppendLine($"ErrorContent:{e.Error.ErrorContent}");
                sb.AppendLine($"{e.Error.RuleInError.GetType().Name}");
                //sb.AppendLine($"Message:{e.Error.Exception.Message}");
                MessageBox.Show(sb.ToString());
            }
        }

3.2,根据当前元素的是否出现验证错误进行样式设置。

<Trigger Property="Validation.HasError" Value="true">

        示例:

<Trigger Property="Validation.HasError" Value="true">                               
                                <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" >                                  
                                </Setter>
                            </Trigger>

3.3,绑定当前元素的当前验证错误信息。

Path=(Validation.Errors)[0].ErrorContent

        示例:

 <Style.Triggers>
                            <Trigger Property="Validation.HasError" Value="true">
                                <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" >
                                </Setter>
                            </Trigger>
                        </Style.Triggers>

        注意此时的附加属性样式与Path关联的附加属性样式有差异:

        Property中附加属性无括号包裹:

<Trigger Property="Validation.HasError" Value="true">

        Path中的附加属性需用括号包裹

<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" >

4,错误模板应用。

        当验证控件的Validation.HasError属性被设置为true时,WPF自动将控件使用的模板切换为由Validation.ErrorTemplate附加属性定义的模板。在文本框中新模板将文本框的轮过改成一条细的红色边框。

        ErrorTemplate的类型为ControlTemplate。

4.1,自定义错误模板。

 <Style TargetType="TextBox">
                        <Setter Property="Margin" Value="0,3"></Setter>
                        <Setter Property="VerticalContentAlignment" Value="Center"></Setter>
                        <Setter Property="Validation.ErrorTemplate">
                            <Setter.Value>
                                <ControlTemplate>
                                    <Border BorderBrush="Green" BorderThickness="1">
                                        <DockPanel LastChildFill="True">
                                        <TextBlock  DockPanel.Dock="Right" Background="Red" ToolTip="{Binding ElementName=adornedElement1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" HorizontalAlignment="Center">*</TextBlock>
                                            <AdornedElementPlaceholder x:Name="adornedElement1"></AdornedElementPlaceholder>
                                    </DockPanel>
                                    </Border>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>

                    </Style>

        错误模板是使用装饰层,装饰层位于普通窗口之上的绘图层。

<AdornedElementPlaceholder x:Name="adornedElement1"></AdornedElementPlaceholder>

        AdornedElementPlaceholder,这里指代被修饰的元素即文本框(AdornedElementPlaceholder必须位于ControlTemplate中,为固定写法。)。

4.2,通过AdornedElementPlaceholder获取被修饰对象上的验证错误信息。

<TextBlock  DockPanel.Dock="Right" Background="Red" ToolTip="{Binding ElementName=adornedElement1, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" HorizontalAlignment="Center">*</TextBlock>

5,效果

6,Demo链接:

https://download.csdn.net/download/lingxiao16888/89263053