考虑一个非常简单的应用程序:遍及一个人的名字和年龄,正如图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按钮事件句柄,是所有的改变同步,当我们显示消息框的时候。然而,容易想象到,当对象的数量增加或者对象属性的数量增加时,这段代码很快就会失去控制。加上,这看起来就像一件相当普通的事情,以至于有人一定事先就提供了一种更简单的方法来做这件事。事实上,这种方法被称为“数据绑定”。