之前大家写代码都喜欢用事件驱动,比如说鼠标输入的click事件、初始化的内容全部放在窗体加载完毕的load事件,等等,里面包含了大量的由事件触发后的业务处理代码。导致了UI和业务逻辑高度耦合在一个地方。代码难于维护、也难以优化。

  我们这个章要讲的内容是忘记我们的事件驱动、尝试理解数据驱动。客户端开发分层的话理论上就是数据层、业务逻辑层、UI层,相对于三层的话一般我们的代码可以分为:

  A:数据的持久化存储;

  B:数据的读取和写入;

  C:业务逻辑处理;

  D:界面业务逻辑处理后数据的展示。

  E:界面与业务逻辑的交互。

  在这样的开发过程中A、B一般都是设计最满意的地方。持久化过程做的既通用、又能清晰,持久化数据和实体类之间的定义、转换,都是变动性最小、最稳定的。而C与客户端的关系最紧密、变动也最大。大多数代码都是集中在这里。D、E两部分是负责显示UI、和处理UI的交互逻辑。也有不少的代码量。

  显然C部分是一个程序中,代码量最多,随着版本迭代最容易混乱的地方,所以我们应该重点把精力放在C部分,但是D、E两个部分切因为和业务层紧密相连,C部分的频繁改动很可能导致我们把本来属于C部分的代码写入D、E部分里。比如窗体或控件的Click、构造函数、load里面。因为这2部分以消息或者事件来与逻辑层沟通,所以一旦出现同一个数据需要在多处展示、修改时,用于同步逻辑得代码就会变得复杂,代码也会到处乱写。因为在解决业务问题时,我们的重点在C部分。但是在解决UI交互问题的时候,D、E的UI展示又编程了我们的重点,思维来回的切换,导致我们写出很多难以维护的代码。

  WPF中引入了Data Binding的概念。使用Data Binding配合属性通知和数据模板,我们就可以把关注的D、E的展示层和C的业务逻辑层更好的分割开来。使我们把重点放在业务逻辑层。UI上的元素通过Data Binding可以和数据关联上、一处数据可以和多处UI元素绑定。也可以双向绑定,如果能很好的使用这个思路,我们就可以很好的实现了逻辑层和UI层的解耦。而且所有与业务逻辑相关的代码都会处于业务逻辑层、用户界面不包含任何代码。

  开头讲了这么说,就是想让大家忘记之前的事件驱动写代码的方式。然后尝试开始学习数据驱动写代码的方式。Data Binding就是第一步。

什么是Binding

  我们先来看一个最简单的例子,我们使用Binding来把一个元素的值绑定到另外一个元素的值上。使用ElementName来指向对应的元素Name,Path来指向我们想绑定的元素对应的属性,该例子不包含任何后台代码:

<Window x:Class="BindingExample.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"
xmlns:local="clr-namespace:BindingExample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<TextBlock Text="{Binding ElementName=slider,Path=Value}"/>
<Slider x:Name="slider" Maximum="100" Minimum="1"/>
</StackPanel> </Grid>
</Window>

我们把代码跑起来效果图如下,我们的TextBlock显示的Text和Slider的滑动值绑定在了一起:

在WPF中Binding可以通过调用类的INotifyPropertyChanged的实现自动通知功能使多个绑定了属性的UI元素自动更新UI。在WPF中依赖项属性是个很重要的知识点,但是我觉得应该先讲解bangding,在建立了数据驱动的思维,先去使用数据驱动,再去搞明白数据驱动的原理。而这个例子中我们使用Binding绑定了Slider的属性Value。再Slider 上按F12.进入到类的说明界面。我们看到了又一个Value属性。还有一个属性名为ValueProperty,类型为DependencyProperty的对象。他就是我们所说的依赖项属性。这一章我们不讲他。只讲如何使用。当作普通属性就好。

所以这个例子就是我们把一个Textlock的Text显示内容通过Binding绑定到了Slider的Value属性上。而通过在属性的Set方法中调用INotifyPropertyChanged的实现。所以TextBlock的Text能随着Slider的Value变化跟着一起显示对应的值就行。这里能理解到这样就可以了。继续往下。

既然2个元素可以绑定一个属性。随着DataContext下对应属性的值的变化而变化,就达到了我们要的目的,解耦业务层和UI层。我们通过业务层修改对应的属性,达到更新UI得目的。UI通过更新对应的属性。达到修改业务层得目的。这样我们就可以把重点放在业务层。

通过这个原理,我们尝试创建一个业务层和UI层交互的属性,并绑定它,通过属性更新UI显示结果。通过UI得交互修改属性得值来达到更新业务层。这样我们只需要关注业务层当前值的变化。

