03 数据绑定
03 数据绑定
数据绑定顾名思义就是将窗体某些控件的值绑定到某些数据上,如:根据用户的输入框内容自动修改某些控件的文本,常规代码写法如下
<TextBox Name="tb" HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Height="30" TextChanged="TextBox_TextChanged"></TextBox>
<Label Name="lbl" HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Height="50" Margin="0,50,0,0"></Label>
隐藏代码如下
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
this.lbl.Content = ((TextBox)sender).Text;
}
使用数据绑定的语法如下
使用{}
表示要进行数据的绑定,ElementName
指定要绑定控件的名字,使用Path
指定绑定对应控件的哪个属性值
<Label Content="{Binding ElementName=tb,Path=Text}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Height="50" Margin="0,120,0,0"></Label>
数据绑定概述 - WPF .NET | Microsoft Learn
WPF 中的数据绑定与传统模型相比具有几个优点,包括本质上支持数据绑定的大量属性、灵活的数据 UI 表示形式以及业务逻辑与 UI 的完全分离。
什么是数据绑定?
数据绑定是在应用 UI 与其显示的数据之间建立连接的过程。 如果绑定具有正确的设置,并且数据提供适当的通知,则在数据更改其值时,绑定到该数据的元素会自动反映更改。数据绑定还意味着,如果元素中数据的外部表示形式发生更改,则基础数据可以自动进行更新以反映更改。
例如,如果用户编辑 TextBox 元素中的值,则基础数据值会自动更新以反映该更改。
数据绑定的典型用法是将服务器或本地配置数据放置到窗体或其他 UI 控件中。 此概念在 WPF 中得到扩展,包括将大量属性绑定到不同类型的数据源。
在 WPF 中,元素的依赖属性可以绑定到 .NET 对象(包括 ADO.NET 对象或与 Web 服务和 Web 属性关联的对象)和 XML 数据。
数据绑定基本概念
不论要绑定什么元素,也不论数据源是什么性质,每个绑定都始终遵循下图所示的模型
通常情况下,每个绑定具有四个组件:
- 绑定目标对象。
- 目标属性。
- 绑定源。
- 指向绑定源中要使用的值的路径。
例如,如果将 TextBox 的内容绑定到 Employee.Name 属性,则可以类似如下所示设置绑定:
设置 | “值” |
---|---|
目标 | TextBox |
目标属性 | Text |
源对象 | Employee |
源对象值路径 | Name |
数据流的方向
正如上图中的箭头所示,绑定的数据流可以从绑定目标流向绑定源(例如,当用户编辑 TextBox
的值时,源值会发生更改)和/或(在绑定源提供正确通知的情况下)从绑定源流向绑定目标(例如,TextBox
内容会随绑定源中的更改而进行更新)。
你可能希望应用允许用户更改数据,然后将该数据传播回源对象。 或者,可能不希望允许用户更新源数据。 可以通过设置 Binding.Mode 来控制数据流。
此图演示了不同类型的数据流:
- 通过 OneWay 绑定,对源属性的更改会自动更新目标属性,但对目标属性的更改不会传播回源属性
- 通过 TwoWay 绑定,更改源属性或目标属性时会自动更新另一方。
- OneWayToSource 绑定与 OneWay 绑定相反;当目标属性更改时,它会更新源属性。
触发源更新的因素
TwoWay 或 OneWayToSource 绑定侦听目标属性中的更改,并将更改传播回源(称为更新源)。 例如,可以编辑文本框的文本以更改基础源值。
但是,在编辑文本时或完成文本编辑后控件失去焦点时,源值是否会更新? Binding.UpdateSourceTrigger 属性确定触发源更新的因素。 下图中右箭头的点说明了 Binding.UpdateSourceTrigger 属性的角色。
如果 UpdateSourceTrigger
值为 UpdateSourceTrigger.PropertyChanged,则目标属性更改后,TwoWay 或 OneWayToSource 绑定的右箭头指向的值会立即更新。 但是,如果 UpdateSourceTrigger
值为 LostFocus,则仅当目标属性失去焦点时才会使用新值更新该值。
UpdateSourceTrigger 值 | 源值更新时间 | TextBox 的示例方案 |
---|---|---|
LostFocus (TextBox.Text 的默认值) |
TextBox 控件失去焦点时。 | 与验证逻辑关联的 TextBox(请参阅下文的数据验证)。 |
PropertyChanged |
键入 TextBox 时。 | 聊天室窗口中的 TextBox 控件。 |
Explicit |
应用调用 UpdateSource 时。 | 可编辑窗体中的 TextBox 控件(仅当用户按“提交”按钮时才更新源值)。 |
数据的绑定
数据的绑定使用{Binding ElementName=控件名, Path=属性 }
,如下:
<Slider Name="slider" Background="Red" Foreground="Green" Maximum="100"/>
<TextBox Text="{Binding ElementName=slider, Path=Value}"/>
该例子中Slider
滑动会同步修改输入框的内容,TextBox
的修改将会在失去焦点后同步到滑块,我们也可以通过修改Mode
和UpdateSourceTrigger
属性来修改这些默认的行为
Mode
控制数据绑定的模式
<!--双向绑定-->
<Slider Name="slider1" Background="Red" Foreground="Green" Maximum="100"/>
<TextBox Text="{Binding ElementName=slider1, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<!--数据源流向目标对象OneWay-->
<Slider Name="slider2" Background="Red" Foreground="Green" Maximum="100"/>
<TextBox Text="{Binding ElementName=slider2, Path=Value, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<!--目标对象流向数据源-->
<Slider Name="slider4" Background="Red" Foreground="Green" Maximum="100"/>
<TextBox Text="{Binding ElementName=slider4, Path=Value, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
<!--数据源流向目标对象一次OneTime,只有在程序初始化时根据数据源流向目标对象1次-->
<Slider Name="slider3" Background="Red" Foreground="Green" Maximum="100" Value="30"/>
<TextBox Text="{Binding ElementName=slider3, Path=Value, Mode=OneTime, UpdateSourceTrigger=PropertyChanged}"/>
UpdateSourceTrigger
控制何时触发修改操作,以下代码着重演示了Explicit
的使用,当点击
<!-- LostFocus: 控件失去焦点时执行 -->
<!-- PropertyChanged: 属性发生变化时执行 -->
<!-- Explicit: 当调用UpdateSource时触发 -->
<Slider Name="slider1" Background="Red" Foreground="Green" Maximum="100"/>
<TextBox Name="a" Text="{Binding ElementName=slider1, Path=Value, Mode=TwoWay, UpdateSourceTrigger=Explicit}"/>
<Button Content="更新" Click="Button_Click_1" />
private void Button_Click_1(object sender, RoutedEventArgs e)
{
// 控件.GetBindingExpression(控件类型.xxxProperty).UpdateSource(); 调用该方法执行数据的更新
a.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
绑定类成员
除了会将控件的某些属性绑定到其他控件属性上之外,还经常会将控件的属性绑定到类的成员上,绑定类成员的方式如下
绑定当前类成员
如果需要将当前窗体控件绑定到当前类的某个属性上,需要以下步骤
- 给当前window添加name属性并命名
- 将类属性绑定到控件上即可
<Window x:Class="WpfApp1.MainWindow"
Name="main">
<StackPanel Name="panel" VerticalAlignment="Top">
<TextBox Text="{Binding MyName, ElementName=main, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Window>
另一种做法是使用相对资源设置DataContext
,代码如下
<Window.DataContext>
<Binding RelativeSource="{RelativeSource FindAncestor,AncestorType=local:MainWindow}"/>
</Window.DataContext>
<TextBlock Text="{Binding Path=属性名}"/>
绑定其他类成员
绑定其他类成员的方式要麻烦一点
- 导入对应类的命名空间
- 引入资源(引入你要绑定哪个类)
- 声明绑定数据上下文对象
- 绑定数据源的属性
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="764" Width="780"
xmlns:local="clr-namespace:WpfApp1">
<!-- 1、引入命名空间,命名为local -->
<StackPanel Name="panel" VerticalAlignment="Top">
<!-- 2、引入资源(类),并命名为abc-->
<StackPanel.Resources>
<!-- <命名空间名:类名 x:Key="该类别名"/> -->
<local:MyClass x:Key="abc"/>
</StackPanel.Resources>
<!-- 3、设置当前容器的数据上下文为abc-->
<StackPanel.DataContext>
<Binding Source="{StaticResource abc}"/>
</StackPanel.DataContext>
<!-- 4、数据绑定 -->
<TextBox Text="{Binding Path=TestName, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Window>
绑定多个类中的属性
如果我们有两个TextBox
,其中一个绑定Class1的属性,另一个绑定Class2的属性,如果还这么写,程序就会报错,因为一个DataContext
中只能存在一个Binding
再考虑一个场景,如果两个类中都拥有相同的属性,那他该去找哪个呢?
<StackPanel Name="panel" VerticalAlignment="Top">
<!--引入两个类的资源-->
<StackPanel.Resources>
<local:MyClass1 x:Key="one"/>
<local:MyClass2 x:Key="two"/>
</StackPanel.Resources>
<!--设置某个类为数据上下文-->
<StackPanel.DataContext>
<Binding Source="{StaticResource one}"/>
</StackPanel.DataContext>
<!--绑定的属性默认去当前数据上下文查找-->
<TextBox Text="{Binding MyName}" />
<!--也可以指定让他去哪个资源中查找-->
<TextBox Text="{Binding MyName, Source={StaticResource one}}" />
<TextBox Text="{Binding MyName, Source={StaticResource two}}" />
</StackPanel>
另一种写法是直接修改对应控件的DataContext
属性
<TextBox Text="{Binding MyName}" DataContext="{Binding Source={StaticResource two}}" />
或者在隐藏代码中这么写
MyClass1 class1 = new MyClass1() { MyName ="哈啊哈哈" };
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.控件名.DataContext = class1;
}
<TextBlock x:Name="a" Text="{Binding MyName}"/>
绑定集合视图
在 ItemsControl 绑定到数据集合后,你可能希望对数据进行排序、筛选或分组。 为此,应使用集合视图,这些视图是实现 ICollectionView 接口的类。
什么是集合视图
集合视图这一层基于绑定源集合,它允许基于排序、筛选和分组查询来导航并显示源集合,而无需更改基础源集合本身。
由于视图不会更改基础源集合,因此每个源集合都可以有多个关联的视图。 例如,可以有 Task 对象的集合。 使用视图,可以通过不同方式显示相同数据。 例如,可能希望在页面左侧显示按优先级排序的任务,而在页面右侧显示按区域分组的任务。
视图的创建
创建并使用视图的一种方式是直接实例化视图对象,然后将它用作绑定源。而不是绑定真正的数据源
<XXX.Resources>
<!-- 和创建普通的资源类似,将控件名修改为CollectionViewSource即可 -->
<CollectionViewSource
Source="{Binding Path=Ps, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:MainWindow}}"
x:Key="list"
/>
</XXX.Resources>
<DataGrid ItemsSource="{Binding Source={StaticResource list}}">
<DataGrid.Columns>
<DataGridTextColumn Header="姓名" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="年龄" Binding="{Binding Path=Age}"/>
</DataGrid.Columns>
</DataGrid>
集合视图的操作
创建并使用集合视图的一种方式是指定集合视图作为绑定源。 WPF 还会为用作绑定源的每个集合创建一个默认集合视图。 如果直接绑定到集合,WPF 会绑定到该集合的默认视图。
获取集合视图
CollectionViewSource view = (CollectionViewSource)this.xxx.FindResource("list");
视图的排序
// 两个参数分别为排序的列以及排序的方式(正序|倒序)
view.SortDescriptions.Add(new System.ComponentModel.SortDescription("Age", System.ComponentModel.ListSortDirection.Ascending));
视图的过滤
// 添加一个过滤条件
view.Filter += (s, e) =>
{
// 通过设置Accepted属性是否为true表示改数据是否通过筛选
e.Accepted = false;
if (e.Item is People p && p.Age >= 18)
{
e.Accepted = true;
}
};
视图的分组
view.GroupDescriptions.Add(new PropertyGroupDescription() { PropertyName = "要分组的列名称" });
关于DataContext
当在 XAML 元素上声明数据绑定时,它们会通过查看其直接的 DataContext 属性来解析数据绑定。数据上下文通常是绑定源值路径评估的绑定源对象。 可以在绑定中重写此行为,并设置特定的绑定源对象值。 如果未设置承载绑定的对象的 DataContext
属性,则将检查父元素的 DataContext
属性,依此类推,直到 XAML 对象树的根。 简而言之,除非在对象上显式设置,否则用于解析绑定的数据上下文将继承自父级。
DataContext的绑定方式
<object.DataContext>
<!-- 绑定某个静态资源 -->
<Binding Source="{StaticResource abc}"/>
</object.DataContext>
<object.DataContext>
<!-- 绑定当前窗体类 -->
<Binding RelativeSource="{RelativeSource FindAncestor,AncestorType=local:MainWindow}"/>
</object.DataContext>
<object.DataContext>
<!-- 绑定某个命名空间下的某个类 -->
<namespace:ClassName />
</object.DataContext>
也通常在隐藏代码中进行数据的绑定,这样绑定的上下文在xaml中没有类型提示,还需要在xaml中进行类型的标注
xxx.DataContext = new Class();
<object d:DataContext="{d:DesignInstance Type=namespace:ClassName}" />