我們知道WPF最重要的一個特性是數據驅動UI,Binding就是實現這個特性的橋樑,這個類把數據和界面控件關聯起來。而且它還支持雙向通信。當數據改變時,界面顯示會自動改變;當界面內容改變時,後臺的數據也會自動改變。當然這個雙向通信是可以設置的。
1、Binding基礎
binding源和目標。剛纔說binding是一座橋樑,那源和目標就是橋樑的兩端。一般來說,源就是數據,目標就是UI,但也可以反過來,也可以兩端都是UI控件。下面看一個最簡單的例子:
public class Student:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int ID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
if(PropertyChanged!=null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
}
public int Age { get; set; }
}
我們定義了一個學生類,繼承接口INotifyPropertyChanged,這個接口只定義了一個屬性值改變的事件。
下面是界面代碼:
<Window x:Class="BindingResearch.Window4"
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:BindingResearch"
mc:Ignorable="d"
Title="Window4" Height="450" Width="800">
<StackPanel>
<TextBox x:Name="txtName" Margin="10"/>
<Button x:Name="btn1" Height="50" Content="OK" Click="btn1_Click"/>
</StackPanel>
</Window>
下面我們在界面的構造函數中設置binding:
Student student = new Student();
public Window4()
{
InitializeComponent();
Binding binding = new Binding("Name") { Source = student };
txtName.SetBinding(TextBox.TextProperty, binding);
}
private void btn1_Click(object sender, RoutedEventArgs e)
{
student.Name += "Name";
}
Binding binding = new Binding("Name") { Source = student };這條語句的含義就是:把binding的源設置爲student對象實例,Name爲binding的訪問路徑,意思就是說這個binding關注的是student對象的Name屬性。
txtName.SetBinding(TextBox.TextProperty, binding);這條語句把binding對象和界面控件關聯起來,意思是把txtName文本框的TextProperty和binding對象關聯起來,因爲binding對象已經關聯了student對象了Name屬性,那麼這兩條語句的意思就是:txtName文本框關聯了student對象的Name屬性值。
那文本框怎麼知道Name的屬性值變了呢,答案就是Name屬性的set代碼塊中。PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));這條語句觸發了Name屬性的值改變事件,因爲binding對象關聯了Name屬性值,所以當binding對象接受到Name屬性值改變後,就把Name屬性值賦值給了txtName文本框。
當我們點擊按鈕後,文本框就會顯示當前Name屬性的值。
2、Binding的路徑(Path)
path即對象的屬性,關於path有很多種用法,下面我們逐一講解:
1、索引器。看下面的例子:
<Window x:Class="BindingResearch.Window4"
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:BindingResearch"
mc:Ignorable="d"
Title="Window4" Height="450" Width="800">
<StackPanel>
<TextBox x:Name="txtName" Margin="10" />
<TextBox x:Name="txtLength" Text="{Binding Path=Text.[1], ElementName=txtName,Mode=OneWay}" Margin="10" />
</StackPanel>
</Window>
我們把txtLength文本框的文本通過binding設置爲txtName文本框文本的第二個字符,運行效果如下:
等效的c#代碼:
Binding binding1 = new Binding("Text.[1]") { Source = txtName,Mode= BindingMode.OneWay };
txtLength.SetBinding(TextBox.TextProperty, binding1);
2、使用集合或者DataView作爲Binding源時,如果我們想把它的默認元素當作path使用,則語法如下:
List<string> stringList = new List<string>() { "Tim", "Tom", "Blog" };
txt1.SetBinding(TextBox.TextProperty, new Binding("/") { Source = stringList });
txt2.SetBinding(TextBox.TextProperty, new Binding("/Length") { Source = stringList, Mode = BindingMode.OneWay });
txt3.SetBinding(TextBox.TextProperty, new Binding("/[1]") { Source = stringList, Mode = BindingMode.OneWay });
/斜槓即代表默認元素,集合的默認元素即第一個元素。
3、如果集合是嵌套的,我們想把子級集合中的元素作爲path,則語法如下:
City c1 = new City() { Name = "黃岡" };
City c2 = new City() { Name = "武漢" };
Province p1 = new Province()
{
Name = "湖北",
CityList = new List<City>() { c1, c2 }
};
City c3 = new City() { Name = "廣州" };
City c4 = new City() { Name = "深圳" };
Province p2 = new Province()
{
Name = "廣東",
CityList = new List<City>() { c3, c4 }
};
Country country1 = new Country()
{
Name = "中國",
ProvinceList = new List<Province>() { p1, p2 }
};
List<Country> countries = new List<Country>();
countries.Add(country1);
txt1.SetBinding(TextBox.TextProperty, new Binding("/Name") { Source= countries });
txt2.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList.[1].Name") { Source = countries });
txt3.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/Name") { Source = countries });
txt4.SetBinding(TextBox.TextProperty, new Binding("/ProvinceList/CityList/Name") { Source = countries });
運行效果如下:
4、沒有指定path的binding。如果path的值爲"."或者直接沒有指定path,則表明binding的源本身就是數據,比如:string,int類型的數據。看下面的例子:
<Window x:Class="BindingResearch.Window7"
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:BindingResearch"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="Window7" Height="450" Width="800">
<Window.Resources>
<sys:String x:Key="myString">菩提本無樹,明鏡亦非臺。本來無一物,何處惹塵埃。</sys:String>
</Window.Resources>
<StackPanel>
<TextBlock x:Name="lable1" Margin="10" Text="{Binding Path=., Source={StaticResource ResourceKey=myString}}"/>
</StackPanel>
</Window>
<TextBlock x:Name="lable1" Margin="10" Text="{Binding Path=., Source={StaticResource ResourceKey=myString}}"/>
我們看到path=.,或者直接把path=.去掉都可以。
這條語句等效的c#代碼:
string s = "菩提本無樹,明鏡亦非臺。本來無一物,何處惹塵埃。";
lable1.SetBinding(TextBlock.TextProperty, new Binding(".") { Source = s });
這裏的"."也可以省略
3、沒有Source的Binding
DataContext屬性被定義在FrameworkElement類裏,這個類是WPF控件的基類,這意味着所有的WPF控件都具備這個屬性。我們知道WPF的UI佈局是樹形結構的,這棵樹的每個節點都是控件,由此我們推出一個結論:在UI元素樹每個節點都有DataContext。這一點很重要,因爲當一個Binding只知道自己的path而不知道自己的source時,它就會沿着UI元素樹一路向樹的根部找過去,每路過一個節點都要看看這個節點的DataContext是否具備path指定的屬性。如果有,那就把這個對象作爲自己的source;如果沒有,那就繼續找下去;如果到了樹的根部還沒有找到,那這個binding就沒有source,因此也不會得到數據。
我們看一個例子:
先定義一個Person類:
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
在xaml裏面定義DataContext和Binding:
<Window x:Class="BindingResearch.Window8"
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:BindingResearch"
mc:Ignorable="d"
Title="Window8" Height="450" Width="800">
<Window.DataContext>
<local:Person ID="1002" Name="李四" Age="40"></local:Person>
</Window.DataContext>
<StackPanel>
<TextBox x:Name="txt1" Text="{Binding Path=ID}" Margin="10"/>
<TextBox x:Name="txt2" Text="{Binding Path=Name}" Margin="10"/>
<TextBox x:Name="txt3" Text="{Binding Path=Age}" Margin="10"/>
</StackPanel>
</Window>
我們把DataContext定義在Window標籤內,給TextBox設置沒有Source,只有Path的Binding。運行效果如下:
4、即沒有指定Source也沒有指定Path的Binding
學習完前面的知識,理解這個其實很容易,我們看一個例子就明白了:
<Window x:Class="BindingResearch.Window9"
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:BindingResearch"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="Window9" Height="450" Width="800">
<Window.DataContext>
<sys:String>Hello DataContext!</sys:String>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Mode=OneWay}" Margin="10"/>
</StackPanel>
</Window>