WPF9-数据绑定进阶

目录

  • 1. 定义
  • 2. 背景
  • 3. Binding源
    • 3.1. 使用Data Context作为Binding的源
    • 3.2. 使用LINQ检索结果作为Binding的源
  • 4. Binding对数据的转换和校验
    • 4.1. 需求
    • 4.2. 实现步骤
    • 4.3. 值转换和校验的好处
      • 4.3.1. 数据转换的好处
    • 4.4. 数据校验的好处
    • 4.5. 原理
      • 4.5.1. 值转换器原理
      • 4.5.2. 数据校验原理

前面的博文我们讨论了Binding的Path知道了如何在一个对象身上寻找数据。本篇继续看如何为Binding指定Source。

1. 定义

Binding,出于方便业界一直使用Binding一词的音译,即“绑定”。我理解Binding更注重表达它是一种像桥梁一样的关联关系。WPF中,正是在这段桥梁上我们有机会为往来流通的数据做很多事情。

Binding在源与目标之间架起了沟通的桥梁,默认情况下数据既能够通过Binding送达目标,也能够从目标返回源(收集用户对数据的修改)。

有时候数据只需要展示给用户、不允许用户修改,这时候可以把Binding模式更改为从源向目标的单向沟通。Binding还支持从目标向源的单向沟通以及只在Binding关系确立时读取一次数据,这需要我们根据实际情况去选择。

控制Binding数据流向的属性是Mode, 它的类型是BindingMode枚举。

BindingMode可取值为:

TwoWay

OneWay

OnTime

OneWayToSource

Default

这里的Default值是指Binding的模式会根据目标的实际情况来确定,比如若是可编辑的(如TextBox.Text属性),Default就采用双向模式;

若是只读的(如TextBlock.Text)则采用单向模式。

2. 背景

让我们回归程序的本质。程序的本质是数据加算法,用户给进一个输入,经过算法的处理程序会反馈一个输出。

这里,数据处于程序的核心地位。反过头来再看“UI驱动程序”,数据处于被动地位,总是在等待程序接收来自UI的消息/事件后被处理或者算法完成处理后被显示。

如何在GUI编程时把数据的地位由被动变主动、让数据回归程序的核心呢?这就是WPF中的Data Binding的背景。

WPF具有这种能力的关键是它引入了Data Binding概念以及与之配套的Dependency Property系统和DataTemplate

