WPF 数据绑定上

我们已经了解到使用WPF可以设计强大的用户界面,那么用户界面和后台逻辑之间的数据传递是如何实现的呢,这里就使用到WPF中的数据绑定功能。这也是WPF比较核心的部分。数据绑定功能的优势包括,包括本质上支持数据绑定的各种属性、灵活的数据 UI 表示形式,以及业务逻辑与 UI 的完全分离。


数据绑定:
数据绑定是应用程序UI与业务逻辑之间建立连接的一个过程。
使用数据绑定始终要有目标和源。 绑定的源可以是任何公共的属性。绑定的目标可以是从DependencyProperty派生而来的任何可访问属性或者元素。 如果绑定具有正确设置并且数据提供正确通知,则当数据更改其值时,绑定到数据的 元素会自动反映更改。 数据绑定可能还意味着如果元素中数据的外部表现形式发生更改,则基础数据可以自动更新以反映更改。
数据绑定需要注意的情况:
  • 每个绑定有四个组件:绑定目标对象、目标属性、绑定源,以及要使用的绑定源中的值的路径。如何区分绑定目标和绑定源头? 如果你将A绑定到B,则A是绑定目标,B是绑定源。始终是绑定目标绑定到绑定源。
  • 绑定目标属性必须是依赖属性。绑定源是多样化的,可以是UIElement、任何列表对象、与 ADO.NET 数据或 Web 服务关联的 CLR 对象,也可以是包含 XML 数据的 XmlNode。也可以是一个集合。

数据绑定实例:
循序渐进的写几个例子来学习下数据绑定。

1. 普通的数据绑定
声明数据源:
public class Person 
  {
    private int age;
    public int Age
    {
      get
      {
        return age;
      }
      set
      {
        age = value; 
      }
    }
  }

开放属性Age供外界使用。
后台xaml代码:
<Window x:Class="DataBinding1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBox x:Name="textBox1" Margin="5" />
        <Button  Content="Pass A Year" x:Name="button1" Click="button1_Click" Margin="5"  Height="25"/>
    </StackPanel>
</Window>

定义了一个TextBox ,绑定到数据源,显示其Age。另外定义了一个Button,点击Button,则Age属性加1.

xaml.cs代码:
namespace DataBinding1
{
    /// <summary>
    /// Window1.xaml 的交互逻辑
    /// </summary>
    public partial class Window1 : Window
    {
        public Person per;
        public Window1()
        {
            InitializeComponent();
	    //C#代码中是这样绑定的
            //数据源
            per = new Person();

            //准备Binding 
            Binding myBinding = new Binding();
            myBinding.Source = per;
            myBinding.Path = new PropertyPath("Age");

            //使用bingding对象 绑定 绑定源 和绑定目标 ,将Textbox绑定到per上
            BindingOperations.SetBinding(this.textBox1,TextBox.TextProperty,myBinding);
            //this.textBox1.SetBinding(TextBox.TextProperty,myBinding);   //也可以这样建立绑定
        }

        private void button1_Click(object sender,RoutedEventArgs e)
        {
            per.Age++;
        }
    }
    public class Person
    {
        public int Age
        {
            get;
            set;
        }
    }
}


运行结果:


可以看到,TextBox成功显示出来Age属性(0为默认值)。当我们点击Button的时候,Age属性会加1,但是TextBox中还是为0,这是什么原因呢。

原因:数据源对象通过自身属性暴漏给外界,但是光有属性是不行的。Binding是一种自动的机制,当你属性的值变更的时候,要有能力通知Binding,然后传递给绑定目标。如何让一个属性拥有这种能力呢。方法如下:在属性的set语句中激发一个PropertyChanged事件。在数据源的类中实现INotifyPropertyChanged 接口。然后Binding会自动监听来自这个接口的PropertyChanged事件。

修改Person类的代码:

public class Person :INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private int age;
        public int Age
        {
            get
            {
                return age;
            }
            set
            {
                age = value;
                if (this.PropertyChanged != null)//激发事件,参数为Age属性
                {
                    this.PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Age"));
                }
            }

        }
    }

OK,这样在去点击Button,则可以成功的发现点击一下,就会显示加1


