考慮一個非常簡單的應用程序:遍及一個人的名字和年齡,正如圖4-1所示:
圖4-1
圖4-1可以實現為一個簡單的xaml如示例4-1。
示例4-1
<!-- Window1.xaml -->
<Window >
<Grid>
<TextBlock >Name:</TextBlock>
<TextBox x:Name="nameTextBox" />
<TextBlock >Age:</TextBlock>
<TextBox x:Name="ageTextBox" />
<Button x:Name="birthdayButton" >Birthday</Button>
</Grid>
</Window>
在這個簡單應用程序中顯示的數據,可以被一個簡單的類表現,如示例4-2所 示。
示例4-2
public class Person {
string name;
public string Name {
get { return this.name; }
set { this.name = value; }
}
int age;
public int Age {
get { return this.age; }
set { this.age = value; }
}
public Person( ) {}
public Person(string name, int age) {
this.name = name;
this.age = age;
}
}
通過這個類,可以自然的實現我們的應用程序行為,如示例4-3所示:
示例4-3
// Window1.xaml.cs
public class Person {}
public partial class Window1 : Window {
Person person = new Person("Tom", 9);
public Window1( ) {
InitializeComponent( );
// Fill initial person fields
this.nameTextBox.Text = person.Name;
this.ageTextBox.Text = person.Age.ToString( );
this.birthdayButton.Click += birthdayButton_Click;
}
void birthdayButton_Click(object sender, RoutedEventArgs e) {
++person.Age;
MessageBox.Show(
string.Format(
"Happy Birthday, {0}, age {1}!",
person.Name,
person.Age),
"Birthday");
}
}
示例4-3的代碼創建了一個Person對象,並且用Person對象的屬性初始化了文 本框。當Birthday按鈕按下時,Person對象的Age屬性值會增加,同時在一個消 息框中顯示更新後的Person數據,如圖4-2所示。
圖4-2
我們的簡單應用程序實現,事實上,非常的簡單。Person對象的Age屬性在改 變後,顯示在消息框中,但是不會顯示在主窗體中。一個保持應用程序UI是最新 的辦法是,編寫代碼使得無論一個Person對象何時更新,將會同時間手動更新UI ,正如示例4-4所示。
示例4-4
void birthdayButton_Click(object sender, RoutedEventArgs e) {
++person.Age;
// Manually update the UI
this.ageTextBox.Text = person.Age.ToString( );
MessageBox.Show(
string.Format(
"Happy Birthday, {0}, age {1}!",
person.Name,
person.Age),
"Birthday");
}
僅僅一行代碼,我們就“修復”了這個應用程序。這是一個誘人而且流行的 方法,然而不能隨著應用程序變得復雜而伸縮,並且需要更多這樣的“單行”代 碼。我們需要一個更好的方法,超越於最簡單的應用程序之上。
4.1.1對象的改變
對於UI,一個更健壯的跟蹤對象改變的方法是,當對象改變的時候為這個對 象激發一個事件。從.NET2.0時開始,正確的方法是為這個對象實現 INotifyPropertyChanged接口,正如示例4-5。
示例4-5
using System.ComponentModel; // INotifyPropertyChanged
public class Person : INotifyPropertyChanged {
// INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propName) {
if( this.PropertyChanged != null ) {
PropertyChanged(this, new PropertyChangedEventArgs (propName));
}
}
string name;
public string Name {
get { return this.name; }
set {
this.name = value;
OnPropertyChanged("Name");
}
}
int age;
public int Age {
get { return this.age; }
set {
this.age = value;
OnPropertyChanged("Age");
}
}
public Person( ) {}
public Person(string name, int age) {
this.name = name;
this.age = age;
}
}
在示例4-5中,當Person對象的任意一個屬性改變時(如由Birthday按鈕激活 引起的實現),就會激活該對象的PropertyChanged事件。我們可以使用這個事 件保持UI同步於Person的屬性值,正如示例4-6。
示例4-6
// Window1.xaml.cs
public class Person : INotifyPropertyChanged {}
public partial class Window1 : Window {
Person person = new Person("Tom", 9);
public Window1( ) {
InitializeComponent( );
// Fill initial person fields
this.nameTextBox.Text = person.Name;
this.ageTextBox.Text = person.Age.ToString( );
// Watch for changes in Tom's properties
person.PropertyChanged += person_PropertyChanged;
this.birthdayButton.Click += birthdayButton_Click;
}
void person_PropertyChanged(
object sender,
PropertyChangedEventArgs e) {
switch( e.PropertyName ) {
case "Name":
this.nameTextBox.Text = person.Name;
break;
case "Age":
this.ageTextBox.Text = person.Age.ToString( );
break;
}
}
void birthdayButton_Click(object sender, RoutedEventArgs e) {
++person.Age; // person_PropertyChanged will update ageTextBox
MessageBox.Show(
string.Format(
"Happy Birthday, {0}, age {1}!",
person.Name,
person.Age),
"Birthday");
}
}
示例4-6顯示了一個單獨的Person示例,創建於主窗體第一次開始出現,使用 Person值初始化了Name和Age的文本框,訂閱了隨屬性改變的事件,用來保持當 Person對象改變時文本框仍然是最新的。在這段代碼的恰當位置,birthday按鈕 的click事件句柄不需要手動更新文本框,當Tom的年齡改變的時候;代替的,更 新Age屬性引起層疊式事件保持年齡的文本框是最新的,隨著Person對象的改變 ,正如圖4-3所示。
圖4-3
步驟如下:
1.用戶點擊按鈕,引起Click的事件被激活
2.Click句柄從Person對象獲得年齡:9
3.Click句柄將Person對象的年齡為10
4.Person的Age屬性設置器激發了PropertyChanged事件
5. PropertyChanged事件傳遞到UI代碼的事件句柄
6.UI代碼更新年齡的文本框,從9改為10
7.按鈕的Click事件句柄顯示一個消息框,顯示新的年齡:10
在消息框顯示Tom的新年齡之前,在表單中,年齡的文本框已經更新了,如圖 4-4所示
圖4-4
隨著處理了InotifyPropertyChanged的事件,當對象的數據改變時,UI將更 新以反映這種改變。然而,這僅僅解決了問題的一半;我們仍然需要處理如何將 UI中的改變反映到對象中。
4.1.2 控件的改變
不考慮跟蹤UI改變並將其反映到對象的特定方法,我們可以容易地以一個例 子告終(想改變一個人的名字),顯示對象(正如點擊Birthday按鈕時所發的) ,以及期望改變已經發生,僅僅對圖4-5有所失望。
圖4-5
注意到圖4-5,表單中,名字是“Thomsen Federick”,而消息框中是“Tom ”,這顯示了UI的一部分已經改變,而底層的對象並未改變。為了修復這個問題 ,我們觀察文本框對象的Text屬性的改變,相應的更新Person對象,如示例4-7 。
示例4-7
public partial class Window1 : Window {
Person person = new Person("Tom", 9);
public Window1( ) {
InitializeComponent( );
// Fill initial person fields
this.nameTextBox.Text = person.Name;
this.ageTextBox.Text = person.Age.ToString( );
// Watch for changes in Tom's properties
person.PropertyChanged += person_PropertyChanged;
// Watch for changes in the controls
this.nameTextBox.TextChanged += nameTextBox_TextChanged;
this.ageTextBox.TextChanged += ageTextBox_TextChanged;
this.birthdayButton.Click += birthdayButton_Click;
}
void nameTextBox_TextChanged(object sender, TextChangedEventArgs e) {
person.Name = nameTextBox.Text;
}
void ageTextBox_TextChanged(object sender, TextChangedEventArgs e) {
int age = 0;
if( int.TryParse(ageTextBox.Text, out age) ) {
person.Age = age;
}
}
void birthdayButton_Click(object sender, RoutedEventArgs e) {
++person.Age;
// nameTextBox_TextChanged and ageTextBox_TextChanged
// will make sure the Person object is up to date
MessageBox.Show(
string.Format(
"Happy Birthday, {0}, age {1}!",
person.Name,
person.Age),
"Birthday");
}
}
現在,不論數據如何改變,Person對象和顯示Person對象的UI會保持同步。 圖4-6顯示了UI中名字的改變,正確傳到了Person對象。
圖4-6
盡管我們得到了想要的功能,仍需要寫相當多的代碼使之發生:
Window1代碼重構,設置控件的初始值
Window1代碼重構,使用PropertyChanged事件鉤子,對Person對象的屬性更 改進行跟蹤。
PropertyChanged事件句柄從Person對象獲取更新過的數據,將數據轉換為適 當的字符串
Window1代碼重構,使用TextBox對象的TextChanged事件鉤子,跟蹤UI的改變
TextChanged事件句柄將更新過的TextBox數據傳入Person對象,將數據適當 的轉換
這段代碼允許我們安全的寫自己的birthday按鈕事件句柄,是所有的改變同 步,當我們顯示消息框的時候。然而,容易想象到,當對象的數量增加或者對象 屬性的數量增加時,這段代碼很快就會失去控制。加上,這看起來就像一件相當 普通的事情,以至於有人一定事先就提供了一種更簡單的方法來做這件事。事實 上,這種方法被稱為“數據綁定”。