WPF中数据绑定验证深入讲解

WPF中数据绑定验证深入讲解

WPF在用户输入时,提供了验证功能,通常验证使用以下两种方式来实现:

  1. 在数据对象中引发错误。通常是在属性设置过程中抛出异常,或者在数据类中实现INotifyDataErrorInfoIDataErrorInfo接口。
  2. 在绑定级别定义验证。

只有来自目标的值正在被用于更新数据源时才会应用验证。

数据对象中设置验证

  1. 在属性中Set上抛出异常
public class MyData
{
    private string _value = "200";

    public string Value
    {
        get { return _value; }
        set
        {
            _value = value;

            if (value == "123")
                throw new System.Exception("报错了~~~[Exception]");
        }
    }
}
  1. 直接抛出异常,wpf经常会忽略,从而得不到异常的信息,此时需要借助ExceptionValidationRule

ExceptionValidationRule是预先构建的验证规则,它向WPF发出所有的异常报告。它必须在<Binding.ValidationRules>里面

<TextBox x:Name="tb1">
    <TextBox.Text>
        <Binding Path="Value" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

ExceptionValidationRule在绑定过程中发生的所有异常,包括编辑的值不能转为正确类型、属性设置器异常以及值转换器异常(float转为string)。当出现验证失败后,System.Windows.Controls.Validation类的附加属性会记录下错误:

  • 在绑定元素上,Validation.HasError为True,同时会自动将控件的模板切换为Validation.ErrorTemplate定义的模板。
  • ValidationRule.Validate()会返回ValidationError,其中中包含错误细节
  • 如果Binding.NotifyOnValidationError被设置为True,则会在绑定元素上引发Validation.Error事件

INotifyDataErrorInfo

INotifyDataErrorInfoINotifyDataErrorInfo都有类似作用,但是INotifyDataErrorInfo界面更加丰富。与上面不同的是,实现INotifyDataErrorInfoIDataErrorInfo接口时,允许用户修改为非法值,只不过给出错误提示。

image-20231108111152474

使用INotifyDataErrorInfo的案例

  1. 新建一个Data类
//类实现了INotifyDataErrorInfo接口,该接口定义了HasErrors属性和GetErrors方法,以及ErrorsChanged事件
public class Data : INotifyDataErrorInfo,INotifyPropertyChanged
{
    //key为属性名,value为错误信息列表
    Dictionary<string, List<string>> errors = new();

    void SetErrors(string propertyName, List<string> value)
    {
        errors.Remove(propertyName);
        errors.Add(propertyName, value);
        if (ErrorsChanged != null)
        {
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }
    void ClearErrors(string propertyName)
    {
        errors.Remove(propertyName);
        if (ErrorsChanged != null)
        {
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }
    public bool HasErrors => errors.Count>0;

    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
    public event PropertyChangedEventHandler? PropertyChanged;

    public IEnumerable Errors => GetErrors("ModelNumber");
    public IEnumerable GetErrors(string? propertyName)
    {
        if (propertyName is null or { Length: <= 0 })
        {
            return errors.Values;
        }
        else
        {
            if (errors.ContainsKey(propertyName))
            {
                return errors[propertyName];
            }
            else
            {
                return null;
            }
        }
    }

    private string modelNumber;

    public string ModelNumber
    {
        get { return modelNumber; }
        set { modelNumber = value;
            bool valid = true;
            foreach (char c in modelNumber)
            {
                if (!char.IsLetterOrDigit(c))
                {
                    valid = false;  
                    break;
                }
            }
            if (!valid)
            {
                List<string> errors = new(); 
                errors.Add("ModelNumber不能含有标点符号,空格等");
                SetErrors("ModelNumber", errors);
            }
            else
            {
                ClearErrors("ModelNumber");
            }
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ModelNumber"));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("HasErrors"));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Errors"));
        }
    }
}
  1. 做一个界面,绑定ModelNumber
<Window ...>
    <Window.DataContext>
        <local:Data/>
    </Window.DataContext>
    <StackPanel>
        <TextBox Text="{Binding ModelNumber ,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"/>
        <TextBlock>
            <Run Text="是否有错误"/>
            <Run Text="{Binding HasErrors, Mode=OneWay}"/>
        </TextBlock>
        <ListView ItemsSource="{Binding Errors}"/>
    </StackPanel>
</Window>

动图

自定义验证规则

自定义验证规则很像自定义转换器

  1. 针对某个属性自定义验证规则
public class ValueRule : ValidationRule
{

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        if (value?.ToString() == "123") return new ValidationResult(false, "输入的值不在范围内");
        return new ValidationResult(true, null);
    }
}
  1. 界面上使用验证规则