知道了怎么在逻辑处建立数据绑定了,那么如何在布局中直接绑定呢。我们将一个TextBlock 绑定到一个ListBox上。让其显示选中的ListBox项的内容,并将背景色设置。代码:

 <StackPanel>
        <ListBox x:Name="listboxColor">
            <ListBoxItem>Yellow</ListBoxItem>
            <ListBoxItem>Blue</ListBoxItem>
            <ListBoxItem>Red</ListBoxItem>
            <ListBoxItem>Black</ListBoxItem>
        </ListBox>
        <TextBlock x:Name="textBlockShowColor" Height="30" Margin="0"  
                
                   Text="{Binding ElementName = listboxColor,Path = SelectedItem.Content}"                    Background="{Binding ElementName = listboxColor,Path = SelectedItem.Content}"                   />    </StackPanel>
逻辑代码不用修改任何部分,便可以实现数据绑定。看下效果。选中某项,则TextBlock显示内容,并修改自身背景色。这里需要注意的是,在这部分添加绑定的时候,属性的大小写一定要注意。写错的话会造成没有效果。。而且不报错。不易发现。



这相当于在后台代码做如下修改,将布局代码中

  Text="{Binding ElementName = listboxColor,Path = SelectedItem.Content}"
                   Background="{Binding ElementName = listboxColor,Path = SelectedItem.Content}"

删除掉,然后在逻辑代码中新添加如下内容。

            Binding myBinding = new Binding();//创建新的绑定对象
            myBinding.Source = this.listboxColor;//绑定源
            myBinding.Path = new PropertyPath("SelectedItem.Content");//绑定源路径
            //将绑定源与绑定目标绑定起来 这是是与绑定目标textBlockShowColor的TextProperty绑定
            BindingOperations.SetBinding(this.textBlockShowColor,TextBlock.TextProperty,myBinding);

            Binding myBindingbcGround = new Binding();
            myBindingbcGround.Source = this.listboxColor;
            myBindingbcGround.Path = new PropertyPath("SelectedItem.Content");
            BindingOperations.SetBinding(this.textBlockShowColor,TextBlock.BackgroundProperty,myBindingbcGround);

绑定的那一部分代码也可以写成下面这种简写的格式:

Binding myBinding = new Binding(){Path= new PropertyPath("Age"),Source = per);
Binding myBinding = new Binding("Age"){Source = per};

可以测试一下,效果一样的。


2. 数据流的方向+Example

绑定的数据流可以从数据目标流向数据源或者从绑定源流向绑定目标,或者两者互相可以传递。Binding对象的属性中有个Mode属性,可以让我们来选择某种模式。OneWay ,TwoWay ,OneWayToSource如下图:


从他们名字上我们可以区分集中模式的区别:

  • OneWay绑定导致对源属性的更改会自动更新目标属性,但是对目标属性的更改不会传播回源属性。如果无需监视目标属性的更改,则使用OneWay绑定模式可避免TwoWay绑定模式的系统开销。
  • TwoWay绑定导致对源属性的更改会自动更新目标属性,而对目标属性的更改也会自动更新源属性。大多数属性都默认为OneWay绑定,但是一些依赖项属性(通常为用户可编辑的控件的属性,如TextBoxText属性和CheckBoxIsChecked属性)默认为TwoWay绑定。
  • OneWayToSourceOneWay绑定相反;它在目标属性更改时更新源属性。
若要检测源更改(适用于OneWayTwoWay绑定),则源必须实现一种合适的属性更改通知机制(如INotifyPropertyChanged)。(看上例子)

下面写一个例子:

代码:

   <TextBox Text="{Binding ElementName = slider1,Path = Value,Mode=TwoWay}" Height="25"/>
   <Slider x:Name="slider1" Maximum="100" Minimum="0" Height="25"/>

将TextBox与Slider的Value 绑定起来,设置模式为TwoWay模式,这样滑动Slider,则可以修改TextBox的值,修改TextBox的值,也可以滑动Slider,看能否实现。


OK,我们发现当滑动Slider的时候可以改变更新,但是当改变Text的值,Slider没有滑动,我们不是已经设置为TwoWay模式了吗?这是什么原因呢。

这个时候我们在添加一个TextBox,不绑定任何东西。显示空的。

 <TextBox Text="{Binding ElementName = slider1,Path = Value}" Height="25"/>
        <Slider x:Name="slider1" Maximum="100" Minimum="0" Height="25"/>
        <TextBox Height="25"/>

这个时候修改完TextBox的值,将鼠标移动到下个TextBox框内,Value 就会修改。或者在第一个框内输入值后,按一下Tab键

是什么原因导致这个情况呢,下面引出触发源更新的原因:

触发源更新的原因:

TwoWayOneWayToSource绑定侦听目标属性的更改,并将这些更改传播回源。这称为更新源。例如,可以编辑文本框中的文本以更改基础源值。如上例子所示,我们想做到修改文本框的值(目标属性的修改),来改变Slider的Value的值。但是没有做到。