我们通过cs文件设置一个简单的属性通知。我们把UI的显示值和设置的Person下的Intelligence属性绑定在一起。如果UI变化了。Intelligence也变化。如果Intelligence变化了。UI也跟着变化。就实现了我们刚才计划的一个业务逻辑层的值变化。直接影响绑定的UI部分。UI的绑定的值变化,可以直接再逻辑层处理。因为只是演示功能。所以我们没有VM层。这里只演示值得变化。后面MVVM会讲解DataContext。和MVVM分层。这里主要理解Binding得值可以双向通知就可以了。

修改XAML代码和CS为以下内容:

<Window x:Class="BindingExample.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"
xmlns:local="clr-namespace:BindingExample "
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Intelligence}"/>
<Slider x:Name="slider" Maximum="100" Minimum="1" Value="{Binding Intelligence}"/>
</StackPanel> </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes; namespace BindingExample
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
Person duwenlong;
public MainWindow()
{
InitializeComponent();
duwenlong = new Person();
this.DataContext = duwenlong;
}
}
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double _intelligence;
public double Intelligence
{
get { return _intelligence; }
set
{
_intelligence = value;
Debug.WriteLine($"Intelligence as {Intelligence}");
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Intelligence"));
}
}
}
}
}

我们再每次Set值得时候。向控制台打印了一个消息。Debug.WriteLine($"Intelligence as {Intelligence}");用来观察是否实现了绑定。我们观察到,值再变化的时候,业务层和UI层是一起再变化的。到此刻我们得目的就达到了。因为基于这个功能,我们结合其他知识我们可以完成很多很多得功能。但是目前要理解和养成数据驱动得思维习惯。

到现在为止,我们也没有在后台写业务代码。因为我们的模板是解耦业务逻辑层和UI层。我们要学习的是通过Binding来实现业务逻辑的值变更、直接更新到UI层。

我们讲解一下以上代码:

在XAML文件中我们创建了一个TextBlock 和一个Slider。2个控件。我们把TextBlock的Text属性(用于显示文本的属性)设置为{Binding Intelligence}。把Slider的Value属性(滑块的当前值)设置为{Binding Intelligence}。

如果想使用绑定。

1、XAML中就必须使用{Binding }这样的写法。后面跟的是属性,而这个属性是来自于当前类的DataContext中。this.DataContext对象是我们自己在cs代码中赋值的。XAML元素通过Binding绑定DataContext下的某个元素的值。来实现更改对应的属性。

而后台代码中必须设置需要绑定的对象到this.DataContext。这个对象(我们当前的Person)必须继承自INotifyPropertyChanged。并且使用PropertyChanged来触发通知。如果这个属性需要通知UI层,在属性的Set里就需要发送通知消息。写法就类似于

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));

如果执行绑定失败可以在对照一下代码。看看哪里有问题。这是简单的绑定。。

接下来我们尝试双向绑定和通过代码设置绑定。修改代码如下:

<Window x:Class="BindingExample.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"
xmlns:local="clr-namespace:BindingExample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Intelligence}"/>
<TextBox Text="{Binding Intelligence,Mode=TwoWay}"/>
<Slider Minimum="1" Maximum="100" Value="{Binding Intelligence}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="名称:"/>
<TextBlock Text="{Binding Name}" MinWidth="120"/>
<TextBlock Text="请输入需要修改的名称:"/>
<TextBox MinWidth="120" x:Name="tb_inputName"/>
</StackPanel>
<Button Content="通过代码修改绑定值得属性。修改Name为杜文龙" Click="AlertText_Click"/>
</StackPanel>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes; namespace BindingExample
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
Person duwenlong;
public MainWindow()
{ InitializeComponent();
duwenlong = new Person();
Binding binding = new Binding();
binding.Source = duwenlong;
    binding.Mode = BindingMode.TwoWay;
binding.Path = new PropertyPath("Name");
BindingOperations.SetBinding(tb_inputName, TextBox.TextProperty, binding);
this.DataContext = duwenlong;
} private void AlertText_Click(object sender, RoutedEventArgs e)
{
duwenlong.Name = "杜文龙";
}
} public class Person : INotifyPropertyChanged
{
private double _intelligence;
public double Intelligence
{
get { return _intelligence; }
set
{
_intelligence = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Intelligence")); }
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
} public event PropertyChangedEventHandler PropertyChanged;
}
}