在从传统的Windows Form迁移到WPF之后,对于一个三层程序而言,数据存储层由数据库和文件系统来构建,数据传输和处理仍然使用.NET FramworkADO.NET等基本类(与Windows Form等开发一样),展示层则使用WPF类库来实现,而展示层与逻辑层的沟通就使用Data Binding`来实现。

可见Data Binding在WPF系统中起到的是数据高速公路的作用。有了这条高速公路,加工好的数据会自动送达用户界面加以显示,被用户修改过的数据也会自动传回逻辑层, 一旦数据被加工好又会被送达用户界面……程序的逻辑层就像一个强有力的引擎不停运转,用加工好的数据 驱动程序的用户界面以文字、图形、动画等形式把数据显示出来——这就是“数据驱动UI”。

3. Binding源

Binding的源是数据的来源,所以,只要一个对象包含数据并能通过属性把数据暴露出来,它就能当作Binding的源来使用。包含数据的对象比比皆是,但必须为Binding的Source指定合适的对象Binding才能正确工作,常见的办法有:

  1. 把普通CLR类型单个对象指定为Source:包括.NETFramework自带类型的对象和用户自定义类型的对象。如果类型实现了INotifyPropertyChanged接口,则可通过在属性的set语句里激发PropertyChanged事件来通知Binding数据已被更新。
  2. 把普通CLR集合类型对象指定为Source:包括数组、List、ObservableCollection等集合类型。实际工作中,我们经常需要把一个集合作为ItemsControl派生类的数据源来使用,一般是把控件的ItemsSource属性使用Binding关联到一个集合对象上。
  3. 把ADO.NET数据对象指定为Source:包括DataTable和DataView等对象。
  4. 使用XmlDataProvider把XML数据指定为Source:XML作为标准的数据存储和传输格
    式几乎无处不在,我们可以用它表示单个数据对象或者集合;一些WPF控件是级联式的

(如TreeView和Menu),我们可以把树状结构的XML数据作为源指定给与之关联的Binding。

  1. 把依赖对象(DependencyObject)指定为Source:依赖对象不仅可以作为Binding的目标,同时也可以作为Binding的源。这样就有可能形成Binding链。依赖对象中的依赖属性可以作为Binding的Path。
  2. 把容器的DataContext指定为Source(WPFDataBinding的默认行为):有时候我们会遇到这样的情况——我们明确知道将从哪个属性获取数据,但具体把哪个对象作为Binding源还不能确定。这时候,我们只能先建立一个Binding、只给它设置Path而不设置Source,让这个Binding自己去寻找Source。这时候,Binding会自动把控件的DataContext当作自己的Source(它会沿着控件树一层一层向外找,直到找到带有Path指定属性的对象为止)。
  3. 通过ElementName指定Source:在C#代码里可以直接把对象作为Source赋值给Binding,但XAML无法访问对象,所以只能使用对象的Name属性来找到对象。
  4. 通过Binding的RelativeSource属性相对地指定Source:当控件需要关注自己的、自己容器的或者自己内部元素的某个值就需要使用这种办法。
  5. 把ObjectDataProvider对象指定为Source:当数据源的数据不是通过属性而是通过方法暴露给外界的时候,我们可以使用这两种对象来包装数据源再把它们指定为Source。
  6. 把使用LINQ检索得到的数据对象作为Binding的源。

3.1. 使用Data Context作为Binding的源

前面的例子都是把单个CLR类型对象指定为Binding的Source,方法有两种——把对象赋值给Binding.Source属性或把对象的Name赋值给Binding.ElementName。

DataContext属性被定义在FrameworkElement类里,这个类是WPF控件的基类,这意味着所有WPF控件(包括容器控件)都具备这个属性。

如前所述,WPF的UI布局是树形结构,这棵树的每个结点都是控件,由此我们推出另一个结论——在UI元素树的每个结点都有DataContext。

这一点非常重要,因为当一个Binding只知道自己的Path而不知道自己的Soruce时,它会沿着UI元素树一路向树的根部找过去,

每路过一个结点就要看看这个结点的DataContext是否具有Path所指定的属性。如果有,那就把这个对象作为自己的Source;如果没有,那就继续找下去;

如果到了树的根部还没有找到,那这个Binding就没有Source,因而也不会得到数据。

先创建一个名为Student的类,它具有Id、Name、Age三个属性:

public class Student{public int Id { get; set; }public string Name { get; set; }public int Age { get; set; }}

<Window x:Class="WpfApp1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfApp1"Title="WPF 绑定进阶" Height="450" Width="800"><StackPanel Background="LightBlue" Margin="10"><!-- DataContext 设置为 Student 对象 --><StackPanel.DataContext><local:Student Id="6" Name="Tim" Age="29" /></StackPanel.DataContext><!-- 绑定到 Student 的属性 --><TextBox Text="{Binding Path=Id, Mode=TwoWay}" Margin="5" /><TextBox Text="{Binding Path=Name, Mode=TwoWay}" Margin="5" /><TextBox Text="{Binding Path=Age, Mode=TwoWay}" Margin="5" /></StackPanel>
</Window>

这个UI布局可以下面树状图来表示:

文章配图

在实际工作中DataContext的用法是非常灵活的。比如:

(1)当UI上的多个控件都使用Binding关注同一个对象时,不妨使用DataContext。

(2)当作为Source的对象不能被直接访问的时候——比如B窗体内的控件想把A窗体内的控件当作自己的Binding源时,

但A窗体内的控件是private访问级别,这时候就可以把这个控件(或者控件的值)作为窗体A的DataContext(这个属性是public访问级别的)从而暴露数据。

形象地说,这时候外层容器的DataContext就相当于一个数据的“制高点”,只要把数据放上去,别的元素就都能看见。

另外,DataContext本身也是一个依赖属性,我们可以使用Binding把它关联到一个数据源上。

3.2. 使用LINQ检索结果作为Binding的源

自3.0版开始,.NET Framework开始支持LINQ(Language-IntegratedQuery,语言集成查询),

使用LINQ,我们可以方便地操作集合对象、DataTable对象和XML对象而不必动辄就把好几层foreach循环嵌套在一起却只是为了完成一个很简单的任务。

LINQ查询的结果是一个IEnumerable类型对象,而IEnumerable又派生自IEnumerable,所以它可以作为列表控件的ItemsSource来使用。

文章配图

我们先来看查询集合对象。要从一个已经填充好的 List对象中检索出所有名字以字母T 开头的学生,代码如下:

<StackPanel Background="LightBlue" Margin="10"><ListView x:Name="listViewStudents" Height="143" Margin="5"><ListView.View><GridView><GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}" /><GridViewColumn Header="Name" Width="100" DisplayMemberBinding="{Binding Name}" /><GridViewColumn Header="Age" Width="80" DisplayMemberBinding="{Binding Age}" /></GridView></ListView.View></ListView><Button Content="Load" Height="25" Margin="5" Click="Button_Click" /></StackPanel>
public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}private void Button_Click(object sender, RoutedEventArgs e){List<Student> stuList = new List<Student>{new Student { Id = 0, Name = "Tim", Age = 29 },new Student { Id = 1, Name = "Tom", Age = 28 },new Student { Id = 2, Name = "Kyle", Age = 27 },new Student { Id = 3, Name = "Tony", Age = 26 },new Student { Id = 4, Name = "Vina", Age = 25 },new Student { Id = 5, Name = "Mike", Age = 24 },};this.listViewStudents.ItemsSource = from stu in stuList where stu.Name.StartsWith("T") select stu;}}

4. Binding对数据的转换和校验

前面我们已经知道,Binding的作用就是架在Source与Target之间的桥梁,数据可以在这座桥梁的帮助下来流通。

就像现实世界中的桥梁会设置一些关卡进行安检一样,Binding这座桥上也可以设置关卡对数据的有效性进行检验,不仅如此,当Binding两端要求使用不同的数据类型时,我们还可以为数据设置转换器。

WPF中通过**值转换器(IValueConverter)和数据校验(IDataErrorInfo或INotifyDataErrorInfo)**来实现数据的转换和校验。

这种机制使得数据在显示和更新时更加灵活和安全。

4.1. 需求

假设我们有一个Person类,包含以下属性:

Age:用户的年龄(整数)。

IsAdult:一个布尔值,表示用户是否成年(年龄是否大于等于18)。

我们需要实现以下功能:

数据转换:将Age属性的值转换为布尔值IsAdult,并在UI中显示。

数据校验:确保用户输入的年龄是有效的(例如,年龄必须大于0)。

4.2. 实现步骤

(1) 创建Person类

Person类实现INotifyPropertyChanged接口,用于支持数据绑定的更新,并实现IDataErrorInfo接口用于数据校验。

文件:Person.cs


using System.ComponentModel;namespace WpfDataBindingExample{public class Person : INotifyPropertyChanged, IDataErrorInfo{private int age;public int Age{get => age;set{if (age != value){age = value;OnPropertyChanged(nameof(Age));OnPropertyChanged(nameof(IsAdult)); // 通知依赖属性更新}}}public bool IsAdult => Age >= 18;// 实现 INotifyPropertyChanged 接口public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}// 实现 IDataErrorInfo 接口public string Error => null; // 不需要整体校验public string this[string columnName]{get{switch (columnName){case nameof(Age):if (Age <= 0)return "年龄必须大于0";break;}return null; // 无错误}}}
}

(2) 创建值转换器

创建一个值转换器,将Age转换为布尔值IsAdult。

文件:AgeToAdultConverter.cs


using System;using System.Globalization;using System.Windows.Data;namespace WpfDataBindingExample{public class AgeToAdultConverter : IValueConverter{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value is int age){return age >= 18;}return false;}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException(); // 不需要反向转换}}
}

(3) XAML文件

在XAML中,使用Binding将Person对象的属性绑定到UI,并使用值转换器和数据校验。

文件:MainWindow.xaml


<Window x:Class="WpfDataBindingExample.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfDataBindingExample"Title="Data Binding with Conversion and Validation" Height="200" Width="400"><Window.Resources><local:AgeToAdultConverter x:Key="AgeToAdultConverter" /></Window.Resources><StackPanel Margin="10"><TextBox x:Name="AgeTextBox" Width="100" Margin="5" VerticalContentAlignment="Center"Text="{Binding Age, Mode=TwoWay, ValidatesOnDataErrors=True}" /><TextBlock Text="是否成年:" Margin="5" /><TextBox IsReadOnly="True" Background="LightGray" Width="100" Margin="5" VerticalContentAlignment="Center"Text="{Binding IsAdult, Converter={StaticResource AgeToAdultConverter}}" /></StackPanel>
</Window>

(4) 代码后台

在代码后台中,初始化Person对象并设置为DataContext。

文件:MainWindow.xaml.cs


using System.Windows;namespace WpfDataBindingExample{public partial class MainWindow : Window{public MainWindow(){InitializeComponent();DataContext = new Person { Age = 20 }; // 初始化数据}}
}

4.3. 值转换和校验的好处

4.3.1. 数据转换的好处

  1. 灵活性:值转换器允许在绑定过程中对数据进行任意转换,例如格式化、逻辑判断等。
  2. 解耦:将数据转换逻辑从UI代码中分离出来,便于维护和复用。

4.4. 数据校验的好处

  1. 用户体验:在用户输入无效数据时,及时给出反馈,提升用户体验。
  2. 数据完整性:确保绑定到模型的数据始终符合业务规则,避免无效数据进入后端逻辑。
  3. 安全性:防止用户输入非法数据,减少潜在的安全风险。

4.5. 原理

4.5.1. 值转换器原理

IValueConverter接口定义了Convert和ConvertBack方法。

Convert方法用于将源数据转换为目标数据。

ConvertBack方法用于将目标数据转换回源数据(可选实现)。

在XAML中通过Binding的Converter属性指定值转换器。

4.5.2. 数据校验原理

IDataErrorInfo接口允许在数据绑定时对属性进行校验。

如果校验失败,Binding会自动将错误信息显示在UI上(例如,输入框显示红色边框)。

校验逻辑在this[string columnName]属性中实现,返回错误信息或null。

通过上述实现,我们可以在WPF中灵活地处理数据绑定的转换和校验,提升应用程序的用户体验和数据安全性。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据治理:数字时代的关键密码

大数据治理&#xff1a;数字时代的关键密码 在信息技术飞速发展的今天&#xff0c;数字化浪潮席卷全球&#xff0c;深刻地改变着我们的生活和工作方式。数据&#xff0c;作为数字化时代的核心资产&#xff0c;正以前所未有的速度增长和积累。据国际数据公司&#xff08;IDC&am…

LeetCode 1299.将每个元素替换为右侧最大元素:倒序遍历,维护最大值,原地修改

【LetMeFly】1299.将每个元素替换为右侧最大元素&#xff1a;倒序遍历&#xff0c;维护最大值&#xff0c;原地修改 力扣题目链接&#xff1a;https://leetcode.cn/problems/replace-elements-with-greatest-element-on-right-side/ 给你一个数组 arr &#xff0c;请你将每个…

机器学习面试题汇总

1. 基础知识 什么是监督学习和无监督学习? 监督学习是基于已标注的训练数据来学习预测模型;无监督学习则是在没有标签的数据上进行学习,寻找数据的结构或模式。什么是过拟合和欠拟合? 过拟合是指模型在训练数据上表现很好,但在测试数据上表现差。欠拟合是指模型在训练数据…

【SQL教程|07】sql中条件查询where用法示例

SQL WHERE 条件查询教程 在SQL中&#xff0c;WHERE 条件用于在 SELECT 语句后过滤结果集&#xff0c;只返回符合条件的记录。它帮助我们从大量数据中提取所需的信息。以下是使用 WHERE 条件的逐步指南。 1. 基本语法 SELECT [字段] FROM [表] WHERE [条件];SELECT&#xff1a…

力扣 跳跃游戏 II

贪心算法&#xff0c;存下每一步的最远&#xff0c;去达到全局的最小跳跃次数。 题目 从题中要达到最少次数&#xff0c;肯定是每一步尽可能走远一点。但注意j被限制了范围&#xff0c;这种不用想每一步遍历时肯定选最大的num[i]&#xff0c;但要注意&#xff0c;题中是可以到…

如何查看 Linux 服务器的 MAC 地址:深入解析与实践指南

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Linux驱动学习(二)--字符设备

设备分类 字符设备块设备网络设备 内核结构图&#xff1a; 字符设备号 字符设备号是32位的无符号整型值 高12位&#xff1a;主设备号低20位&#xff1a;次设备号 查看设备号 cat /proc/devices 设备号构造 直接使用宏MKDEV #define MKDEV(ma,mi) (((ma) << MINORBITS…

开发小技巧分享 02:xml解析工具

1.百度词条 可扩展标记语言 (Extensible Markup Language, XML) &#xff0c;标准通用标记语言的子集&#xff0c;可以用来标记数据、定义数据类型&#xff0c;是一种允许用户对自己的标记语言进行定义的源语言。 XML是标准通用标记语言 可扩展性良好,内容与形式分离,遵循严格的…

ffmpeg configure 研究1-命令行参数的分析

author: hjjdebug date: 2025年 02月 14日 星期五 17:16:12 CST description: ffmpeg configure 研究1 ./configure 命令行参数的分析 文章目录 1 configure 对命令行参数的分析,在4019行1.1 函数名称: is_in1.2. 函数名称: enable1.3. 函数名称: set_all 2 执行退出判断的关键…

Linux操作系统:从分布式计算到容器化的实践

Linux集群与高可用性技术&#xff1a;从分布式计算到容器化的实践 摘要 随着云计算和大数据技术的飞速发展&#xff0c;Linux集群和高可用性技术已成为现代IT架构的核心组成部分。本文以幽默风趣的方式&#xff0c;深入探讨了Linux集群技术&#xff08;如Hadoop、Spark等分布…

python和pycharm 和Anaconda的关系

好的&#xff0c;下面我会详细说明 Python、PyCharm 和 Anaconda 三者的关系&#xff0c;并逐一解释它们的功能和作用。 1. Python&#xff08;编程语言&#xff09; 定义&#xff1a;Python 是一种高级编程语言&#xff0c;设计简洁&#xff0c;易于学习&#xff0c;且功能强…

STM32 外部中断和NVIC嵌套中断向量控制器

目录 背景 外部中断/事件控制器(EXTI) 主要特性 功能说明 外部中断线 嵌套向量中断控制器 特性 ‌中断线&#xff08;Interrupt Line&#xff09; 中断线的定义和作用 STM32中断线的分类和数量 优先级分组 抢占优先级&#xff08;Preemption Priority&#xff09; …

代码随想录算法【Day49】

Day49 42. 接雨水 思路 这道题利用单调栈进行横向求解。对于每一个元素&#xff0c;找到它右边第一个比它大的元素和左边第一个比它大&#xff08;或者与它相等的元素&#xff0c;当然这种情况可以忽略&#xff09;&#xff0c;最后计算雨水的存储量&#xff1a;&#xff08…

PHP 网络编程介绍

PHP 学习资料 PHP 学习资料 PHP 学习资料 在当今数字化时代&#xff0c;网络编程是开发各类应用必不可少的技能。PHP 作为一门广泛应用于 Web 开发的编程语言&#xff0c;同样具备强大的网络编程能力。接下来&#xff0c;我们将深入探讨 PHP 中网络连接的建立、Socket 编程、…

《深度学习》——ResNet网络

文章目录 ResNet网络ResNet网络实例导入所需库下载训练数据和测试数据设置每个批次的样本个数判断是否使用GPU定义残差模块定义ResNet网络模型导入GPU定义训练函数定义测试函数创建损失函数和优化器训练测试数据结果 ResNet网络 ResNet&#xff08;Residual Network&#xff0…

为什么要学习AI、掌握AI技能有什么用?

随着人工智能的迅速的发展&#xff0c;DeepSeek的爆火&#xff0c;加之目前就业环境的走向&#xff0c;越来越多的职场朋友开始关注到AI的发展&#xff0c;重视AI技能的掌握。不少同学都会问&#xff1a;“职场人为什么要学习AI、掌握AI技能&#xff1f;” 为什么要学AI 现…

AIP-146 泛化域

编号146原文链接AIP-146: Generic fields状态批准创建日期2019-05-28更新日期2019-05-28 API中的大多数域&#xff0c;无论是在请求、资源还是自定义应答中&#xff0c;都有具体的类型或模式。这个模式是约定的一部分&#xff0c;开发者依此约定进行编码。 然而&#xff0c;偶…

vue3和vue2的组件开发有什么区别

Vue3和Vue2在组件开发上存在不少差异&#xff0c;下面从多个方面详细介绍&#xff1a; 响应式原理 Vue2&#xff1a;用Object.defineProperty()方法来实现响应式。打个比方&#xff0c;它就像给对象的每个属性都安排了一个“小管家”&#xff0c;属性被访问或修改时&#xff0…

【NLP 25、模型训练方式】

目录 一、按学习范式分类 1. 监督学习&#xff08;Supervised Learning&#xff09; 2. 无监督学习&#xff08;Unsupervised Learning&#xff09; 3. 半监督学习&#xff08;Semi-supervised Learning&#xff09; 4. 强化学习&#xff08;Reinforcement Learning, RL&#x…

1-知识图谱-概述和介绍

知识图谱&#xff1a;浙江大学教授 陈华军 知识图谱 1课时 http://openkg.cn/datasets-type/ 知识图谱的价值 知识图谱是有什么用&#xff1f; 语义搜索 问答系统 QA问答对知识图谱&#xff1a;结构化图 辅助推荐系统 大数据分析系统 自然语言理解 辅助视觉理解 例…