<StackPanel>
    <TextBox>
        <TextBox.Text>
            <Binding Path="Max" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
                <Binding.ValidationRules>
                    <local:ValueRule/>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
</StackPanel>

可以看出,<Binding.ValidationRules>下面可以放置多个验证规则,按顺序执行,当所有的验证规则都通过后,则调用转换器(如果存在),其中ExceptionValidationRule比较特殊,当输入内容不能转换为其他规则所定义的转换时,也会触发。

错误显示

首先只有设置了Binding.NotifyOnValidationError为true时,才会引发Validation.Error事件,当含有错误时,可以使用静态类Validation中的附加属性ErrorsHasError来获取信息。

通常出现错误时,边框显示未红色,也可以自行设置错误模板,错误模板位于装饰层,它位于普通窗口内容之上。

<TextBox Width="130">
    <TextBox.Text>
        <Binding
            Mode="TwoWay"
            Path="Max"
            UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:ValueRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
    <Validation.ErrorTemplate>
        <ControlTemplate>
            <DockPanel LastChildFill="True">
                <TextBlock
                    DockPanel.Dock="Right"
                    Foreground="Red"
                    Text="*" />
                <Border BorderBrush="Green" BorderThickness="2">
                    <AdornedElementPlaceholder />
                </Border>
            </DockPanel>
        </ControlTemplate>
    </Validation.ErrorTemplate>
</TextBox>

image-20231108140348940

其中AdornedElementPlaceholder代表控件本身,上面案例中是将*放入了控件周围,如果想将*重叠放到控件上面,可以使用Grid,放在同一窗格。

<Validation.ErrorTemplate>
    <ControlTemplate>
        <Grid>
            <TextBlock
                Margin="50,5,0,0"
                DockPanel.Dock="Right"
                Foreground="Red"
                Text="*" />
            <Border BorderBrush="Green" BorderThickness="2">
                <AdornedElementPlaceholder />
            </Border>
        </Grid>
    </ControlTemplate>
</Validation.ErrorTemplate>

image-20231108141002670

但是这样显示不出错误信息,可以使用ToolTip来显示第一个错误内容

<Validation.ErrorTemplate>
    <ControlTemplate>
        <Grid>
            <TextBlock
                Margin="50,5,0,0"
                DockPanel.Dock="Right"
                Foreground="Red"
                Text="*"
                ToolTip="{Binding ElementName=adornerPlaceholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
            <Border BorderBrush="Green" BorderThickness="2">
                <AdornedElementPlaceholder x:Name="adornerPlaceholder" />
            </Border>
        </Grid>
    </ControlTemplate>
</Validation.ErrorTemplate>

上面模板中使用了AdornedElementPlaceholderAdornedElement属性指向背后的元素。

动图

这样只有悬浮在后面的*号时才会显示错误信息,如果想作为TextBox元素本身的ToolTip,可借助Validation.HasError可以实现。

<TextBox Width="130">
    <TextBox.Text>
        <Binding
            Mode="TwoWay"
            Path="Max"
            UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:ValueRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
    <Validation.ErrorTemplate>
        <ControlTemplate>
            <Grid>
                <TextBlock
                    Margin="50,5,0,0"
                    DockPanel.Dock="Right"
                    Foreground="Red"
                    Text="*"
                    ToolTip="{Binding ElementName=adornerPlaceholder, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
                <Border BorderBrush="Green" BorderThickness="2">
                    <AdornedElementPlaceholder x:Name="adornerPlaceholder" />
                </Border>
            </Grid>
        </ControlTemplate>
    </Validation.ErrorTemplate>
    <TextBox.Style>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

动图

验证多个值

很多时候需要动态验证多个绑定值,比如有两个属性,一个Max,一个Min,要求是用户输入Min必须小于Max,要实现这个功能可以使用绑定组来创建。

绑定组的原理很简单,同样是创建继承自ValidationRule的类,不同的是,不能将该规则绑定到单个绑定表达式,而是将其附加到包含所有绑定控件的容器上。

  1. ViewModel中有两个属性
public class Data : INotifyDataErrorInfo,INotifyPropertyChanged
{
    public int Max { set; get; } = 100;
    public int Min { set; get; } = 1;
}
  1. 创建验证规则
public class ValueRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = (BindingGroup)value;
        var d= (Data)bindingGroup.Items[0];
        if (d.Min >= d.Max)
        {
            return new ValidationResult(false, "错误,最小值必须小于最大值");
        }
        return new ValidationResult(true, null);
    }
}
  1. UI上绑定,注意,此处要在Grid中添加绑定组
