zoukankan      html  css  js  c++  java
  • WPF9 Binding-1

    程序的本质是数据加算法。数据会在存储、逻辑和展示三个层流通,在数据角度上看,三层都很重要,但从算法角度上来看,算法在程序中的分布就不是很均匀,对于一个三层结构的程序来说,算法一般分布在:

    A、数据库内部

    B、读取和写会数据

    C、业务逻辑

    D、数据展示

    E、界面和逻辑的交互

    A、B两个部分的算法一般都非常稳定,不会轻易改动,复用性也很高;C处与客户需求关系最紧密,最复杂,变动也最大,大多数算法都集中在此;D、E两层负责UI与逻辑的交互,也占有一定量的算法。

    C是程序的核心,是占用开发精力最多的地方,但D、E却因为与逻辑层紧密相关,而容易把本应该写在逻辑层里的算法写在D、E部分(所以才有了MVC等模式)。WPF可以让展示出永远处于逻辑层的从属地位,这种能力的实现关键是它引入了Data Binding概念以及配套的Dependency Property系统和DataTemplate。

    Data Binding在WPF系统中起到的是数据告诉公路的作用,加工好的数据会自动送达用户界面加以显示,被用户修改过的数据也会自动传回逻辑层,一旦数据被加工好又会送达用户界面,这就是数据驱动UI,也是现在前端流行的各类MVVM框架的核心概念。

    Binding的两端分别是源Source和目标Target,数据源是一个对象,这个对象可能有和很多数据,这些数据通过属性暴露给外界,UI上的元素关心的是哪个属性值的变化,这个属性就成为Binding的路径,但Binding还需要有一个自动机制,即当值变化后属性要有能力通知Binding,让Binding把变化传递给UI元素,这个功能的实现是在属性的set语句中激发一个PropertyChanged事件。这个事件不需要手动声明,要做的是让作为数据源的类实现System.ComponentModel名称空间中的INotifyPropertyChanged接口。当为Binding设置了数据源后,Binding就会自动监听来自这个接口的PropertyChanged事件。

        public class Student:INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
            private string name;
            public string Name
            {
                get
                {
                    return name;
                }
                set
                {
                    name = value;
                    //触发事件
                    if(this.PropertyChanged!=null)
                    {
                        this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
                    }
                }
            }
        }

    为了在Student的值发生改变时有能力通知Binding,让Binding把变化传递给UI元素。方法是在属性的set语句中激发一个PropertyChanged事件。这个事件不需要自行声明,而是让作为数据源的类实现System.ComponentModel名称空间中的INotifyPropertyChanged接口。

    在窗体上准备一个TextBox和一个Button,TextBox将作为Binding目标,Button的Click事件发生时会改变Student对象的Name属性值。

        <StackPanel>
            <TextBox x:Name="textBoxName" BorderBrush="Black" Margin="5"/>
            <Button Content="AddAge" Margin="5" Click="Button_Click"/>
        </StackPanel>

    然后使用Binding把数据源和UI元素连接起来。

        public partial class MainWindow : Window
        {
            Student stu;
            public MainWindow()
            {
                InitializeComponent();
                //准备数据源
                stu = new Student();
                //准备Binding
                Binding binding = new Binding();
                binding.Source = stu;
                binding.Path = new PropertyPath("Name");
                //使用Binding连接数据源与Binding目标
                BindingOperations.SetBinding(this.textBoxName, TextBox.TextProperty, binding);
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                stu.Name += "Name";
            }
        }

    Student stu;声明在构造函数和按钮事件外是为了让Window的构造器和按钮事件都能访问由它引用的Student实例。

    Binding部分,先创建Binding的实例,然后使用binding.Source=stu;为Binding实例指定数据源,最后使用binding.Path=new PropertyPath("Name");来为Binding指定访问路径。

    把数据源和目标连接在一起的任务是使用"BindingOperations.SetBinding(...)"方法完成的,这个方法有三个重要参数:

    1、第一个参数用于指定Binding的目标,本例中是this.textBox.Name

    2、第二个参数用于为Binding指明把数据送达目标的哪个属性。但这个参数并没有使用对象的属性,而只是一个静态只读的DependencyProperty类型成员变量,这就是依赖属性。这类属性的值可以通过Binding依赖在其他对象的属性值上,被其他对象的属性值所驱动。

    3、第三个参数很明了,就是指定使用哪个Binding实例将数据源与目标关联起来。

    按钮事件用于对Name属性进行更新。

    一、Binding的源和路径

    Bingding的源也就是数据的源头。Binding对源的要求并不苛刻,只要它是一个对象,并且通过属性公开自己的数据,就能作为Binding的源。

    数据源不仅仅可以是类(比如实现INotifyPropertyChanged接口,并在属性set语句中激发PropertyChanged事件),还可以是控件自己、自己的容器、子元素,另一个控件、集合(作为ItemsControl)的数据源、XML(TreeView、Menu的数据源)、把多个控件关联到一个“数据制高点”上,甚至不给Binding指定数据源,让他自己去寻找。

    1、把控件作为Binding源和Binding标记扩展

    大多数情况下Binding的源是逻辑层的对象,但有时候为了让UI元素产生一些联动效果,也会使用Binding在控件间关联。

            <TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=slider}" BorderBrush="Black" Margin="5"/>
            <Slider x:Name="slider" Maximum="100" Minimum="0" Margin="5"/>

    WPF中,代码可以访问XAML代码中声明的变量,但XAML代码却无法访问C#代码中声明的变量、

    上一句话等价于this.TextBox1.SetBinding(TextBox.TextProperty,new Binding("Value")){ElementName="slider"}

    2、控制Binding的方向和数据更新

    Binding在源和目标之间架起了沟通的桥梁,默认情况下数据既能够通过Binding送达目标,也能够从目标返回源(手机用户对数据的修改)。有时候数据只需要展示给用户、不允许用户修改,这时候可以把Binding模式更改为源向目标的单项沟通。

    控制Binding数据流向的属性是Mode,它的类型是BindingMode枚举。BindingMode可取值为TwoWay、OneWay、OnTime、OneWayToSource和Default。这里的Default值是指Binding的模式会根据目标的实际情况来确定,比如若是可编辑的(TextBox.Text属性),Default就采用双向模式;若是只读的(TextBlock.Text)就采用单向模式。

    Binding的另一个属性——UpdateSourceTrigger,它的类型是UpdateSourceTrigger枚举,可取值为PropertyChanged、LostFocus、Explicit、Default、

    3、Binding的路径

    作为Binding源的对象可以由很多属性,通过这些属性Binding源可以把数据暴露给外界。Binding Path属性来指定Binding到底关注那个属性的值。例如把Slider控件对象作为源、把它的Value属性作为路径

    <TextBox x:Name="textBox1" Text="{Binding Path=Value,ElementName=slider}"/>

    等价于C#:

    Binding binding=new Binding(){Path=new PropertyPath("Value"),Source=this.slider};

    this.textBox1.SetBinding(TextBox.TextProperty,binding);

    也可以让一个TextBox显示另一个TextBox的文本长度

            <TextBox x:Name="textBox2" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textbox3" Text="{Binding Path=Text.Length,ElementName=textBox2,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
        

    索引器也能作为Path来使用,比如让一个TextBox显示另一个TextBox文本的第四个字符

            <TextBox x:Name="textbox4" BorderBrush="Black" Margin="5"/>
            <TextBox x:Name="textbox5" Text="{Binding Path=Text.[3],ElementName=textbox4,Mode=OneWay}" BorderBrush="Black" Margin="5"/>
        

    Text.[3]中的.可以去掉。

    当使用一个集合或者DataView作为Binding源时,如果我们想把它的默认元素当做Path使用,则需要这样的语法:

                List<string> stringList = new List<string>() { "Tim", "Tom", "Blog" };
                this.textbox4.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList, Mode = BindingMode.OneWay });      

    如果要是用默认元素,则直接/即可,如果要取第3个元素/[2],如果要取子集合元素,则可以一路/下去。

    4、没有path的Binding

    当Binding源本身就是数据且不需要Path类指定时,Path就是一个.或者干脆没有Path的Binding。典型的,string、int等基本类型就是这样,它们的实例本身就是数据,则无法通过它的某个属性来访问这个数据,此时只需要把Path的值设置为.即可。XAML中可以省略,而C#中不可以。

            <StackPanel.Resources>
                <sys:String x:Key="myString">
                    static string
                </sys:String>
            </StackPanel.Resources>
    <TextBlock x:Name="textBlock1" TextWrapping="Wrap" Text="{Binding Path=.,Source={StaticResource ResourceKey=myString}}" FontSize="16" Margin="5"/>

    等价于C#代码:

    Text="{Binding Source={StaticResource ResourceKey=myString}}"

    4、为Binding指定源的集中方法

    Binding的源时数据的来源,所以只要一个对象包含数据并能通过属性把数据暴露出来,它就能高档做Binding的源来使用,包含数据的对象比比皆是,但必须为Binding的Source指定合适的对象Binding才能正确工作。

    常见的绑定方法有:

    A、把普通CLR类型单个对象指定为Source:包括.Net Framework自带类型对象和用户自定义类型的对象。如果类型实现了INotifyPropertyChanged接口,则可通过在属性的set语句里激发PropertyChanged事件来通知Binding数据已被更新。

    B、把普通CLR集合类型对象指定为Source:包括数组、List<T>、ObservableCollection<T>等集合类型,实际工作中,我们经常需要把一个集合作为ItemsControl派生类的数据源来使用,一般是把控件的ItemsSource属性使用Binding关联到一个集合对象上。

    C、把ADO.NET数据对象指定为Source,包括DataTable和DataView等对象。

    D、使用XmlDataProvider把XML数据指定为Source:XML作为标准的数据存储和传输格式几乎无处不在,可以用它标识单个数据对象或集合;

    E、把依赖对象(Dependency Object)指定为Source:依赖对象不仅可以作为Binding的目标,同时也可以作为Binding的源。这样就有可能形成Binding链。依赖对象中的依赖属性可以作为Binding的Path。

    F、把容器的DataContext指定为Source:(WPF Data Binding默认行为),直到从哪个属性获取数据,但把哪个对象作为Binding源还不能确定。此时,就可以先建立一个Binding、只给它设置Path而不设置Source,让这个Binding自己去寻找Source。此时,Binding会自动把控件的DataContext当做自己的Source(会沿着控件树一层一层向外找,直到找到带有Path指定属性的对象为止)。

    G、通过ElementName指定Source:在C#代码里可以直接把对象作为Source赋值给Binding,但XAML无法访问对象,所以只能使用对象的Name属性来找到对象。

    H、通过Binding的RelativeSource属性相对地指定Source,当控件需要关注自己的、自己容器的或自己内部元素的某个值就需要使用这种方法。

    I、通过Binding的RelativeSource属性相对地指定Source,当控件需要关注自己的、自己容器的或者自己内部元素的某个值就需要使用这种方法。

    J、把ObjectDataProvider对象指定为Source:当数据源的数据不是通过属性而是通过方法暴露给外界的时候,可以使用这两种对象来包装数据源并再把他们指定为Source。

    5、没有Source的Binding-使用DataContext作为Binding的源

    Binding可以把单个CLR类型对象指定为Binding的Source,但如果一个Binding只知道自己的Path而不知道自己的Source时,就会沿着UI元素树一路向树根找过去,没过一个节点就会看这个节点的DataContext是否具有Path所制定的属性。如果有,这把这个对象作为自己的Source,否则继续寻找。

            <StackPanel.DataContext>
                <local:Student Id="6" Age="29" Name="Tim"/>
            </StackPanel.DataContext>
            <Grid>
                <StackPanel>
                    <TextBox Text="{Binding Path=Id}" Margin="5"/>
                    <TextBox Text="{Binding Path=Name}" Margin="5"/>
                    <TextBox Text="{Binding Path=Age}" Margin="5"/>
                </StackPanel>
            </Grid>

    此时属性结构就有一个DataContext:Student。

    使用xmlns:local="clr-namespace:WPF_XMAL_6_Binding"就可以使用在C#中定义的Student类。

                    <TextBox Text="{Binding Path=Id}" Margin="5"/>
                    <TextBox Text="{Binding Path=Name}" Margin="5"/>
                    <TextBox Text="{Binding Path=Age}" Margin="5"/>

    3个TextBox虽然没有指定Source,但Binding就会自动向UI树的长层去寻找可用的DataContext对象。Binding的Path可以设置为.,也可以直接省略不写。

    Binding这么智能是因为,当没有为空间的某个依赖属性显式赋值时,空间会把自己容器的属性值借过来当自己的属性值,例如如果为一个Button所在的Grid设置DataContext=“6”,然后Button的事件中设置MessageBox.Show(btn.DataContext.ToString());,虽然Button本身没有值,但它会显式Grid的DataContext。

    <StackPanel DataContext="LiaoRan">
    <Button x:Name="btn" Content="OK" Click="btn_Click"/>
    .... private void btn_Click(object sender, RoutedEventArgs e) { MessageBox.Show(btn.DataContext.ToString()); }

    属性值沿着UI树向下传递了。

    实际工作中DataContext用法非常灵活,比如:

    1、当UI上的多个控件都使用Binding关注同一个对象时,不妨使用DataContext

    2、当作为Source的对象不能被直接访问的时候,例如A窗体内的控件是private访问级别,B要去访问A窗体内的控件作为自己的Binding源,就可以把A窗体内的这个控件或控件的值作为窗体A的DataContext(这个属性是public级别)从而暴露数据。

    6、使用集合对象作为列表控件的ItemSource

    WPF中的列表式控件都派生自ItemsControl类,也就自然继承了ItemsControl这个属性。ItemsSource属性可以接受一个IEnumerable接口派生类的实例作为自己的值,每个ItemsControl派生类都具有自己对应的条目容器,比如ListBox的条目容器是ListBoxItem,ItemSource里存放的是一条一条的数据,要想把数据显示出来需要为其穿上外衣,就是Binding。只要为每一个ItemsControl对象设置ItemsSource属性值,ItemsControl对象就会自动迭代其中的数据元素,为每个元素准备一个条目容器,并建立起关联。

            <StackPanel x:Name="stackPanel" Background="LightBlue">
                <TextBlock Text="StudentID:" FontWeight="Bold" Margin="5"/>
                <TextBox x:Name="textBoxId" Margin="5"/>
                <TextBlock Text="StudentList:" FontWeight="Bold" Margin="5"/>
                <ListBox x:Name="listBoxStudents" Height="110" Margin="5"/>
            </StackPanel>  
            public MainWindow()
            {
                InitializeComponent();
                //准备数据
                List<Student> stulist = new List<Student>()
                {
                    new Student(){Id=0,Name="Tim",Age=21},
                    new Student(){Id=1,Name="Tom",Age=22},
                    new Student(){Id=2,Name="Cloud",Age=23},
                    new Student(){Id=3,Name="Strife",Age=24},
                    new Student(){Id=4,Name="Tifa",Age=25},
                    new Student(){Id=5,Name="Victor",Age=26},
                };
                //为ListBox设置Binding
                this.listBoxStudents.ItemsSource = stulist;
                this.listBoxStudents.DisplayMemberPath = "Name";
                //为TextBox设置Binding
                Binding binding = new Binding("SelectedItem.Id") { Source = this.listBoxStudents };
                this.textBoxId.SetBinding(TextBox.TextProperty, binding);
            }

    7、使用Linq作为Binding的源

            <StackPanel Background="LightBlue">
                <ListView x:Name="listViewStudent" Height="143" Margin="5">
                    <ListView.View>
                        <GridView>
                            <GridViewColumn Header="Id" Width="60" DisplayMemberBinding="{Binding Id}"/>
                            <GridViewColumn Header="Id" Width="100" DisplayMemberBinding="{Binding Name}"/>
                            <GridViewColumn Header="Id" Width="80" DisplayMemberBinding="{Binding Age}"/>
                        </GridView>
                    </ListView.View>
                </ListView>
                <Button Content="Local" Height="25" Margin="5,0" Click="Button_Click_1"/>
            </StackPanel>
            private void Button_Click_1(object sender, RoutedEventArgs e)
            {
                //获取DataTable实例
                List<Student> list = new Student().GetStudentList();
                //绑定
                this.listViewStudent.ItemsSource = list.Where(x => x.Name.StartsWith("T"));
            }

    绑定到listViewStudents.ItemsSource上即可。

     

  • 相关阅读:
    PHP 包含文件路径问题
    PHP显示今天、今月、上月、今年的起点/终点时间戳
    PHP Web基础教程
    php如何生成随机密码的几种方法
    You don't have permission to access /~whpc on this server.
    linux 网络 PING IP可以通,ping域名ping不通
    CPU : 二级缓存容量
    简单建立个人WEB网站
    MKL程序编译与连接:Lapack篇
    ldd命令原理与使用
  • 原文地址:https://www.cnblogs.com/NicolasLiaoran/p/13042975.html
Copyright © 2011-2022 走看看