这里有这么一个情况,源值是在您编辑文本的同时进行更新,还是在您结束编辑文本并将鼠标指针从文本框移走后才进行更新呢?绑定的UpdateSourceTrigger属性确定触发源更新的原因。其类型是一个枚举类型:

  //  Describes the timing of binding source updates.
    public enum UpdateSourceTrigger
    {
        // Summary:
        //     The default System.Windows.Data.UpdateSourceTrigger value of the binding
        //     target property. The default value for most dependency properties is System.Windows.Data.UpdateSourceTrigger.PropertyChanged,//     while the System.Windows.Controls.TextBox.Text property has a default value
        //     of System.Windows.Data.UpdateSourceTrigger.LostFocus.
        Default = 0,//     Updates the binding source immediately whenever the binding target property Changes.
        PropertyChanged = 1,//     Updates the binding source whenever the binding target element loses focus.
        LostFocus = 2,//     Updates the binding source only when you call the System.Windows.Data.BindingExpression.UpdateSource() method
        Explicit = 3,}
从上可以看出,如果UpdateSourceTrigger值为PropertyChanged,则TwoWayOneWayToSource绑定的右箭头指向的值会在目标属性更改时立刻进行更新。但是,如果LostFocus,则仅当目标属性失去焦点时,该值才会使用新值进行更新。

Mode属性类似,不同的依赖项属性具有不同的默认UpdateSourceTrigger值。大多数依赖项属性的默认值都为PropertyChanged,而Text属性的默认值为LostFocus这意味着,只要目标属性更改,源更新通常都会发生,这对于CheckBox和其他简单控件很有用。但对于文本字段,每次键击之后都进行更新会降低性能,用户也没有机会在提交新值之前使用退格键修改键入错误。这就是为什么Text属性的默认值是LostFocus而不是PropertyChanged的原因。

这样也解释清楚了我们上例中为什么修改了TextBox的文本内容的值的时候,没有修改Value,因为始终没有LostFocus。我们可以做以下修改,

<TextBox Text="{Binding ElementName = slider1,Path = Value,UpdateSourceTrigger= PropertyChanged}" Height="25"/>
        <Slider x:Name="slider1" Maximum="100" Minimum="0" Height="25"/>

OK,现在重新执行即可发现,键入新的内容Slider就开始滑动了。


TwoWayOneWayToSource绑定侦听目标属性的更改,并将这些更改传播回源。这称为更新源。UpdateSourceTrigger属性用于处理源更新,因此仅适用于TwoWayOneWayToSource绑定。若要使OneWayToSource绑定生效,源对象需要提供属性更改通知。(第一个例子中的PropertyChanged事件。)


Binding的多级路径:

当我们绑定源的路径Path 的属性还可以继续细分的时候,(譬如说我们将一个TextBox的Text绑定到另外一个TextBox的Text的时候,我们想显示这个显示内容的第3个字符),Binding也可以支持这种多级路径的绑定:

<TextBox x:Name="textbox1" Height="25"/>
<TextBox Height="25" Text="{Binding ElementName =textbox1,Path=Text.[3]}"/>
这个时候会提示出现错误,原因是:A TwoWay or OneWayToSource binding cannot work on the read-only property 'Chars' of type 'System.String'.

因为我们绑定源Path是Text.[3] ,TextBox默认是TwoWay模式,当绑定目标更新的时候就会更新绑定源,无法使用String去更新一个只读Chars类型。OK。修改下绑定Mode,改成OneWay模式,修改如下:

<TextBox Height="25" Text="{Binding ElementName =textbox1,Path=Text.[3],Mode = OneWay}"/>

看运行结果:



当将一个集合或者DataView 作为Binding的源的时候,如果将默认元素作为Path的时候,可以如下使用:


            List<string> example = new List<string>() { "haha","hahahehe","hahahehexixi" };
            //使用默认元素当作Path使用
            this.textbox1.SetBinding(TextBox.TextProperty,new Binding("/") { Source = example });
            //使用属性的下一级属性,需要修改模式
            this.textbox2.SetBinding(TextBox.TextProperty,new Binding("/Length") { Source = example,Mode = BindingMode.OneWay});
            this.textbox3.SetBinding(TextBox.TextProperty,new Binding("/[3]") { Source = example,Mode = BindingMode.OneWay});
运行结果:




