This article has been localized into Chinese by the community.
响应变化
在之前的教程中,我們大多是在UI元件與現有的類別做綁定,但在現實生活的應用程式中,明顯的你會綁定到你現有的資料物件。這時容易,不過當你開始做了之後,你可能會發現一件讓你失望的事---就像之前的例子一樣,變化不會自動的反應。你將會在這篇文章中學習到,你需要額外費一點功夫來達成這件事,不過很幸運的,WPF讓這件事情變的相當簡單。
响应数据源变化
在处理数据源变更时,您可能面临两种不同场景需要处理:项目列表变更和每个数据对象的绑定属性变更。如何处理它们可能有所不同,取决于您正在做什么以及您希望实现什么,但WPF提供了两种非常简单的解决方案可以使用: ObservableCollection 和INotifyPropertyChanged 接口。
以下示例将向您展示我们为什么需要这两件事:
<Window x:Class="WpfTutorialSamples.DataBinding.ChangeNotificationSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ChangeNotificationSample" Height="150" Width="300">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">
<Button Name="btnAddUser" Click="btnAddUser_Click">Add user</Button>
<Button Name="btnChangeUser" Click="btnChangeUser_Click" Margin="0,5">Change user</Button>
<Button Name="btnDeleteUser" Click="btnDeleteUser_Click">Delete user</Button>
</StackPanel>
<ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTutorialSamples.DataBinding
{
public partial class ChangeNotificationSample : Window
{
private List<User> users = new List<User>();
public ChangeNotificationSample()
{
InitializeComponent();
users.Add(new User() { Name = "John Doe" });
users.Add(new User() { Name = "Jane Doe" });
lbUsers.ItemsSource = users;
}
private void btnAddUser_Click(object sender, RoutedEventArgs e)
{
users.Add(new User() { Name = "New user" });
}
private void btnChangeUser_Click(object sender, RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
(lbUsers.SelectedItem as User).Name = "Random Name";
}
private void btnDeleteUser_Click(object sender, RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
users.Remove(lbUsers.SelectedItem as User);
}
}
public class User
{
public string Name { get; set; }
}
}
尝试自己运行它并观察, 即使您向列表添加内容或更改其中一个用户的名称,UI中的任何内容都不会更新。这个例子非常简单,一个保存着用户姓名的User类,一个用来显示它们的ListBox,一些用于操作列表及其内容的按钮。列表的ItemsSource被分配为我们在窗口构造函数中创建的几个用户的快速列表。问题是似乎没有一个按钮能正确工作。让我们通过两个简单的步骤来解决这个问题。
反映列表数据源中的更改
第一步是,例如当我们增加或者删除一个用户时,让UI能响应列表源(ItemSource)的更改。我们需要一个列表,任何目标当它的内容变更时都能通知到,幸运的是,WPF提供一种列表类型能做到这点。它被称作ObservableCollection,除了一些区别外,可以像常规的List一样使用。
在下面的最后一个例子中,我们简单地用一个ObservableCollection <user>替换了List <user> - 这就是全部!这将使“添加”和“删除”按钮起作用,但它不会对“更改名称”按钮执行任何操作,因为更改将发生在绑定数据对象本身而不是源列表上 - 第二步将处理该场景。
反映数据对象的变化
第二步是让我们的自定义User类实现INotifyPropertyChanged接口。这样做以后,当User对象的属性发生更改时,能通知到UI层。这比起我们上面一段所做的列表类型更改工作要复杂点,但它仍然是实现数据对象更改后能自动更新UI的最简单方法之一了。
最终工作的例子
通过上述两个更改,我们现在有一个示例将反映数据源中的更改。它看起来像这样:
<Window x:Class="WpfTutorialSamples.DataBinding.ChangeNotificationSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ChangeNotificationSample" Height="135" Width="300">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Margin="10,0,0,0">
<Button Name="btnAddUser" Click="btnAddUser_Click">Add user</Button>
<Button Name="btnChangeUser" Click="btnChangeUser_Click" Margin="0,5">Change user</Button>
<Button Name="btnDeleteUser" Click="btnDeleteUser_Click">Delete user</Button>
</StackPanel>
<ListBox Name="lbUsers" DisplayMemberPath="Name"></ListBox>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfTutorialSamples.DataBinding
{
public partial class ChangeNotificationSample : Window
{
private ObservableCollection<User> users = new ObservableCollection<User>();
public ChangeNotificationSample()
{
InitializeComponent();
users.Add(new User() { Name = "John Doe" });
users.Add(new User() { Name = "Jane Doe" });
lbUsers.ItemsSource = users;
}
private void btnAddUser_Click(object sender, RoutedEventArgs e)
{
users.Add(new User() { Name = "New user" });
}
private void btnChangeUser_Click(object sender, RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
(lbUsers.SelectedItem as User).Name = "Random Name";
}
private void btnDeleteUser_Click(object sender, RoutedEventArgs e)
{
if(lbUsers.SelectedItem != null)
users.Remove(lbUsers.SelectedItem as User);
}
}
public class User : INotifyPropertyChanged
{
private string name;
public string Name {
get { return this.name; }
set
{
if(this.name != value)
{
this.name = value;
this.NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
小结
正如您所看到的,实现INotifyPropertyChanged非常简单,但它确实会在您的类上创建一些额外的代码,并为您的属性添加一些额外的逻辑。如果您想要绑定到自己的类并立即在UI中反映更改,那么这是您必须付出的代价。显然,您只需要在绑定的属性的setter中调用NotifyPropertyChanged - 其余的可以保持原样。
另一方面,ObservableCollection非常容易处理 - 它只是要求您在需要更改绑定目标中反映的源列表的情况下使用此特定列表类型。