zoukankan      html  css  js  c++  java
  • WPF教程三:学习Data Binding把思想由事件驱动转变为数据驱动

      之前大家写代码都喜欢用事件驱动,比如说鼠标输入的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

  • 相关阅读:
    设计模式---003代理模式(转载自我的老师 Alley-巷子)
    设计模式---002适配模式(转载自我的老师 Alley-巷子)
    设计模式---001单例模式(转载自我的老师)
    【JavaScript算法】---希尔排序(转载自我的老师 Alley-巷子)
    【JavaScript算法】---快速排序法(转载自我的老师 Alley-巷子)
    将博客搬至CSDN
    程序员10元盒饭完整版
    querylist采集数据 模拟滑动验证码 jcapche
    php压缩图片工具类(亲测可用)
    elastic-php should匹配设置至少匹配一个条件
  • 原文地址:https://www.cnblogs.com/duwenlong/p/14503097.html
Copyright © 2011-2022 走看看