某种情况下我们会遇到Binding源本身就是数据,不需要也无法通过它的哪个属性来访问这个数据,譬如string,int等基本类型。这个时候我们只需要将Path的值设置为"."就可以了,(在XAML中可以省略不写,C#代码中不能省略)

   <StackPanel>
        <StackPanel.Resources>
            <s:String x:Key="Example" >
                哈哈 呵呵 嘻嘻
            </s:String>
        </StackPanel.Resources>
        <TextBox x:Name="textbox1" Height="25" Text="{Binding Path=.,Source = {StaticResource ResourceKey = Example}}"/>
  </StackPanel>

需要在前面加上:
xmlns:s="clr-namespace:System;assembly=mscorlib"
运行结果:




绑定数据源的几种方法和案例:

上文我们已经了解到,绑定源的类型可以是多样的。可以是CLR对象,动态对象,ADO.NET对象,XML对象 和DependencyObject对象,和某种集合类型。我们将依次来写一些实例进行了解。

  • 普通CLR类型的单个对象。包括.NET Framework自带类型的对象和用户自定义类型的对象。类型需实现INotifyPropertyChanged接口,在属性的set语句中激发PropertyChanged事件来通知Binding数据已被更新。
  • 普通CLR集合类型对象。例如数组,List<T>、ObservableCollection<T>等。这个也是我们一会重点讨论的对象。
  • ADO.NET数据对象。包括DataTable和DataView等对象。
  • 使用XmlDataProvider把XML数据指定为Source。
  • 依赖对象。依赖对象不仅可以用作Binding的目标,同时也可以作为Binding的源。
  • 容器的DataContext。这种情况适用于 我们已经明确知道确定从哪个属性获得数据,但是不能具体到哪个对象作为Binding的源。这个时候我们会先建立一个Binding,只给它设置Path,不设置Source。让Binding自己去寻找Source。Binding会自动把控件的DataContext作为自己的Source,(沿着控件树一层一层的向外找,直到找到带有Path属性的对象为止)
  • 适用LINQ检索得到的数据对象作为Binding的源。


1.使用DataContext作为Binding的源。+Example

DataContext作为FrameworkElement类的属性。意味着WPF控件都会具备这个属性。-->在UI树中每个节点都有DataContext。当我们已经明确知道确定从哪个属性获得数据,但是不能具体到哪个对象作为Binding的源。这个时候我们会先建立一个Binding,只给它设置Path,不设置Source。让Binding自己去寻找Source。Binding会自动把控件的DataContext作为自己的Source,(沿着控件树一层一层的向外找,直到找到带有Path属性的对象为止)如果到了树的根部还没有找到,则该Binding没有Source。因而不会得到数据。看例子:


修改我们上面所创建的Person类:

 public class Person : 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"));
                    }
                }
            }

            private int age;
            public int Age
            {
                get
                {
                    return age;
                }
                set
                {
                    age = value;
                    if (this.PropertyChanged != null)//激发事件,参数为Age属性
                    {
                        this.PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Age"));
                    }
                }

            }
        }
给最外层的Window DataContext赋一个Person,

        public Person superStar;
        public Window1()
        {
            superStar = new Person()
            {
                Name = "LeBron James",Age = 29
            };
       
            InitializeComponent();
            this.DataContext = superStar;//给外层Window的DataContext进行了赋值

        }

XAML代码是:

 <Grid>
        <StackPanel>
            <TextBox Height="25" Margin="5" x:Name="textbox1" Text="{Binding Path = Age}"/>
            <TextBox Height="25" Margin="5" x:Name="textbox2" Text="{Binding Path = Name}"/>
        </StackPanel>
    </Grid>

则,现在的UI树情况是如下图所示:


这样这2个TextBox的Binding就会自动的向UI元素树的上层寻找DataContext对象,寻找是否满足两个Path。


我们也可以在xaml代码中给控件的DataContext赋值,用下列语句。

 xmlns:local="clr-namespace:DataBindingExample"
        <Grid.DataContext>
            <local:Person Name="LeBron James" Age ="29"/>
        </Grid.DataContext>
效果是和在C#中使用是一样的。
 this.grid1.DataContext = superStar;//给外层Window的DataContext进行了赋值

执行结果:

