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 绑定模式的系统开销。

用前面的例子来说:

我们将MyDataDisplayText 属性绑定到TextBox.Text属性上,

当修改MyData.DisplayText属性时,界面会进行更新。

但是在TextBox 进行编辑时,MyData.DisplayText属性不会被更新。

TwoWay模式(默认)(UI <=> 属性)

  • 通过 TwoWay 绑定,更改源属性或目标属性时会自动更新另一方。 此类型的绑定适用于可编辑窗体或其他完全交互式 UI 方案。 大多数属性默认为 OneWay 绑定,但某些依赖属性(通常为用户可编辑控件的属性,例如 TextBox.TextCheckBox.IsChecked)默认为 TwoWay 绑定。

用前面的例子来说:

我们将MyDataDisplayText 属性绑定到TextBox.Text属性上,

当修改MyData.DisplayText属性时,界面会进行更新。

当在TextBox 进行编辑时,MyData.DisplayText属性也会被更新。

OneWayToSource模式

  • OneWayToSource 绑定与 OneWay 绑定相反;当目标属性更改时,它会更新源属性。(UI -> 属性)

用前面的例子来说:

我们将MyDataDisplayText 属性绑定到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属性来设置。

需要注意的是只有TwoWayOneWayToSource 模式的绑定会生效,因为只有这两种模式的数据流是从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

相关推荐
神仙别闹1 小时前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
向宇it10 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
九鼎科技-Leo11 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
Heaphaestus,RC12 小时前
【Unity3D】获取 GameObject 的完整层级结构
unity·c#
baivfhpwxf202312 小时前
C# 5000 转16进制 字节(激光器串口通讯生成指定格式命令)
开发语言·c#
直裾12 小时前
Scala全文单词统计
开发语言·c#·scala
ZwaterZ14 小时前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
ZwaterZ16 小时前
el-table-column自动生成序号&&在序号前插入图标
前端·javascript·c#·vue
SRC_BLUE_1718 小时前
SQLI LABS | Less-55 GET-Challenge-Union-14 Queries Allowed-Variation 2
oracle·c#·less
yngsqq19 小时前
037集——JoinEntities连接多段线polyline和圆弧arc(CAD—C#二次开发入门)
开发语言·c#·swift