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

1,前言:

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

例如以下情况:

XML 复制代码
<TextBox>
    <TextBox.Text>
         <Binding Path="UnitCost"  >                                                       
         </Binding>
    </TextBox.Text>
 </TextBox>
cs 复制代码
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"

XML 复制代码
<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 异常验证规则等同 :

XML 复制代码
<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。

cs 复制代码
 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,在绑定中添加自定义的验证规则,并设置相应属性。

XML 复制代码
 <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,在父容器添加附加事件,监听子元素产生的验证错误。

XML 复制代码
Validation.Error="Grid_Error"

示例:

XML 复制代码
 <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>
cs 复制代码
  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,根据当前元素的是否出现验证错误进行样式设置。

XML 复制代码
<Trigger Property="Validation.HasError" Value="true">

示例:

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

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

XML 复制代码
Path=(Validation.Errors)[0].ErrorContent

示例:

XML 复制代码
 <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中附加属性无括号包裹:

XML 复制代码
<Trigger Property="Validation.HasError" Value="true">

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

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

4,错误模板应用。

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

ErrorTemplate的类型为ControlTemplate。

4.1,自定义错误模板。

XML 复制代码
 <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>

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

XML 复制代码
<AdornedElementPlaceholder x:Name="adornedElement1"></AdornedElementPlaceholder>

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

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

XML 复制代码
<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

相关推荐
充值内卷2 小时前
WPF入门教学四 WPF控件概述
windows·ui·wpf
小白1 天前
WPF自定义Dialog模板,内容用不同的Page填充
wpf
Crazy Struggle1 天前
C# + WPF 音频播放器 界面优雅,体验良好
c#·wpf·音频播放器·本地播放器
James.TCG2 天前
WPF动画
wpf
He BianGu2 天前
笔记:简要介绍WPF中FormattedText是什么,主要有什么功能
笔记·c#·wpf
脚步的影子3 天前
.NET 6.0 + WPF 使用 Prism 框架实现导航
.net·wpf
jyl_sh3 天前
Ribbon (WPF)
ribbon·wpf·client·桌面程序开发·c/s客户端
wo63704313 天前
[Visual Stuidio 2022使用技巧]2.配置及常用快捷键
c#·.net·wpf
小黄人软件3 天前
wpf 字符串 与 变量名或函数名 相互转化:反射
wpf
界面开发小八哥3 天前
DevExpress WPF中文教程:如何解决排序、过滤遇到的常见问题?(二)
.net·wpf·界面控件·devexpress·ui开发