我们知道当Binding的Source本身就是对象,不需要使用属性来暴露数据的时候,Path可以设置为".",或者可以不写(在C#代码中必须写"."),现在我们了解到了Source也可以不写。SO,合起来,当某个控件的DataContext是一个简单类型对象的时候,会出现一个"没有path也没有Source"的Binding。

如下:

 <Grid x:Name="grid1">
        <Grid.DataContext>
            <s:String>
                LeBron James is My favorite basketball player! 
            </s:String>
        </Grid.DataContext>
        <StackPanel x:Name="stackPanel1">
            <TextBox Background="Red" FontWeight="Bold" Height="25" Margin="5" x:Name="textbox2" Text="{Binding Path = .,Mode = OneWay}"/>// 这里需要设置成OneWay,因为默认是TwoWay,目标属性无法修改。
            <TextBox Background="Yellow" FontWeight="Bold" Height="25" Margin="5" x:Name="textbox1" Text="{Binding Mode = OneWay}"/> // 这里需要设置成OneWay,因为默认是TwoWay,目标属性无法修改。
        </StackPanel>
    </Grid>

运行结果:



关于DataContext的详细解释见下一篇博客,点击这里


2.使用集合对象作为列表控件的ItemSource+Example

目前为止,我们只讨论了绑定到单个对象,但是绑定到数据集合是一个常见方案。常使用ItemControl类来显示数据集合。

WPF中列表控件派生于ItemControl类,继承了ItemSource这个属性。这个属性可以接收一个IEnumerable接口派生类的作为自己的值。(所有可能被遍历的集合都实现了这个接口,包括数组,List<T> ),而每个派生于ItemControl的类都有自己的条目容器(Item Container)

如何将条目容器与对应的集合类条目对应起来呢,使用Binding。只要我们为一个ItemConrol对象设置了ItemSource值,则会自动迭代其中的数据元素,为每个数据元素准备了一个条目容器。关系图:


注意,绑定是OneWay,因为ItemsSource属性默认情况下支持OneWay绑定。

集合类可以采用已经枚举实现IEnumerable接口的任何集合。但是,若要设置动态绑定,以便集合中的插入或删除操作可以自动更新 UI,则该集合必须实现INotifyCollectionChanged接口。此接口公开一个事件,只要基础集合发生更改,都应该引发该事件。在实现自己的集合之前,请先考虑使用ObservableCollection<T>或一个现有的集合类List<T>等。

创建一个List,类型为BasketBallPlayer,代码如下:

public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
      List<BasketBallPlayer> myList = new List<BasketBallPlayer>()
      {
        new BasketBallPlayer(){Name = "LeBron James",Age = 28,TeamName = "Miami Heat"},new BasketBallPlayer(){Name = "Dwyane Wade",Age = 32,new BasketBallPlayer(){Name = "Chris Bosh",new BasketBallPlayer(){Name = "Kevin Durant",Age = 25,TeamName = "OKC Thunder"},new BasketBallPlayer(){Name = "Russell Westbrook",new BasketBallPlayer(){Name = "YaoMing",Age = 33,TeamName = "Huston Rocket"}
      };
      this.listBoxBasketBallPlayer.ItemsSource = myList;
      this.listBoxBasketBallPlayer.DisplayMemberPath = "Name";
    }
   
  }
  public class BasketBallPlayer : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;
    private int age;
    public int Age
    {
      get
      {
        return age;
      }
      set
      {
        age = value;
        if (this.PropertyChanged != null)
        {
         
          this.PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Age"));
        }
      }
    }
    private string name;
    public string Name
    {
      get
      {
        return name;
      }
      set
      {
        name = value;
        if (this.PropertyChanged != null)
        {
          this.PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Name"));
        }
      }
    }
    private string teamName;
    public string TeamName
    {
      get
      {
        return teamName;
      }
      set
      {
        teamName = value;
        if (this.PropertyChanged != null)
        {
          this.PropertyChanged.Invoke(this,new PropertyChangedEventArgs("TeamName"));
        }
      }
    }
  }
将ItemsControl的ItemsSource属性设置为List<BasketBallPlayer>的一个实例,那么这一句是什么意思呢

 this.listBoxBasketBallPlayer.DisplayMemberPath = "Name";
这句话实际上是 当DisplayMemeberPath属性被复制后,ListBox在获得Item的时候会创建等量的ListBoxItem并且以DisplayMemberPath的属性值为Path创建Binding,源当然是List<T>实例,目标是ListBoxItem的内容。实际上是一个TextBox。

xaml代码:

 <StackPanel x:Name="stackPanel1" Background="LightBlue">
        <StackPanel Orientation="Horizontal">
            <TextBox  Text="Name" Height="25" Width="150"/>
            <TextBox Text="Age" Height="25" Width="150"/>
            <TextBox Text="TeamName" Height="25" Width="150"/>
        </StackPanel>
        <ListBox x:Name="listBoxBasketBallPlayer" Height="200" Margin="5"/>        
    </StackPanel>

看下显示效果:


每一行显示一个DisplayMemberPath设置的属性。那么我们如何做到一行显示出来一个BasketBallPlayer的所有属性呢。这里需要了解下ItemTemplate属性。(即自己设置每一行显示的内容以及样式)


修改下xaml代码,让一行展示3个属性。对ListBox进行修改,

 <ListBox x:Name="listBoxBasketBallPlayer" Height="200" Margin="5">
            <ListBox.ItemTemplate>//每一行的样式和数据来源都在下面DataTemplate的内容中设置
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path = Name}"  Width="150"/>//自己绑定要展示的属性Path
                        <TextBlock Text="{Binding Path = Age}"  Width="150"/>
                        <TextBlock Text="{Binding Path = TeamName}"  Width="150"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
