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中灵活地处理数据绑定的转换和校验,提升应用程序的用户体验和数据安全性。

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

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

相关文章

【Unity Shader编程】之图元装配与光栅化

执行方式&#xff1a;自动完成 图元装配自动化流程 顶点坐标存入装配区 → 按绘制模式连接顶点 → 生成完整几何图元 示例&#xff1a;gl.drawArrays(gl.TRIANGLES, 0, 3)自动生成三角形 会自动自动裁剪超出屏幕范围&#xff08;NDC空间外&#xff09;的三角形&#xff0c;仅保…

ssm121基于ssm的开放式教学评价管理系统+vue(源码+包运行+LW+技术指导)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

网工项目理论1.11 网络出口设计

本专栏持续更新&#xff0c;整一个专栏为一个大型复杂网络工程项目。阅读本文章之前务必先看《本专栏必读》。 一.网络出口接入技术 二.单一出口网络结构 三.同运营商多出口结构 四.多运营商多出口结构——出向流量 五.多运营商多出口结构——服务器访问流量 六.多运营商多出口…

Django 5 实用指南(一)安装与配置

1.1 Django5的背景与发展 Django 自从2005年由Adrian Holovaty和Simon Willison在 Lawrence Journal-World 新闻网站上首次发布以来&#xff0c;Django 一直是 Web 开发领域最受欢迎的框架之一。Django 框架经历了多个版本的演进&#xff0c;每次版本更新都引入了新功能、改进了…

Redis实战-扩展Redis

扩展Redis 1、扩展读性能2、扩展写性能和内存容量3、扩展复杂的查询3.1 扩展联合查询3.2 扩展分片排序 如有侵权&#xff0c;请联系&#xff5e; 如有错误&#xff0c;也欢迎批评指正&#xff5e; 本篇文章大部分是来自学习《Redis实战》的笔记 1、扩展读性能 单台Redis服务器…

【AI面板识别】

题目描述 AI识别到面板上有N&#xff08;1 ≤ N ≤ 100&#xff09;个指示灯&#xff0c;灯大小一样&#xff0c;任意两个之间无重叠。 由于AI识别误差&#xff0c;每次别到的指示灯位置可能有差异&#xff0c;以4个坐标值描述AI识别的指示灯的大小和位置(左上角x1,y1&#x…

朴素模式匹配算法与KMP算法(有next[]和nextval[]详细讲解

这篇文章是建立在上篇文章的基础上的,看此篇文章要有串的基本知识 举个例子引进我们今天的知识 假设我们这里有两个字符串,一个主串,一个子串 主串: aaa223aa225 子串: aa22 我们这里需要进行匹配,传统的朴素模式匹配算法,就是主串下标i从1开始,主串j从1开始…

文件操作(PHP)(小迪网络安全笔记~

免责声明&#xff1a;本文章仅用于交流学习&#xff0c;因文章内容而产生的任何违法&未授权行为&#xff0c;与文章作者无关&#xff01;&#xff01;&#xff01; 附&#xff1a;完整笔记目录~ ps&#xff1a;本人小白&#xff0c;笔记均在个人理解基础上整理&#xff0c;…

【分治法】棋盘覆盖问题 C/C++(附代码和测试实例及算法分析)

问题描述 在一个 2 k 2 k 2^k \times 2^k 2k2k大小的棋盘中&#xff0c;有一个与其他方块不同的特殊方块&#xff0c;如下图红色方块。另有4种形态不同的L型骨块&#xff08;见下图&#xff09;&#xff0c;要用图示四种骨块覆盖棋盘上除特殊方格外的其他所有方格&#xff0c…

el-table的hasChildren不生效?子级没数据还显示箭头号?树形数据无法展开和收缩

问题&#xff1a;明明子级只有一条数据&#xff0c;还显示箭头号 原因&#xff1a;最开始row-key写的是id,父级和子级都有该属性&#xff0c;所以展开失效了。 解决方法&#xff1a;row-key&#xff1a;id改成 row-key"name"

2002-2019年各省人口老龄化程度数据

2002-2019年各省人口老龄化程度数据 1、时间&#xff1a;2002-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;地区、年度、六十五岁以上占比 4、范围&#xff1a;31省 5、指标解释&#xff1a;人口老龄化是指人口生育率降低和人均寿命延长导致的总人…

面向机器学习的Java库与平台简介、适用场景、官方网站、社区网址

Java机器学习的库与平台 最近听到有的人说要做机器学习就一定要学Python&#xff0c;我想他们掌握的知道还不够系统全面。本文作者给大家介绍几种常用Java实现的机器学习库&#xff0c;快快收藏加关注吧&#xff5e; Java机器学习库表格 Java机器学习库整理库/平台概念适合场…

MySQL 之服务器配置和状态(MySQL Server Configuration and Status)

MySQL 之服务器配置和状态 1 MySQL 架构和性能优化 1.3 服务器配置和状态 设置 MySQL 服务的特性&#xff0c;可以通过 mysqld 服务选项&#xff0c;服务器系统变量和服务器状态变量这三个方面来进行设置和查看。 官方文档 https://dev.mysql.com/doc/refman/8.0/en/serve…

Linux的基础指令和环境部署,项目部署实战(下)

目录 上一篇&#xff1a;Linxu的基础指令和环境部署&#xff0c;项目部署实战&#xff08;上&#xff09;-CSDN博客 1. 搭建Java部署环境 1.1 apt apt常用命令 列出所有的软件包 更新软件包数据库 安装软件包 移除软件包 1.2 JDK 1.2.1. 更新 1.2.2. 安装openjdk&am…

LabVIEW无刷电机控制器检测系统

开发了一种基于LabVIEW的无刷电机控制器检测系统。由于无刷电机具有高效率、低能耗等优点&#xff0c;在电动领域有取代传统电机的趋势&#xff0c;而无刷电机的核心部件无刷电机控制器产量也在不断增长。然而&#xff0c;无刷电机控制器的出厂检测仍处于半自动化状态&#xff…

《仙台有树》里的馅料(序)

《仙台有树》一起追剧吧&#xff08;二&#xff09;&#xff1a;馅料合集概览 ●德爱武美玩&#xff0c;全面发展 ●猜猜我是谁&真假美清歌 ●失忆的风还是吹到了仙台 ●霸道师徒强制收&你拜我&#xff0c;我拜你&#xff0c;师徒徒师甜蜜蜜 ●霸道总裁强制爱 ●仙台有…

网站搭建基本流程

需求分析&#xff1a; 实现网站搭建的过程&#xff1a;首先进行网站的需求性分析 网站可分为前台系统和后台系统&#xff0c;由不同的功能拆分为不同的模块 如下是一个电商网站可以拆分出的模块&#xff1a; 在编写代码前&#xff0c;我们要先对网站进行架构&#xff0c;通过…

反射机制的简单示例

一个使用反射机制的简单示例&#xff0c;这个示例将展示如何使用反射来实现一个通用的数据导出功能。 首先&#xff0c;让我们创建必要的项目结构和文件&#xff1a; 首先修改 pom.xml 添加依赖&#xff1a; <?xml version"1.0" encoding"UTF-8"?&…

Qt:多元素控件

目录 多元素控件介绍 QListWidget QTableWidget QTreeWidget 多元素控件介绍 多元素控件表示这个控件中包含了很多的元素&#xff0c;元素可能指的是字符串&#xff0c;也可以指的是更加复杂的数据结构、图片等等 Qt 中提供的多元素控件有: QListWidgetQListViewQTableW…

DeepSeek 助力 Vue 开发:打造丝滑的范围选择器(Range Picker)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…