《Programming WPF》翻译 第4章 1.不使用数据绑定

考虑一个非常简单的应用程序:遍及一个人的名字和年龄,正如图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) {
    
ifthis.PropertyChanged != null ) {
      PropertyChanged(
thisnew 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值初始化了NameAge的文本框,订阅了随属性改变的事件,用来保持当Person对象改变时文本框仍然是最新的。在这段代码的恰当位置,birthday按钮的click事件句柄不需要手动更新文本框,当Tom的年龄改变的时候;代替的,更新Age属性引起层叠式事件保持年龄的文本框是最新的,随着Person对象的改变,正如图4-3所示。

4-3


步骤如下:

1.用户点击按钮,引起Click的事件被激活

2.Click句柄从Person对象获得年龄:9

3.Click句柄将Person对象的年龄为10

4.PersonAge属性设置器激发了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;
    
ifint.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按钮事件句柄,是所有的改变同步,当我们显示消息框的时候。然而,容易想象到,当对象的数量增加或者对象属性的数量增加时,这段代码很快就会失去控制。加上,这看起来就像一件相当普通的事情,以至于有人一定事先就提供了一种更简单的方法来做这件事。事实上,这种方法被称为“数据绑定”。

posted @ 2008-04-04 12:15  包建强  Views(1055)  Comments(1Edit  收藏  举报