</ListBox>

在将上面的DisplayMemberPath删除掉(因为我们已经在xaml代码中设置了Path的值,所以不能再C#中设置这个了)。

显示结果:


有关DataTemplate,后面会继续写篇博文。地址是:。。。。暂时没有。那当我们在程序运行期间List<T>实例内容修改的时候,会自动在UI界面上修改吗,我们做一个测试,添加一个Button,点击Button,添加一个值进来,看UI上有没有反应。

xaml代码添加button

<Button Height="25" x:Name="button1" Content="Click" Click="button1_Click"/>
点击事件:

  private void button1_Click(object sender,RoutedEventArgs e)
        {
            myList.Add(new BasketBallPlayer() { Name = "LeBron James",TeamName = "Miami Heat" });
        }
myList为上文中定义的List<BasketBallPlayer>实例。

运行点击button,无反应,UI没有变化。那是什么原因导致源更改而没有反应到目标上呢。

这是因为List<T>默认没有实现INotifyCollectionChanged和INotifyPropertyChanged接口,当属性发生变化的时候,无法通过Binding传递出去。So没有反应。

可以考虑用ObservableCollection<T>来代替它。它是由系统实现了这个功能的。测试一下,修改代码就可以了

ObservableCollection<BasketBallPlayer> myList;
myList = new ObservableCollection<BasketBallPlayer>()

再次点击运行,可以发现随着点击,UI界面的内容也在发生变化。(因为点击的时候,我们修改了后台myList的内容)。





3.使用ADO.Net对象作为Binding的源+Example

.NET开发中,我们使用ADO.NET类对数据库进行操作。例如将数据读取到DataTable,然后在显在UI列表控件中。即在DataTable与UI列表控件之间直接简历Binding。我们首先来了解下DataTable。

DataTable:表示内存中数据的一个表。该表可以在代码中独立的创建,也可以用于其他对象中,最常见的就是DataSet和DataView.

在代码中以编程的形式创建DataTable,首先必须通过DataColumn对象添加到DataColumnCollection中来定义其框架结构。(这个我们也可以想象的到)。

若要向DataTable中添加行,必须先使用NewRow方法返回新的DataRow对象。NewRow方法返回具有DataTable的架构的行,就像由该表的DataColumnCollection定义的那样。DataTable可存储的最大行数是 16,777,216。

创建DataTable的实例代码:我们将上文中定义的BasketBallPlayer对象创建到一个DataTable中.


       //创建一个名字叫做BasketBallPlayer的DataTable
            DataTable myTable = new DataTable("BasketBallPlayer");
            //创建Table的框架结构
            DataColumn myColumn = myTable.Columns.Add("Name",typeof(string));
            //可以设计myColumn的属性
            myTable.Columns.Add("Age",typeof(int));
            myTable.Columns.Add("TeamName",typeof(string));

            //添加行
            DataRow workRow;
            workRow = myTable.NewRow();
            workRow[0] = "LeBron James";
            workRow[1] = 29;
            workRow["TeamName"] = "Miami Heat";
            myTable.Rows.Add(workRow);

,我们这个时候已经创建了一个DataTable的实例,其中有一行数据

Name Age TeamName
LeBron James 29 Miami Heat
我们可以以同样的方式进行添加一堆内容进去。下面将DataTable实例与UI 的列表控件Binding起来。


xaml代码:

 <ListBox x:Name="listBoxBasketBallPlayer2" Height="200" Margin="5">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Path = Name}"  Width="150"/>
                        <TextBlock Text="{Binding Path = Age}"  Width="150"/>
                        <TextBlock Text="{Binding Path = TeamName}"  Width="150"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