<Grid Margin="60" TextBox.LostFocus="Grid_LostFocus">
    <Grid.BindingGroup>
        <BindingGroup x:Name="customGroup">
            <BindingGroup.ValidationRules>
                <local:ValueRule />
            </BindingGroup.ValidationRules>
        </BindingGroup>
    </Grid.BindingGroup>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <TextBox
        x:Name="ddd"
        Grid.Row="0"
        Text="{Binding Path=Max, BindingGroupName=customGroup, UpdateSourceTrigger=PropertyChanged}" />
    <TextBox Grid.Row="1" Text="{Binding Path=Min, BindingGroupName=customGroup, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
  1. 此时并不会验证,绑定组使用了事务处理编辑系统,只有正式提交后才会进行验证,所以在Grid上增加事件,当TextBox失去焦点时触发
private void Grid_LostFocus(object sender, RoutedEventArgs e)
{
    customGroup.CommitEdit();
}
  1. 如果验证失败,则整个Grid会认为是无效的。

    动图

注意:

  1. 当存在多个绑定组时,要为BindingGroup设置Name,这样可以在具体绑定时设置绑定组Text="{Binding Path=Max, BindingGroupName=customGroup, UpdateSourceTrigger=PropertyChanged}" />
  2. 默认情况时,Validate方法中接收到的数据是原始对象,而不是新修改的值,所以为了验证新值,可以使用GetValue方法
BindingGroup bindingGroup = (BindingGroup)value;
var d = (Data)bindingGroup.Items[0];
var newValue = bindingGroup.GetValue(d, "Min");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/126707.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

epoll实现 IO复用

1、epoll实现 IO复用 epoll的提出--》它所支持的文件描述符上限是系统可以最大打开的文件的数目&#xff1b;eg&#xff1a;1GB机器上&#xff0c;这个上限10万个左右。 每个fd上面有callback(回调函数)函数&#xff0c;只有活跃的fd才有主动调用callback&#xff0c;不需要轮询…

【Python爬虫】网页抓取实例之淘宝商品信息抓取

之前我们已经说过网页抓取的相关内容 上次我们是以亚马逊某网页的产品为例 抓取价格、品牌、型号、样式等 该网页上价格、品牌、型号、样式等 都只有一个 如果网页上的目标内容 根据不同规格有多个 又该怎么提取呢&#xff1f; ▼如下图所示 当机身颜色、套餐、存储容量…

python3.8.10虚拟环境安装talib总报平台不匹配

目录 环境&#xff1a; 需求&#xff1a; 问题&#xff1a; 概述 过程及解决 解决方案总结 环境&#xff1a; 操作系统&#xff1a;window10、64位 开发工具&#xff1a;pycharm python版本&#xff1a;python3.8.10 需求&#xff1a; 在python3.8.10的虚拟环境中安…

神经网络遗传算法函数极值寻优

大家好&#xff0c;我是带我去滑雪&#xff01; 对于未知的非线性函数&#xff0c;仅仅通过函数的输入和输出数据难以寻找函数极值&#xff0c;这一类问题可以通过神经网络结合遗传算法求解&#xff0c;利用神经网络的非线性拟合能力和遗传算法的非线性寻优能力寻找函数极值。 …

国内外PLC的差异化对比

在聊PLC的市场格局和国产发展现状之前&#xff0c;我们先来简单了解一下PLC的作用。所谓PLC&#xff0c;你可以把它当成是一台小型电脑&#xff0c;只不过这台电脑是专用于工业领域&#xff0c;用来控制各种机械或生产的过程。比如说我们身上穿的衣服&#xff0c;都是由机器缝制…

合成数据在医疗保健行业的案例研究

从机器人辅助手术到医学成像技术&#xff0c;人工智能在医疗保健领域的应用正在迅速改变医疗保健行业&#xff0c;并改善服务成本和服务质量。例如&#xff0c;埃森哲表示&#xff0c;到 150 年&#xff0c;人工智能临床健康应用每年可以为美国医疗保健行业节省 2026 亿美元。 …

Ubuntu22.04 下 NFS 相关问题与完整配置(客户机 MacOS)

categories: [Linux-Shell] tags: Linux NFS 写在前面 最近折腾一下 NFS, 先白嫖一顿华子云的 1 个月服务器, 2C4G 感觉不错了, 但NFS 配置起来还是有点难度, 主要还是随机分配的端口配置方面比较恶心. server环境: 华为云 2C4G Ubuntu22.04 client环境: MacOS M1 with brew …

个人网厅——销户

