一、什么是數據綁定
官方這樣解釋:數據綁定是在應用程序 UI 與業務邏輯之間建立連接的過程。簡單的理解就是通過數據綁定的方式實現了后台數據和前台UI元素的關聯,可以比喻成UI元素和數據源的橋梁,更詳細點的說數據綁定提供了一種數據呈現與交互的簡捷方式,使得數據與UI分離,並能使得數據源和UI元素之間的自動更新、同步。
二、數據綁定的幾個名詞
在正式介紹數據綁定之前簡單說幾個關於數據綁定的常用名詞,我想大家只要看一遍都會理解什么意思,不需要多說。
(1)綁定源:即數據的來源,綁定源可以是任意的CLR對象。不過實際需要的時候則是該對象的某一特定屬性。
(2)綁定目標:即從綁定源獲得的數據綁定到的UI元素。可以是FrameworkElement類型的任意對象,相對於綁定源,實際上綁定的是該控件的一個特定屬性。
(3)、綁定模式:這個也很好理解,a、OneTime:僅僅一次綁定,在綁定創建時使用源數據更新目標,適用於只顯示數據而不進行數據的更新。簡單理解為:綁定源僅僅把數據給綁定目標一次后,兩者變得毫不相關。b、OneWay:單向綁定,在綁定創建時或者源數據發生變化時更新到目標,適用於顯示變化的數據。簡單理解為數據源能把數據更新到綁定目標,而綁定目標的變化卻不能影響到數據源。 c、TwoWay:雙向綁定,在任何時候都可以同時更新源數據和目標。前兩種綁定方式理解了這個相對理解就容易,通俗的講就是綁定源和綁定目標是互通的,無論誰的變化都能夠影響到對方,比如綁定源數據有變化就能馬上把綁定目標的數據更新,反之一樣。
(4)綁定對象:當綁定目標需要綁定數據源時該借助什么呢?綁定對象來完成,可以比喻成中介,起了個名字:Binding。這個對象有三個重要屬性(它們當然與綁定源,綁定目標等有關系,畢竟它是兩者的中介):a、Source:字面意思就能知道指的是綁定源對象 b、RelativeSource:指定綁定源相對於綁定目標的位置來標識綁定源 c、ElementName,綁定源也是UI對象時候,設置其綁定源的的名稱即可。說了這三種屬性,其主要的目的就是找到綁定源,因為Binding一般會寫在綁定目標中,這樣只要找到綁定源就好了,這樣就能把兩者聯系起來。這三個屬性的功能就是找到綁定源。所以這三種方式指定任意一種即可,只要讓中介binding找到綁定源就好了。不過事實上我們在寫程序的過程中很少這么用的,微軟提供了一個更加通用的方法就是:通過設置UI元素的DataContext屬性來制定綁定源,這樣以上三個屬性都無需指定,下文我會在做些介紹。
(5)Path(路徑):Binding對象的一個屬性,來指示綁定源對象中用以提供數據的屬性。
三、通過實例理解基本數據綁定
下面我會通過在頁面上綁定一個學生信息,來逐步探究數據綁定。
1、基本數據綁定
(1)我有這樣一個需求想把一個學生信息綁定到UI界面上,我們該如何實現?首先考慮UI界面上的元素,這個不用多說,直接上圖如下:
(2)簡單說明下,布局很簡單,canvas里面包含着一個Image控件(顯示學生照片),9個TextBlock控件用來顯示學生相關信息,還有兩個button按鈕,當我們點擊小紅和小明時分別顯示他們的信息到UI控件。主要xaml代碼如下:
<phone:PhoneApplicationPage
x:Class="數據綁定實例1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot 是包含所有頁面內容的根網格-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel 包含應用程序的名稱和頁標題-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="數據綁定實例" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="學生信息" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - 在此處放置其他內容-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Button Content="小紅" Height="72" HorizontalAlignment="Left" Margin="29,337,0,0" Name="buttonXiaohong" VerticalAlignment="Top" Width="160" Click="buttonXiaohong_Click" />
<Button Content="小明" Height="72" HorizontalAlignment="Left" Margin="232,337,0,0" Name="buttonXiaoming" VerticalAlignment="Top" Width="160" Click="buttonXiaoming_Click" />
</Grid>
<Canvas Height="253" HorizontalAlignment="Left" Margin="21,54,0,0" Name="canvas1" VerticalAlignment="Top" Width="441" Grid.Row="1">
<Image Height="150" Name="image1" Stretch="Fill" Width="164" Canvas.Left="20" Canvas.Top="50" />
<TextBlock Height="30" Name="textBlockName" Text="" Canvas.Left="269" Canvas.Top="50" />
<TextBlock Height="30" Name="textBlockSex" Text="" Canvas.Left="269" Canvas.Top="86" />
<TextBlock Height="30" Name="textBlockAge" Text="" Canvas.Left="269" Canvas.Top="121" />
<TextBlock Height="30" Name="textBlockBirthday" Text="" Canvas.Left="309" Canvas.Top="157" />
<TextBlock Height="30" Name="textBlockBlog" Text="博客地址:" Canvas.Left="20" Canvas.Top="206" />
<TextBlock Canvas.Left="203" Canvas.Top="50" Height="30" Name="textBlock1" Text="姓名:" />
<TextBlock Canvas.Left="203" Canvas.Top="86" Height="30" Name="textBlock2" Text="性別:" />
<TextBlock Canvas.Left="203" Canvas.Top="121" Height="30" Name="textBlock3" Text="年齡:" />
<TextBlock Canvas.Left="203" Canvas.Top="157" Height="30" Name="textBlock4" Text="出生日期:" />
</Canvas>
</Grid>
</phone:PhoneApplicationPage>
(3)UI設計好了,在准備下資源,兩個人需要兩張圖片了(MM.jpg和GG.jpg),網上隨便找的,加入項目中去,下一步當然是把頁面抽象成類了,下面我們將新建一個學生類,作為基本的數據源。代碼如下:
namespace 數據綁定實例1
{
public class Student
{
public string Name { set; get; }
public string Sex { set; get; }
public int Age { set; get; }
public string Birthday { set; get; }
public BitmapImage Picture { set; get; }
public string Blog { set; get; }
}
}
(4)現在我們開始構造小紅和小明這兩個人,並給出他們的具體信息,代碼如下:
Student Xiaohong = new Student()
{
Name = "小紅",
Sex = "女",
Age = 20,
Birthday = "1988/8/1",
Picture =new BitmapImage(new Uri("MM.jpg",UriKind.RelativeOrAbsolute)),
Blog = "http://home.cnblogs.com/u/fuchongjundream/"
};
Student Xiaoming = new Student()
{
Name = "小明",
Sex = "男",
Age = 22,
Birthday = "1989/6/30",
Picture =new BitmapImage(new Uri("GG.jpg",UriKind.RelativeOrAbsolute)),
Blog = "http://home.cnblogs.com/u/fuchongjundream/"
};
(5)下面剩余的關鍵問題就是把美女和帥哥的信息綁定到UI控件了。綁定代碼如下:
首先引入命名空間:using System.Windows.Data;因為Binding對象在這個命名空間下。
在buttonXiaohong_Click中加入如下代碼:
private void buttonXiaohong_Click(object sender, RoutedEventArgs e)
{
//綁定名字
Binding bdName = new Binding();
bdName.Source = Xiaohong;
bdName.Path = new PropertyPath("Name");
this.textBlockName.SetBinding(TextBlock.TextProperty, bdName);
//綁定性別
Binding bdSex = new Binding();
bdSex.Source = Xiaohong;
bdSex.Path = new PropertyPath("Sex");
this.textBlockSex.SetBinding(TextBlock.TextProperty, bdSex);
//綁定年齡
Binding bdAge = new Binding();
bdAge.Source = Xiaohong;
bdAge.Path = new PropertyPath("Age");
this.textBlockAge.SetBinding(TextBlock.TextProperty, bdAge);
//綁定生日
Binding bdBirthday = new Binding();
bdBirthday.Source = Xiaohong;
bdBirthday.Path = new PropertyPath("Birthday");
this.textBlockBirthday.SetBinding(TextBlock.TextProperty, bdBirthday);
// 綁定照片
Binding bdPicture = new Binding();
bdPicture.Source = Xiaohong;
bdPicture.Path = new PropertyPath("Picture");
this.image1.SetBinding(Image.SourceProperty, bdPicture);
// 綁定博客地址
Binding bdBlog = new Binding();
bdBlog.Source = Xiaohong;
bdBlog.Path = new PropertyPath("Blog");
this.textBlockBlog.SetBinding(TextBlock.TextProperty, bdBlog);
}
(6)現在我們可以運行下看看,單擊小紅,效果圖如下:
(7)很高興我們實現了我們想要的結果,小明的綁定和小紅的道理一樣,我不在贅述,通過上面我們可以總結出綁定的一般步驟:
第一步:實例化一個綁定對象(找到中介);第二步:設置綁定對象的Source 屬性,即設定綁定源(數據來源的地方);第三步:設置綁定路徑,即綁定到綁定源的那個屬性;第四步調用UI控件的綁定方法實現綁定,即實現把數據綁定到綁定目標的那個屬性。以上是基本的數據綁定流程,也是數據綁定的基礎。也許你還有些疑問:比如沒有設置Mode、Convert屬性,沒有設置說明取了默認值,Mode默認為OneWay,Convert沒有設置說明沒有需要數據轉化,下文我也將會介紹。
2、通過DataContext(數據上下文)屬性指定數據源
通過上面的例子知道我們通過Binding對象實現了疏浚到UI的綁定,但是我們寫代碼時很明顯意識到了代碼有很多的冗余,比如說 bdPicture.Source = Xiaohong;設置綁定源是重復的。鑒於此,我們還可以用另一種方式設置數據源的方式,即通過UI元素的數據上下文DataContext屬性來指定數據源。開始接觸DataContext可能不好理解,我簡單說下我的理解:一般來說每個UI控件都是有父類控件的,比如我們上面所用到的image,textblock,button控件父控件是Canvas,Canvas的父控件是Grid;每個控件都會有DataContext屬性,因為每種控件都繼承自FramworkElement,一旦我們指定了這個屬性,其子元素控件在不指定Source和DataContext情況下,都默認使用該屬性指定對象作為綁定源。那上面例子說,我們要是設置Canvas控件的DataContext為小紅:this.canvas1.DataContext = Xiaohong;那么canvas子控件在沒有指定DataContext 屬性情況下,都默認使用Xiaohong作為數據源。再深層次理解下,其實只要設置了父類控件的DataContext 屬性后,程序會自動通過遍歷樹(內部數據結構為樹)而找到要綁定數據源的合適屬性。甚至來說可以設置整個頁面的DataContext為Xiaohong,當然一般我們不會這么做。實際上我們一般的做法是先用DataContext指定高層的元素的數據上下文,然后對特殊的子元素再另行指定綁定源。
通過以上解釋,我們很容易理解可以把上面例子的代碼作如下改進:去掉所有的設置綁定源的語句,我們只用指定this.canvas1.DataContext = Xiaohong;即可實現同樣的功能。
3、通過XAML代碼實現綁定
以上例子都是通過C#代碼后台綁定,當然我們也可以通過XAML代碼更容易的實現綁定。綁定語法說下:<UI標記 綁定屬性="{Binding Path=*,Mode=*,Convert+*,Source=*;}"/>,下面我們還以上面例子為例子通過XAML來綁定小紅和小明。下面我僅僅貼出改動過得代碼:
<Canvas Height="253" HorizontalAlignment="Left" Margin="21,54,0,0" Name="canvas1" VerticalAlignment="Top" Width="441" Grid.Row="1">
<Image Height="150" Name="image1" Stretch="Fill" Width="164" Canvas.Left="20" Canvas.Top="50" Source="{Binding Path=Picture}" />
<TextBlock Height="30" Name="textBlockName" Text="{Binding Path=Name}" Canvas.Left="269" Canvas.Top="50" />
<TextBlock Height="30" Name="textBlockSex" Text="{Binding Path=Sex}" Canvas.Left="269" Canvas.Top="86" />
<TextBlock Height="30" Name="textBlockAge" Text="{Binding Path=Age}" Canvas.Left="269" Canvas.Top="121" />
<TextBlock Height="30" Name="textBlockBirthday" Text="{Binding Path=Birthday}" Canvas.Left="309" Canvas.Top="157" />
<TextBlock Height="30" Name="textBlockBlog" Text="{Binding Path=Blog}" Canvas.Left="20" Canvas.Top="206" />
<TextBlock Canvas.Left="203" Canvas.Top="50" Height="30" Name="textBlock1" Text="姓名:" />
<TextBlock Canvas.Left="203" Canvas.Top="86" Height="30" Name="textBlock2" Text="性別:" />
<TextBlock Canvas.Left="203" Canvas.Top="121" Height="30" Name="textBlock3" Text="年齡:" />
<TextBlock Canvas.Left="203" Canvas.Top="157" Height="30" Name="textBlock4" Text="出生日期:" />
</Canvas>
當然數據源的指定我們仍然通過Canvas的DataContext屬性在C#代碼中指定:
private void buttonXiaohong_Click(object sender, RoutedEventArgs e)
{
this.canvas1.DataContext = Xiaohong;
}
private void buttonXiaoming_Click(object sender, RoutedEventArgs e)
{
this.canvas1.DataContext = Xiaoming;
}
效果圖如下:
4、數據轉換器(Converter)的使用。
如果認真看完代碼的同學肯定會注意到Picture這個屬性,我用的是BitmapImage類型,實例化學生對象,對Picture屬性這樣賦值:Picture =new BitmapImage(new Uri("GG.jpg",UriKind.RelativeOrAbsolute)),這樣雖成功實現了,但總感覺有點不妥。我們一般會把圖片的地址存為字符串類型,但是UI控件Source屬性卻需要的是BitmapImage類型。該如何解決這個矛盾呢?數據轉換器來幫您解決。
根據上面的敘述和需求我們不難總結出轉換器的作用:當綁定源提供的數據格式或者類型與綁定目標所需不一致時就需要通過一個轉換器來進行轉換。Converter的類型其實是一個實現了IValueConverter接口的類。IValueConverter接口中定義了兩個方法Convert和ConvertBack用來對數據的雙向轉換。從字面意思很容易理解,我不在多說。本實例中我們需要的是想把string類型的地址轉換為BitMapImage類型,不需要雙向的轉換,所以我僅僅實現IValueConverter接口中的Convert方法。
首先新建一個轉換類:string類型到BitMapImage類型,並實現IValueConverter接口,需要注意的是:引入using System.Windows.Data命名空間,IValueConverter在此命名空間。
第一步:
using System;
using System.Windows.Data;
using System.Windows.Media.Imaging;
namespace 數據綁定實例1
{
public class StringToBitMapImage:IValueConverter
{
#region IValueConverter 成員
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//value為object類型,需強制轉換(string)value
BitmapImage picture = new BitmapImage(new Uri((string)value, UriKind.RelativeOrAbsolute));
return picture;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
代碼我就不解釋了,我想大家都會很容易的理解。
第二步:定義好了轉換類,下面就是我們該如何用了.
a、我們需要在XAML頁面引入此類的名字空間,並指定一個key。在<phone:PhoneApplicationPage />中引入命名空間:
xmlns:my="clr-namespace:數據綁定實例1"
b、接着把此轉換類定義為一個靜態資源,我定義為Canvas控件的靜態資源,當然你也可以定義為Grid,甚至整個頁面,這是資源樣式的知識我不再細說。
<Canvas.Resources>
<my:StringToBitMapImage x:Key="StringToBitMapImageKey"></my:StringToBitMapImage>
</Canvas.Resources>
我們可以通過指定的Key來訪問轉換類。
c、最后就是轉換器了的使用了:
<Image Height="150" Name="image1" Stretch="Fill" Width="164" Canvas.Left="20" Canvas.Top="50" Source="{Binding Path=Picture,
Converter={StaticResource StringToBitMapImageKey}}" />
d、完成以上代碼后我們回到C#代碼中把Picture屬性統一的改為String類型,然后F5,結果和原來一樣,我們成功實現了轉換。改動的地方如下:
public class Student
{
public string Name { set; get; }
public string Sex { set; get; }
public int Age { set; get; }
public string Birthday { set; get; }
public string Picture { set; get; }
public string Blog { set; get; }
}
Student Xiaohong = new Student()
{
Name = "小紅",
Sex = "女",
Age = 20,
Birthday = "1988/8/1",
Picture ="MM.jpg",
Blog = "http://home.cnblogs.com/u/fuchongjundream/"
};
Student Xiaoming = new Student()
{
Name = "小明",
Sex = "男",
Age = 22,
Birthday = "1989/6/30",
Picture ="GG.jpg",
Blog = "http://home.cnblogs.com/u/fuchongjundream/"
};
this.canvas1.DataContext = Xiaoming;
通過這個小例子我想我們能夠知道了轉換器的用法,需要說明的是轉換器不僅可以實現這些簡單的類型轉換、格式轉換,甚至可以轉換一些更為復雜的邏輯轉換,當然道理都是大同小異。
5、數據綁定模式
開篇已經做了相關介紹,我就不再做詳細說明,僅僅簡單說下,前面我們沒有設置綁定模式,默認情況下都是OneWay。OneTime屬於與一次性綁定很好理解,用到的也很少。TwoWay屬於雙向綁定,可以實現數據在數據源與綁定目標之間可以相互流動,也即源與目標任何一方發生改變都會立即通知到對方並引起對方數據的更新,可以說是實時同步的。下面通過實例主要說明下TwoWay綁定原理。
為了還是用上面的例子,我們需要再加入一個沒有什么實際意義的TextBox控件,僅僅為了做演示(上面用的控件都是沒法改變其綁定值的)。在Canvas再放置一個TextBox控件,並綁定Name屬性,如下圖:
XamL代碼如下:
<TextBox Canvas.Left="6" Canvas.Top="242" Height="74" Name="textBoxName" Text="{Binding Path=Name}" Width="429" />
運行下,順利把小紅的名字信息綁定到了TextBox控件中,這也就是說實現了默認的OneWay,可是我們該如何更改TextBox的值同時更新到數據源呢?在正式開始寫代碼之前我們還需要了解,當選擇OneWay或者TowWay時候,為了綁定源的的變化能夠實時的通知到綁定目標,源對象必須實現INotifyPropertyChanged接口(這個接口在using System.ComponentModel命名空間下,記得加入)。換句話說如果使綁定目標的顯示與綁定源的同步,必須滿足兩個條件:一是設定綁定模式為OneWay或者TwoWay;而是綁定源實現INotifyPropertyChanged接口。下面我們通過實例具體演示:
第一步:源對象實現INotifyPropertyChanged接口
namespace 數據綁定實例1
{
public class Student:INotifyPropertyChanged
{
private string name;
public string Name
{
set
{
this.name = value;
NotifyPropertyChanged("Name");
}
get
{
return this.name;
}
}
public string Sex { set; get; }
public int Age { set; get; }
public string Birthday { set; get; }
public string Picture { set; get; }
public string Blog { set; get; }
#region INotifyPropertyChanged 成員
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
說明:上面代碼我僅僅貼出了Name屬性的相關代碼,其他屬性與其類同,讀者可自行加上。其實簡單的理解就是每當綁定源的相關屬性發生改變時候,綁定源通過以上的通知機制告訴綁定目標“我改變了,你如果是TwoWay或者是OneWay,你也趕緊改變吧”。
第二步:設置綁定模式
<TextBox Canvas.Left="6" Canvas.Top="242" Height="74" Name="textBoxName" Text="{Binding Path=Name, Mode=TwoWay}" Width="429" />
好了,現在我們運行程序看看會有什么變化,程序啟動后單擊小紅:
改變下TextBox的內容,然再單擊小紅看看會有什么情況發生?在預料之中當我們修改完TextBox的內容后,姓名被改變了,這也正是我們想要的結果。
細心的你這時可能再會單擊下小紅,甚至單擊小明后再單擊小紅,發現“小紅”這個名字再也回不來了,因為數據源發生了改變。也許你還會問照片左邊的姓名我們沒有設定綁定模式啊,怎么也改變了。是的我們沒有設定,但系統默認OneWay的。其實過程簡單的是這樣的:我改變了TextBox的內容,因為它和數據源是TwoWay方式所以,它將會通知數據源我改變了你也改變吧,然后數據源就改變了,數據源改變了他當然要通知實現OneWay的照片旁邊的姓名,我被TextBox改變了,你也跟着改變吧。其實也算是個連鎖反應,好了不解釋了,應該能理解的。
好了,可以結尾了,終於把基本的數據綁定通過實例的方式講解完了,當然這在實際運用當中還是遠遠不夠的,隨后我將在WindowsPhone基礎瑣碎總結-----數據綁定(二)中介紹集合對象的數據綁定,敬請關注。
---------------------------------------------------------------------------------------------------------------------------------------------
作者:GavinDream(GavinDream主頁 博客園)
出處:http://www.cnblogs.com/fuchongjundream/
任何轉載必須保留完整文章,在顯要地方顯示署名以及原文鏈接。如您有任何疑問或者授權方面的協商,請發郵件給我 或者 留言。