程式的本質是數據加演算法。通俗一點來說呢,其實就是用戶給一個輸入,經過演算法的處理之後,電腦反饋一個輸出給用戶。可以很清楚的看出,在這個過程中,處於主導地位的是數據。但是,當我們在進行圖形用戶界面(Graphic User Interface,GUI)編程的時,數據總是處於被動地位。也就是說,程式總是 ...
程式的本質是數據加演算法。通俗一點來說呢,其實就是用戶給一個輸入,經過演算法的處理之後,電腦反饋一個輸出給用戶。可以很清楚的看出,在這個過程中,處於主導地位的是數據。但是,當我們在進行圖形用戶界面(Graphic User Interface,GUI)編程的時,數據總是處於被動地位。也就是說,程式總是在等待接收來自UI的消息/事件,在這些事件被處理之後,才會反饋給用戶一個輸出。我們用Data Binding可以在GUI編程時讓數據回到程式的核心。
1、Data Binding在WPF中的地位
我們之前也提到過,程式的本質就是數據+演算法。數據一般會在存儲、邏輯和展示三個層進行流通,相對應的來講,演算法一般會分佈在以下幾處:
A、資料庫內部
B、讀取和寫入數據
C、業務邏輯
D、數據展示
E、界面與邏輯的交互
我們很容易能夠理解,A、B兩部分的演算法一般很穩定,我們在編程的過程中很少去改動他們,同時他們的復用性也是很高。C這一部分包含大量的演算法,因為其與客戶的需求關係最為緊密,這也就導致這一部分的代碼最複雜、變動最大。D、E兩部分主要負責UI與邏輯的交互,也會有相應的演算法。 顯而易見,包含大部分代碼的C部分最為重要,是開發的重中之重,但是在我們開發的過程中,D、E兩個部分卻經常成為麻煩的來源。問題的根源就在於邏輯層與展示層的地位不固定——當我們去編程實現用戶的需求是,很顯然邏輯層處於中心地位,但是在UI交互的過程中,展示層又處於中心地位。WPF作為一種專門的展示層技術,在其華麗的外觀這種表層現象之下,更重要的是他在很層次上幫助程式員把思維的重心固定在邏輯層,無論在什麼情況下,展示層總是處於從屬地位。是什麼讓WPF擁有這種“超能力”呢?關鍵就在於WPF引進了Data Binding以及與之配套的Dependency Property和Data Template。
我們常常說在WPF開發中,是“數據驅動UI”,那麼究竟什麼事“數據驅動UI”呢?其實,在WPF編程中,Data Binding起到的作用就如同現實生活中的高速公路,有了他,加工好的數據就會自動傳入到用戶界面加以顯示,被用戶修改過的數據也會通過他自動回傳到邏輯層……周而複始,程式的邏輯層就想一個引擎一樣不停的運轉。
在引入了Data Binding機制後,邏輯層和展示層之間就會是直來直去的,不涉及任何的邏輯問題。這樣帶來的好處就是用戶界面不包含演算法;而Data Binding本身就是雙向通信,所以相當於將D、E兩個部分合二為一。
2、Binding基礎
什麼是Binding?我們可以將Binding看做一個橋梁,他的兩端分別是Source和Target。數據從哪裡來,那麼哪裡就是source,到哪去那就是target。一般而言,Binding的源是邏輯層的對象,Binding的對象是UI層的控制項對象。實現Binding後,數據就會源源不斷的從邏輯層通過Binding送到UI層,UI層將這些數據進行展示,這就完成了數據驅動UI的過程。同時,在這個過程中,我們可以設置源於目標之間是雙向通信還是單向通信,還可以在數據傳送的過程中對數據的準確性進行校驗甚至轉換數據的類型。
下麵,我們來看一個最簡單的Binding的例子。
首先,我們創建一個Student的對象,讓它作為數據源進行使用。
1 class Student
2 {
3 private string _name;
4 public string Name
5 {
6 get{return _name;}
7 set{_name = value;}
8 }
9 }
這個類很是簡單,簡單到只有一個string類型的Name屬性。前面說過數據源是一個對象,一個對象本身可能會有很多數據,這些數據又通過屬性暴露給外界。那麼其中哪個元素是你想通過Binding送達UI元素的呢,換句話說,UI元素關心的是哪個屬性值的變化呢?這個屬性值稱之為Binding的路徑(Path)。但光有屬性還不行-------Binding是一種自動機制,當值變化後屬性要有能力通知Binding,讓Binding把變化傳遞給UI元素。怎樣才能讓一個屬性具備這種通知Binding值已經改變的能力呢?方法是在屬性的Set語句中激發一個PropertyChanged事件。這個事件不需要我們自己聲明,我們要做的事是讓作為數據源的類實現System.ComponentModel名稱空間中的INotifyPropertyChanged介面。當為Binding設置了數據源之後,Binding就會自動偵聽來自這個介面PropertyChanged事件。
實現INotifyPropertyChanged介面的類看起來是這樣:
1 class Student:INotifyPropertyChanged
2 {
3 public event PropertyChangedEventHandler PropertyChanged;
4 private string _name;
5
6 public string Name
7 {
8 get
9 {
10 return _name;
11 }
12
13 set
14 {
15 _name = value;
16 //激發事件
17 if (PropertyChanged != null)
18 {
19 this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name"));
20 }
21 }
22 }
23 }
當我們這樣升級之後,只要Student中Name屬性的值一變化,PropertyChanged事件就會被激發,Binding接收到這個事件後發現事件的消息告訴它是Name屬性值發生了變化,於是通知Binding目標端的UI元素顯示新的值。
現在,我們在窗體上準備一個Text和一個Button,代碼如下:
1 <Window x:Class="_1_Binding基礎.MainWindow"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:local="clr-namespace:_1_Binding基礎"
7 mc:Ignorable="d"
8 Title="MainWindow" Height="110" Width="300">
9 <Grid>
10 <StackPanel>
11 <TextBox Name="txtBoxName" Margin="5" BorderBrush="Black"></TextBox>
12 <Button Content="Add Age" Click="Button_Click" Margin="5"></Button>
13 </StackPanel>
14 </Grid>
15 </Window>
接下來,我們使用Binding將數據源和UI元素連接起來,具體代碼如下:
1 public partial class MainWindow : Window
2 {
3 Student stu;
4 public MainWindow()
5 {
6 InitializeComponent();
7 //準備數據源
8 stu = new Student();
9
10 //準備Binding
11 Binding binding = new Binding();
12 binding.Source = stu;
13 binding.Path = new PropertyPath("Name");
14
15 //使用Binding連接數據源與Binding目標
16 BindingOperations.SetBinding(this.txtBoxName, TextBox.TextProperty, binding);
17
18
19 }
20
21 private void Button_Click(object sender, RoutedEventArgs e)
22 {
23 stu.Name += "Button";
24 }
25 }
下麵我們逐一的解讀一下這一段代碼。在準備Binding的部分,首先用“Binding binding = new Binding();”聲明Binding類型變數並創建實例,之後用“binding.Source = stu;”為Binding指定源,用“binding.Path = new PropertyPath("Name");”語句為Binding指定訪問路徑。
把數據源和目標連接在一起的任務是使用“BindingOperations.SetBinding(...)”方法完成的,這個方法的3個參數是我們記憶的重點:
第一個參數是指定Binding的目標,本例中的this.textBoxName。
與數據源的Path原理類似,第二個參數用於為Binding指明為Binding指明把這個數據送達目標的哪個數據。
第三個參數很明顯,就是指定使用哪個Binding實例將數據源和目標關聯起來。
最後我們在Button的Click事件中對Name屬性進行了更新,運行程式,點擊Button,可以看到如下的結果:
先用這個例子作為基礎,後面我們來研究Binding的每個特點。