在XAML中我们添加了一个TextBox 它的TextBox为 {Binding Intelligence,Mode=TwoWay} ,注意这个Mode=TwoWay。这是一个双向绑定的意思。通过它可以实现UI的内容更新了会返回到后台的绑定属性上。(因为没有绑定TextChanged事件,所以输入完成后需要丢失焦点才会有反应我按下了Tab键。这里需要使用Binding的一个属性UpdateSourceTrigger.他的类型是一个枚举。目前先不延申去讲)。我们看到了没有针对性的写后台的的代码通过一个属性,就完成了多处的使用和更新。而这个属性是业务层的。所以可以通过这个值来干很多的事情。

接下来我们继续看上面其他的代码:

 <TextBlock Text="{Binding Name,Mode=TwoWay}" MinWidth="120"/>
  <TextBox MinWidth="120" x:Name="tb_inputName"/>
<Button Content="通过代码修改绑定值得属性。修改Name为杜文龙" Click="AlertText_Click"/>
在这里TextBlock 的Text绑定了后台代码的Person实例下的Name属性,
Name为tb_inputName的TextBox通过后台代码也实现了绑定Name。还是双向绑定。在cs文件下和XAML文件下使用{Binding }效果是一样的。

我们把TextBlock和TextBox都绑定了Person的Name属性。我们又在Button下创建了Click事件。用来模拟修改Name属性(我们目前没有分层。也没有学习Command 所以假设cs文件是业务层)。

在Click事件中我们修改了person对象的Name属性为杜文龙。Name属性通过绑定关联了textblock和textbox。所以我们没有直接操作UI层。

当Name属性变化时。对应绑定的UI控件的值也发生了变化。因为双向绑定当TextBox的值变化时,Name也发生了变化。这样就可以在业务层处理了。。

我们尝试把双向绑定修改为单向绑定:

XAML下写法:

cs下写法:

在尝试修改TextBox并把焦点切换走。会发现其他绑定Name值得控件的值并没有变化。这章就讲这么多拉。主要是尝试培养数据驱动得思维。

Binding还支持多级路径、省略Path等写法。作为新手目前不推荐延申这些知识。因为主要先搞明白什么是数据驱动。如何使用数据驱动。在去考虑如何使用更高级的功能。

漏掉了一个在Binding中比较重要的知识点。RelativeSource.  使用RelativeSource对象指向源对象。用这个可以在当前元素的基础上查找其他对象用于绑定到源对象。

在实际使用Binding的过程中大部分时间Binding都放在了数据模板和控件模板中,(数据模板是控件模板用于定义控件的UI)。

在模板中编写Binding时有时候无法直接拿到我们需要绑定的数据对象,我们不能确定我们需要的Source对象叫什么,但是我们直到了我们需要使用的对象在UI布局上的相对关系。比如控件自己关联了某个数据,关键自己某个层级的容器数据。这个时候我们的RelativeSource就派上了用场。我们使用RelativeSource首先要3个关键参数。

AncestorType=我们需要查找的类型。比如Grid

AncestorLevel= 我们需要向上查找几级

Path=我们找到的元素需要绑定的属性。

这三个关键的参数配置完。我们就可以完成对RelativeSource的使用。

    <Grid x:Name="G0" Margin="12" Background="Red">
<TextBlock Text="In this Grid0 container"/>
<Grid x:Name="G1" Margin="12" Background="Blue">
<TextBlock Text="In this Grid1 container"/>
<Grid x:Name="G2" Margin="12" Background="Yellow">
<TextBlock Text="In this Grid2 container"/>
<Grid x:Name="G3" Margin="12" Background="Beige">
<StackPanel>
<TextBlock Text="In this Grid3 container"/>
<TextBlock Name="ces" Text="{Binding RelativeSource={RelativeSource AncestorType=Grid,AncestorLevel=1},Path=Name}"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
</Grid>

我们嵌套几个Grid,并在每个嵌套的Grid中都放入了一行文本用来显示自己所在的位置。设置了Margin使他有部分的重叠,可以更好的看到相互之间的层级关系。最内层使用一个TextBlock.在TextBlock的Text属性上使用RelativeSource。通过修改AncestorLevel 来设置向上查找Grid的等级。我们设置为1.向外层查找第一个找到的Grid对象。并绑定对应的Name。可以尝试修改一下并且看一下效果。

我创建了一个C#相关的交流群。用于分享学习资料和讨论问题。欢迎有兴趣的小伙伴:QQ群:542633085