C#代码:

  //创建一个名字叫做BasketBallPlayer的DataTable
            DataTable myTable = new DataTable("BasketBallPlayer");
            //创建Table的框架结构
            DataColumn myColumn = myTable.Columns.Add("Name",typeof(string));

            //添加行
            DataRow workRow;
            workRow = myTable.NewRow();
            workRow[0] = "LeBron James";
            workRow[1] = 29;
            workRow["TeamName"] = "Miami Heat";
            myTable.Rows.Add(workRow);

            //添加行
            
            workRow = myTable.NewRow();
            workRow[0] = "LeBron James";
            workRow[1] = 29;
            workRow["TeamName"] = "Miami Heat";
            myTable.Rows.Add(workRow);

            //添加行
            
            workRow = myTable.NewRow();
            workRow[0] = "LeBron James";
            workRow[1] = 29;
            workRow["TeamName"] = "Miami Heat";
            myTable.Rows.Add(workRow);

            //添加行
            
            workRow = myTable.NewRow();
            workRow[0] = "LeBron James";
            workRow[1] = 29;
            workRow["TeamName"] = "Miami Heat";
            myTable.Rows.Add(workRow);

            //添加行
            
            workRow = myTable.NewRow();
            workRow[0] = "LeBron James";
            workRow[1] = 29;
            workRow["TeamName"] = "Miami Heat";
            myTable.Rows.Add(workRow);

            //添加行
            
            workRow = myTable.NewRow();
            workRow[0] = "LeBron James";
            workRow[1] = 29;
            workRow["TeamName"] = "Miami Heat";
            myTable.Rows.Add(workRow);

            this.listBoxBasketBallPlayer2.ItemsSource = myTable.DefaultView;
不能直接把DataTable实例赋给ItemsSource,可以把它的属性DefaultView(这是一个DataView类型对象,实现了IEnumerable接口)赋值给ItemsSource属性。

运行结果:







4.使用XML数据作为Binding的源。+Example
现在程序设计只要涉及数据传输就离不开XML,因为大多数数据传输都基于SOAP相关的协议。XML文本是树形结构的。所以XML可以方便的用于表示线性集合(如Array,List等)和树形结构数据。

需要注意的是,当使用XML数据作为Binding的Source的时候,我们将使用XPath属性而不是Path属性来指定数据来源。

例子:
1.XML单独作为一个文件存在

首先创建一个XML文件,放到桌面上。内容是上例中的一个SuperStar列表。
XML代码:
<?xml version="1.0" encoding="utf-8" ?>
<SuperStarList>
  <SuperStar Name ="LeBron James">
    <Age>28</Age>
    <TeamName>Miami Heat</TeamName>
  </SuperStar>
  <SuperStar Name ="Dwyane Wade">
    <Age>32</Age>
    <TeamName>Miami Heat</TeamName>
  </SuperStar>
  <SuperStar Name ="Chris Bosh">
    <Age>28</Age>
    <TeamName>Miami Heat</TeamName>
  </SuperStar>
  <SuperStar Name ="Kevin Durant">
    <Age>28</Age>
    <TeamName>Oklahoma City Thunder</TeamName>
  </SuperStar>
  <SuperStar Name ="Westbrook">
      <Age>28</Age>
      <TeamName>Oklahoma City Thunder</TeamName>
  </SuperStar>
</SuperStarList>
XML是由一个SuperStarList列表构成,列表又由一个一个的SuperStar构成。那么我们如何在代码中获取到这个XML实例,并且作为Source赋给Binding呢。这个时候要用到XmlDataProvider和XmlDocument两个类。

.NET Framework提供了一套处理XML数据的类库: 符合DOM标准的类库。包括 XmlDocument,XmlNode等类。

XmlDataProvider类则允许以声明方式访问数据绑定的 XML 数据。它是用来处理数据绑定的XML数据的。我们看一下两者的用法。

XmlDataProvider的两个重要属性:
Document 获取或设置要用作绑定源的XmlDocument
XPath
获取或设置用于生成数据集合的XPath查询。
XmlDocument的Load方法:
Load(Stream) Loads the XML document from the specified stream.
Load(String) Loads the XML document from the specified URL.
Load(TextReader) Loads the XML document from the specifiedTextReader.
Load(XmlReader) Loads the XML document from the specifiedXmlReader.
LoadXml Loads the XML document from the specified string.

C#代码(用来获取我们刚才的XML文件,存放在桌面,位置是C:\Documents and Settings\10182528\Desktop\SuperStar.xml):
 private void loadButton_Click(object sender,RoutedEventArgs e)
        {
            XmlDocument doc = new XmlDocument();// 生成XmlDocument实例
            doc.Load(@"C:\Documents and Settings\10182528\Desktop\SuperStar.xml");//获取XML文件

            XmlDataProvider xdp = new XmlDataProvider();
            xdp.Document = doc;//设置绑定的XmlDocument

            xdp.XPath = @"/SuperStarList/SuperStar";//设置生成的数据集合
            this.listBox1.DataContext = xdp;
            this.listBox1.SetBinding(ListBox.ItemsSourceProperty,new Binding());

        }
