在之前的教程中,我们大多是在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中的任何内容都不会更新。这个例子非常简单,一个用户类将保留用户名,一个ListBox用于显示它们,一些按钮用于操作列表及其内容。列表的ItemsSource被分配给我们在窗口构造函数中创建的几个用户的快速列表。问题是没有一个按钮似乎工作。让我们通过两个简单的步骤来解决这个问题。
第一步是让UI响应列表源(ItemsSource)中的更改,就像我们添加或删除用户时一样。我们需要的是一个列表,通知任何目的地其内容的更改,幸运的是,WPF提供了一种类型的列表。它被称为ObservableCollection,你使用它就像常规的List 一样,只有一些区别。
在下面的最后一个例子中,我们简单地用一个ObservableCollection <user>替换了List <user> - 这就是全部!这将使“添加”和“删除”按钮起作用,但它不会对“更改名称”按钮执行任何操作,因为更改将发生在绑定数据对象本身而不是源列表上 - 第二步将处理该场景。
第二步是让我们的自定义User类实现INotifyPropertyChanged接口。通过这样做,我们的User对象能够警告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非常容易处理 - 它只是要求您在需要更改绑定目标中反映的源列表的情况下使用此特定列表类型。