WPF教程三:学习Data Binding把思想由事件驱动转变为数据驱动的更多相关文章

  1. WPF中的数据绑定Data Binding使用小结

    完整的数据绑定的语法说明可以在这里查看: http://www.nbdtech.com/Free/WpfBinding.pdf MSDN资料: Data Binding: Part 1 http:// ...

  2. WPF教程三:布局之WrapPanel面板

    WrapPanel:环绕面板 WrapPanel布局面板将各个控件从左至右按照行或列的顺序罗列,当长度或高度不够时就会自动调整进行换行,后续排序按照从上至下或从右至左的顺序进行. Orientatio ...

  3. .NET: WPF Data Binding

    WPF是分离UI和Logic的最佳工具,不同于Window Form的事件驱动原理,WPF采用的是数据驱动,让UI成为了Logic的附属,达到分离的效果. 本篇主要讲讲wpf的精华:data bind ...

  4. 完全掌握Android Data Binding

    转载:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0603/2992.html 来源 https://github.com/L ...

  5. Android开发教程 - 使用Data Binding(三)在Activity中的使用

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  6. [WPF]如何调试Data Binding

    前言 在WPF开发中,将ViewModel中对象绑定到UI上时,会出现明明已经将数据对象Binding到UI,但是UI上就是不显示等等的问题.这篇博客将介绍WPF Data Binding相关的内容, ...

  7. Android开发教程 - 使用Data Binding(五)数据绑定

    本系列目录 使用Data Binding(一)介绍 使用Data Binding(二)集成与配置 使用Data Binding(三)在Activity中的使用 使用Data Binding(四)在Fr ...

  8. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)

    题外话:本篇是对之前那篇的重排版.并拆分成两篇,免得没了看的兴趣. 前言 在Spring Framework官方文档中,这三者是放到一起讲的,但没有解释为什么放到一起.大概是默认了读者都是有相关经验的 ...

  9. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(二)

    接前一篇 Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 本篇主要内容:Spring Type Conver ...

随机推荐

  1. iapp后台一本通php源码+iapp源码

    给大家分享一个后台源码,内有后台php源码,还有iapp对接源码,一本通 iapp+ PHP源码 经过一个小时的研究看看,测试了一下, 1.注册登录以修复正常,签到正常 2.所有工具正常 3.接口,i ...

  2. ThreadLocal与ThreadLocalMap源码分析

    ThreadLocal类 该类主要用于不同线程存储自己的线程本地变量.本文先通过一个示例简单介绍该类的使用方法,然后从ThreadLocal类的初始化.存储结构.增删数据和hash值计算等几个方面,分 ...

  3. 限流&熔断的考量

    限流的原则,是尽量在流量源头限,并且是需要依据现有团队所掌握的技能来. 如上最左侧便是主要流量的来源入口,首先就要限制的地方就是slb节点的income流量 slb节点的流量特点是啥?加限流怎么加?限 ...

  4. Git 快速控制

    Git 快速控制 聊聊学习 Git 那些事 现在回想起来,其实接触 Git 的时候是在大一的时候表哥带入门的.当时因为需要做一个项目,所以他教如何使用 Git 将写好的代码推送到 GitHub 上,然 ...

  5. DMS是临时解决方案?

    DMS是临时解决方案? Who Says DMS Is an Interim Solution? 现在是认真对待DMS驱动程序监控系统的时候了. 特斯拉(Tesla)在台湾高速公路上撞上翻倒卡车的镜头 ...

  6. [源码解析] 深度学习分布式训练框架 horovod (2) --- 从使用者角度切入

    [源码解析] 深度学习分布式训练框架 horovod (2) --- 从使用者角度切入 目录 [源码解析] 深度学习分布式训练框架 horovod (2) --- 从使用者角度切入 0x00 摘要 0 ...

  7. Vue项目开发环境搭建

    初步学习vue.js相关知识,下面是我搭建环境中一些经验总结,希望可以帮到有需要的同学.首选先安装好以下的工具和环境. 一.软件安装 1.WebStorm官网下载地址:https://www.jetb ...

  8. PageHelper--Mybatis分页插件(ssm框架下的使用)

    1.导入PageHelper依赖 <!-- MyBatis 分页插件 --> <dependency> <groupId>com.github.pagehelper ...

  9. [源码解析] 深度学习分布式训练框架 horovod (3) --- Horovodrun背后做了什么

    [源码解析] 深度学习分布式训练框架 horovod (3) --- Horovodrun背后做了什么 目录 [源码解析] 深度学习分布式训练框架 horovod (3) --- Horovodrun ...

  10. DOS命令行(9)——wmic-系统管理命令行工具

    wmic 介绍与语法 WMI(Windows Management Instrumentation,Windows 管理规范)是一项核心的 Windows 管理技术:用户可以使用 WMI 管理本地和远 ...