WPF MVVM入门系列教程(三、数据绑定)
本文主要介绍WPF的数据绑定(Data Binding)功能,如果你已经熟悉本文的内容,可以跳过并直接阅读后面的文章。
什么是数据绑定
我们先来看一下MSDN上的说明:
数据绑定是在应用 UI 与其显示的数据之间建立连接的过程。 如果绑定具有正确的设置,并且数据提供适当的通知,则在数据更改其值时,绑定到该数据的元素会自动反映更改。 数据绑定还意味着,如果元素中数据的外部表示形式发生更改,则基础数据可以自动进行更新以反映更改。
我们先不考虑技术细节 ,通俗点来说,可以理解为:
将控件的某个依赖属性(UI)(BindingTarget)绑定到某个数据(BindingSource)上,当数据进行更改时,绑定的依赖属性值会更新(UI更新)。而当依赖属性的值更改(UI更改)时,绑定的数据也会进行更改。
举个简单的例子:
例如TextBox控件有个依赖属性Text,它可以设置TextBox的显示内容。
然后我们有一个对象,对象里有个属性叫DisplayText, 将TextBox.Text属性绑定到这个对象的DisplayText属性上。
当我们在界面上对这个TextBox的文本进行编辑时,DisplayText属性会更新。反之,我们对DisplayText进行操作时,TextBox也会进行刷新 。
一些基础概念
通常情况下,每个绑定具有四个组件:
- 绑定目标对象。
- 目标属性。
- 绑定源。
- 指向绑定源中要使用的值的路径
例如TextBox控件有个依赖属性Text,然后我们有一个对象Object,对象里有个属性叫DisplayText, 将TextBox.Text属性绑定到这个对象的DisplayText属性上。
对应绑定的四个组件如下:
设置 | “值” |
---|---|
目标 | TextBox |
目标属性 | Text |
源对象 | Object |
源对象值路径 | DisplayText |
说明:
1、目标属性必须为依赖属性
大多数 UIElement 属性都是依赖属性,而大多数依赖属性(只读属性除外)默认支持数据绑定。 只有从 DependencyObject 派生的类型才能定义依赖项属性。 所有 UIElement 类型从 DependencyObject
派生。
2、绑定源不限于自定义 .NET 对象。
绑定源对象不限于自定义 .NET 对象。 WPF 数据绑定支持 .NET 对象、XML 甚至是 XAML 元素对象形式的数据。
3、在建立绑定时,需要将绑定目标绑定到绑定源。 例如,如果要使用数据绑定在 ListBox 中显示List<T>的数据,则需要将 ListBox
绑定到 List<T>数据。
为什么要使用数据绑定
在第一篇文章中,介绍MVVM的基础概念时,使用了上面这张图,来介绍MVVM的原理。可以看到ViewModel层和View层的交互方式里,有DataBinding。
视图为用户提供两项服务:数据展示和与数据交互。数据展示通过读取数据并以某种格式显示,数据交互指的是编辑现有数据或添加新数据。而实现这一功能的基础就是数据绑定。
数据上下文(DataContext)
当在 XAML 元素上声明数据绑定时,WPF会通过查看它的 DataContext 属性来解析数据绑定。在MVVM开发中,就是将ViewModel赋值给整个窗口的DataContext属性。
如果未设置元素的 DataContext
属性,则将检查父元素的 DataContext
属性,依此类推,直到 XAML 对象树的根。 简而言之,除非在对象上显式设置,否则用于解析绑定的数据上下文将继承自父级。
当 DataContext
属性发生更改时,所以的绑定值都会进行刷新。
我们先通过一个简单的例子演示一下:
首先我们创建一个窗口,放置一个TextBox控件,然后将Text属性绑定到DisplayText
1 <Window x:Class="BasicDataContextDemo.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:BasicDataContextDemo" 7 mc:Ignorable="d" 8 Title="MainWindow" Height="450" Width="800" Name="window"> 9 <Grid Name="grid"> 10 <TextBox Name="textbox" HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Text="{Binding DisplayText}"></TextBox> 11 </Grid> 12 </Window>
然后我们创建一个数据对象,其中包含一个DisplayText属性:
1 public class MyData 2 { 3 private string displayText = "HelloWorld"; 4 5 public string DisplayText 6 { 7 get => displayText; 8 set => displayText = value; 9 } 10 }
创建一个MyData对象,并绑定到DataContext上
1 public partial class MainWindow : Window 2 { 3 public MainWindow() 4 { 5 InitializeComponent(); 6 7 var myData = new MyData(); 8 this.textbox.DataContext = myData; 9 } 10 }
运行程序可以看到文本框已经被赋值"HelloWorld"
数据流的方向
在进行绑定时,可以通过Mode属性来设置数据流的方向。WPF中支持以下几种模式。
OneWay模式 (属性 -> UI)
-
通过 OneWay 绑定,对源属性的更改会自动更新目标属性,但对目标属性的更改不会传播回源属性。 如果绑定的控件为隐式只读,则此类型的绑定适用。
如果无需监视目标属性的更改,则使用 OneWay 绑定模式可避免 TwoWay 绑定模式的系统开销。
用前面的例子来说:
我们将MyData的DisplayText属性绑定到TextBox.Text属性上,
当修改MyData.DisplayText属性时,界面会进行更新。
但是在TextBox进行编辑时,MyData.DisplayText属性不会被更新。
TwoWay模式(默认)(UI <=> 属性)
-
通过 TwoWay 绑定,更改源属性或目标属性时会自动更新另一方。 此类型的绑定适用于可编辑窗体或其他完全交互式 UI 方案。 大多数属性默认为 OneWay 绑定,但某些依赖属性(通常为用户可编辑控件的属性,例如 TextBox.Text 和 CheckBox.IsChecked)默认为 TwoWay 绑定。
用前面的例子来说:
我们将MyData的DisplayText属性绑定到TextBox.Text属性上,
当修改MyData.DisplayText属性时,界面会进行更新。
当在TextBox进行编辑时,MyData.DisplayText属性也会被更新。
OneWayToSource模式
-
OneWayToSource 绑定与 OneWay 绑定相反;当目标属性更改时,它会更新源属性。(UI -> 属性)
用前面的例子来说:
我们将MyData的DisplayText属性绑定到TextBox.Text属性上,
当修改MyData.DisplayText属性时,界面不会进行更新。
当在TextBox进行编辑时,MyData.DisplayText属性会被更新。
OntTime模式
-
OneTime 绑定未在图中显示,该绑定会使源属性初始化目标属性,但不传播后续更改。 如果数据上下文发生更改,或者数据上下文中的对象发生更改,则更改不会在目标属性中反映。 如果适合使用当前状态的快照或数据实际为静态数据,则此类型的绑定适合。 如果你想使用源属性中的某个值来初始化目标属性,且提前不知道数据上下文,则此类型的绑定也有用。 此模式实质上是 OneWay 绑定的一种简化形式,它在源值不更改的情况下提供更好的性能。
使用示例如下:
1 <TextBox Text="{Binding DisplayText,Mode=TwoWay}"></TextBox>
属性更改通知
在使用MVVM模式进行开发时,属性更改通知是一个很关键的知识点。
让我们再次看到前面的示例,将TextBox.Text绑定到MyData.DisplayName。
1 var myData = new MyData(); 2 this.textbox.DataContext = myData;
此时我们会发现一个问题,即使我们使用了TwoWay模式绑定,当我们修改MyData.DisplayName值时,界面并不会更新。
1 var myData = this.textbox.DataContext as MyData; 2 myData.DisplayText = "tragedy"; //ui not update
如何实现属性更改通知呢?我们需要对MyData进行一些改造。
需要修改的地方如下
1、将MyData实现 INotifyPropertyChanged 接口。
2、当属性更改时,调用PropertyChanged事件进行通知。
完整的示例如下:
1 public class MyData2 : INotifyPropertyChanged 2 { 3 private string displayText2 = "HelloWorld"; 4 5 public string DisplayText2 6 { 7 get => displayText2; 8 set 9 { 10 displayText2 = value; 11 12 PropertyChanged?.Invoke(this,new PropertyChangedEventArgs("DisplayText2")); 13 14 //or 15 //OnPropertyChanged(); 16 } 17 } 18 19 public event PropertyChangedEventHandler? PropertyChanged; 20 21 22 protected void OnPropertyChanged([CallerMemberName] string name = null) 23 { 24 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 25 } 26 }
此时我们再修改属性的值,界面也会同步刷新。
触发源更新的因素
在进行绑定时,可以通过UpdateSourceTrigger属性来设置如何触发源更新。
通俗点来说,就是当界面上的值更改了,例如我编辑了TextBox,何时去更改绑定的属性值呢?这种情况就可以通过UpdateSourceTrigger属性来设置。
需要注意的是只有TwoWay 或 OneWayToSource 模式的绑定会生效,因为只有这两种模式的数据流是从UI到属性。
支持以下几种模式
Default
大多数依赖属性的默认值是 PropertyChanged,但是 Text 属性的默认值是 LostFocus。
PropertyChanged
每当绑定目标属性发生变化时,立即更新绑定源。
LostFocus
当绑定目标元素失去焦点时更新绑定源。
Explicit
仅在调用 UpdateSource() 方法时更新绑定源。
示例如下:
1 <TextBox Text="{Binding Text,UpdateSourceTrigger=PropertyChanged}"></TextBox>
示例代码
WPF-MVVM-Beginner/3_DataBinding/DataBinding at main · zhaotianff/WPF-MVVM-Beginner · GitHub
参考资料:
数据绑定概述 - WPF .NET | Microsoft Learn
如何:实现属性更改通知 - WPF .NET Framework | Microsoft Learn