TOC

This article has been localized into Chinese by the community.

資料綁定:

响应变化

在之前的教程中,我們大多是在UI元件與現有的類別做綁定,但在現實生活的應用程式中,明顯的你會綁定到你現有的資料物件。這時容易,不過當你開始做了之後,你可能會發現一件讓你失望的事---就像之前的例子一樣,變化不會自動的反應。你將會在這篇文章中學習到,你需要額外費一點功夫來達成這件事,不過很幸運的,WPF讓這件事情變的相當簡單。

响应数据源变化

在处理数据源更改时,您可能想要或可能不想处理两种不同的方案:更改项目列表以及每个数据对象中绑定属性的更改。如何处理它们可能会有所不同,具体取决于您正在做什么以及您希望实现的目标,但WPF提供了两个非常简单的解决方案:ObservableCollectionINotifyPropertyChanged接口。

以下示例将向您展示我们为什么需要这两件事:

<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响应列表源(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非常容易处理 - 它只是要求您在需要更改绑定目标中反映的源列表的情况下使用此特定列表类型。


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!