目录 需求文档 公积金销户类 controller层 service层 service层实现类 1.验证 &#xff08;个人账户&#xff09; 2.提交&#xff08;添加&#xff09; controller层 service层 service层实现类 3.分页查询 controller层 service层 service层实现类 4. 详情查询…

2.【自动驾驶与机器人中的SLAM技术】左乘模型推导ESKF

目录 1. 证明题 证明&#xff1a;若某个高斯随机变量为零均值&#xff0c;协方差为对角线矩阵且大小相同&#xff08;各向同性&#xff09;&#xff0c;那么在乘任意旋转矩阵以后&#xff0c;其均值仍为零&#xff0c;且协方差不变&#xff1b; 2. 代码实现运动方程将F矩阵…

layui table合并相同的列

table.render({elem: #samples,url: /index/Develorderss/samplelists?od_idod_id //数据接口,page: { //支持传入 laypage 组件的所有参数&#xff08;某些参数除外&#xff0c;如&#xff1a;jump/elem&#xff09; - 详见文档layout: [prev, page, next, count,skip,limit]…

链表OJ题(1)

今天讲解两道链表OJ题目。 1.链表的中间节点 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&#xff1a;链表只有一个…

mac 安装使用svn教程

mac 安装使用svn教程 一、安装Homebrew 要在Mac OS上安装SVN&#xff0c;首先需要安装Homebrew。Homebrew是一个流行的包管理器&#xff0c;因此我们将使用它来安装SVN。 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"…

区块链多链数字钱包开发

随着区块链技术的不断发展&#xff0c;多链数字钱包的开发逐渐成为热门领域。多链数字钱包是一种可以支持多种区块链网络的数字钱包&#xff0c;用户可以使用它来存储、管理和转移不同的数字资产。本文将探讨多链数字钱包的开发背景、市场需求、技术实现和未来趋势等方面。 一、…

redisson中的分布式锁二

公平锁&#xff08;Fair Lock&#xff09; 基于Redis的Redisson分布式可重入公平锁也是实现了java.util.concurrent.locks.Lock接口的一种RLock对象。同时还提供了异步&#xff08;Async&#xff09;、反射式&#xff08;Reactive&#xff09;和RxJava2标准的接口。它保证了当…

YB1205B S0T23开关式异步升压具恒压恒流LED驱动器

YB1205B S0T23开关式异步升压具恒压恒流LED驱动器 产品简介&#xff1a; YB1205B是一种输入电压范围宽(0.85.5V),可调恒定电流和限定电流两种模式来驱动白光LED而设计的升压型DCDC变换器。采用变频模式&#xff0c;逐周期限流&#xff0c;使输入输出电流随电源电压降低均匀变…

微服务之Nacos注册管理

文章目录 一、Nacos安装步骤1.安装地址2.安装版本3.目录说明4.端口配置5.启动 二、Nacos服务注册1.Nacos依赖2.客户端修改配置文件3.启动效果图4.总结 三、Nacos服务集群属性1.服务跨集群调用问题2.服务集群属性3.总结 四、Nacos根据集群负载均衡1.修改配置文件2.设置集群服务类…

【C语法学习】20 - 文件访问顺序

文章目录 0 前言1 文件位置指示符2 rewind()函数2.1 函数原型2.2 参数2.3 返回值2.4 使用说明 3 ftell()函数3.1 函数原型3.2 参数3.3 返回值 4 fseek()函数4.1 函数原型4.2 参数4.3 返回值 5 示例5.1 示例15.2 示例2 0 前言 C语言文件访问分为顺序文件访问和随机文件访问。 …

Kotlin库实现多线程爬取数据

由于字数限制&#xff0c;以下是一个简化版的爬虫程序示例&#xff0c;使用了Kotlin的网络库kotlinx.coroutines和kotlinx.html。这个程序会爬取一个简单的Python多线程跑数据的网页&#xff0c;并打印出结果。 import kotlinx.coroutines.* import kotlinx.html.* import java…

oracle-sql语句解析类型

语句执行过程&#xff1a;1. 解析(将sql解析成执行计划) 2.执行 3.获取数据(fetch) 1. shared pool的组成。 share pool是一块内存池。 主要分成3块空间。free&#xff0c; library(库缓存&#xff0c;缓存sql以及执行计划)&#xff0c;row cache(字典缓存) select * from v…

振南技术干货集:C语言的一些“骚操作”及其深层理解(10)

注解目录 第二章《c语言的一些“操作”及其深层理解》 一、字符串的实质就是指针 &#xff08;如何将 35 转为对应的十六进制字符串”0X23”&#xff1f;&#xff09; 二 、转义符\ &#xff08;打入字符串内部的“奸细”。&#xff09; 三、字符串常量的连接 &#xff…