在Button的Click事件中,点击Button 获取XML文件。
 this.listBox1.DataContext = xdp;     //将xdp赋给listBox的DataContext
 this.listBox1.SetBinding(ListBox.ItemsSourceProperty,new Binding());//设置绑定,没有源,没有路径。自己去寻找。
这两句相当于下面的正常绑定

Binding myBinding = new Binding();
myBinding.Source = xdp;//设置绑定源
this.listBox1.SetBinding(ListBox.ItemsSourceProperty,myBinding);//绑定myBinding到listBox1到ItemsSource属性

运行结果:


OK,执行成功。


2.XML文件存放在XAML的资源区域中,需要有根目录和<x:XData>标签

<StackPanel>
        <StackPanel.Resources>
            <XmlDataProvider x:Key="SuperStarXml" XPath="List/SuperStarList">
                <x:XData>
                    <List xmlns="">
                        <!--根目录-->
                        <SuperStarList>
                            <SuperStar Name ="LeBron James">
                                <Age>28</Age>
                                <TeamName>Miami Heat</TeamName>
                                <ChineseName>勒布朗.詹姆斯</ChineseName>
                            </SuperStar>
                            <SuperStar Name ="Dwyane Wade">
                                <Age>32</Age>
                                <TeamName>Miami Heat</TeamName>
                                <ChineseName>德维恩韦德</ChineseName>
                            </SuperStar>
                            <SuperStar Name ="Chris Bosh">
                                <Age>29</Age>
                                <TeamName>Miami Heat</TeamName>
                                <ChineseName>克里斯波什</ChineseName>
                            </SuperStar>
                            <SuperStar Name ="Kevin Durant">
                                <Age>25</Age>
                                <TeamName>Oklahoma City Thunder</TeamName>
                                <ChineseName>凯文杜兰特</ChineseName>
                            </SuperStar>
                            <SuperStar Name ="Westbrook">
                                <Age>24</Age>
                                <TeamName>Oklahoma City Thunder</TeamName>
                                <ChineseName>拉塞尔维斯布鲁克</ChineseName>
                            </SuperStar>
                        </SuperStarList>
                    </List>
                </x:XData>
            </XmlDataProvider>
        </StackPanel.Resources>
        <TextBlock FontSize="18" FontWeight="Bold" HorizontalAlignment="Center"
                   Text="BasketBall Star Information" />
        <ListBox>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Age" Width="60"/>
                <TextBlock Text="TeamName" Width="120"/>
                <TextBlock Text="ChineseName" Width="120"/>
            </StackPanel>
        </ListBox>
        <ListBox >
            <ListBox.ItemsSource>
                <Binding Source="{StaticResource SuperStarXml}"
                         XPath="*[@Name ='LeBron James']|*[Age>=26]"/>
            </ListBox.ItemsSource>
            
            
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                      <TextBlock Width="60" Text="{Binding XPath= Age}"/>
                      <TextBlock Width="120" Text="{Binding XPath= TeamName}"/>    
                      <TextBlock Width="120" Text="{Binding XPath= ChineseName}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        
    </StackPanel>

运行结果:

设置和绑定ItemsSource:
<ListBox.ItemsSource>
                <Binding Source="{StaticResource SuperStarXml}" //使用动态资源绑定Source
                         XPath="*[@Name ='LeBron James']|*[Age>=26]"/>//XPath 选择路径
</ListBox.ItemsSource>

XPath用来选择路径,有关XPath的介绍, 点击这里

写到这里我们已经发现。绑定ListBox这种列表控件,首先应该绑定ItemsSource,然后可以在绑定Path(有两种方式,可以直接用DisplayMemberPath来设置,或者用自己的数据模板来自己设定)
如果我们将数据模板去掉,改成下面这样子。
  <ListBox >
            <ListBox.ItemsSource>
                <Binding Source="{StaticResource SuperStarXml}"
                         XPath="*"/>
            </ListBox.ItemsSource>
            <ListBox.DisplayMemberPath>
               @Name
            </ListBox.DisplayMemberPath>
</ListBox>
则运行结果,只展示了一个Name(因为Name是属性,需要加@)



OK,这一部分到此结束。

相关文章

php输出xml格式字符串
J2ME Mobile 3D入门教程系列文章之一
XML轻松学习手册
XML入门的常见问题(一)
XML入门的常见问题(三)
XML轻松学习手册(2)XML概念