TOC

This article is currently in the process of being translated into Vietnamese (~82% done).

Data binding:

Responding to changes

Cho đến nay trong hướng dẫn này, chúng tôi chủ yếu tạo các ràng buộc giữa các thành phần UI và các lớp hiện có, nhưng trong các ứng dụng thực tế, rõ ràng bạn sẽ ràng buộc với các đối tượng dữ liệu của riêng bạn. Điều này thật dễ dàng, nhưng một khi bạn bắt đầu thực hiện nó, bạn có thể khám phá điều gì đó làm bạn thất vọng: Các thay đổi không được phản ánh tự động, giống như trong các ví dụ trước. Như bạn sẽ tìm hiểu trong bài viết này, bạn chỉ cần thêm một chút công việc để điều này xảy ra, nhưng may mắn thay, WPF làm điều này khá dễ dàng.

Phản hồi khi nguồn dữ liệu có thay đổi

Có hai kịch bản khác nhau mà bạn có thể hoặc không muốn xử lý khi xử lý các thay đổi nguồn dữ liệu: Thay đổi danh sách các mục và thay đổi trong các thuộc tính ràng buộc trong từng đối tượng dữ liệu. Cách xử lý chúng có thể khác nhau, tùy thuộc vào những gì bạn đang làm và những gì bạn đang muốn thực hiện, nhưng WPF đi kèm với hai giải pháp rất dễ dàng mà bạn có thể sử dụng: Giao diện ObservableCollection và giao diện INotifyPropertyChanged.

Ví dụ sau đây sẽ cho bạn thấy lý do tại sao chúng ta cần hai điều sau:

<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; }
	}
}

Hãy thử tự chạy nó và xem cách bạn thêm một cái gì đó vào danh sách hoặc thay đổi tên của một trong những người dùng, không có gì trong UI được cập nhật. Ví dụ này khá đơn giản, với lớp Người dùng sẽ giữ tên người dùng, ListBox để hiển thị chúng và một số nút để thao tác cả danh sách và nội dung của nó. ItemSource của danh sách được gán cho một danh sách nhanh gồm một vài người dùng mà chúng ta tạo trong hàm tạo của cửa sổ. Vấn đề là không có nút nào có vẻ hoạt động. Hãy sửa nó, trong hai bước dễ dàng.

Phản ánh những thay đổi trong danh sách dữ liệu nguồn

Bước đầu tiên là để UI phản hồi các thay đổi trong nguồn danh sách (ItemSource), như khi chúng ta thêm hoặc xóa người dùng. Những gì chúng ta cần là một danh sách thông báo bất kỳ đích thay đổi nào cho nội dung của nó và may mắn thay, WPF cung cấp một loại danh sách sẽ làm việc đó. Nó được gọi là ObservableCollection và bạn sử dụng nó giống như một List<T>, chỉ có một vài khác biệt.

Trong ví dụ cuối cùng, mà bạn sẽ tìm thấy bên dưới, chúng tôi chỉ đơn giản là thay thế List<User> bằng ObservableCollection<User> - đó là tất cả những gì bạn cần! Điều này sẽ làm cho nút Thêm và Xóa hoạt động, nhưng nó sẽ không làm gì cho nút "Change name", vì thay đổi sẽ xảy ra trên chính đối tượng dữ liệu bị ràng buộc chứ không phải danh sách nguồn - mặc dù bước thứ hai sẽ xử lý tình huống đó.

Phản ánh những thay đổi trong các đối tượng dữ liệu

Bước thứ hai là để cho lớp Người dùng tùy chỉnh khi chúng tôi triển khai giao diện INotifyPropertyChanged. Bằng cách đó, các đối tượng Người dùng của chúng tôi có khả năng cảnh báo lớp UI thay đổi đối với các thuộc tính của nó. Điều này hơi phức tạp hơn một chút so với việc chỉ thay đổi loại danh sách, như chúng tôi đã làm ở trên, nhưng đây vẫn là một trong những cách đơn giản nhất để thực hiện các cập nhật tự động này.

The final and working example

Với hai thay đổi được mô tả ở trên, giờ đây chúng ta có một ví dụ SẼ phản ánh các thay đổi trong nguồn dữ liệu. Nó trông như thế này:

<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));
		}
	}
}

Summary

As you can see, implementing INotifyPropertyChanged is pretty easy, but it does create a bit of extra code on your classes, and adds a bit of extra logic to your properties. This is the price you will have to pay if you want to bind to your own classes and have the changes reflected in the UI immediately. Obviously you only have to call NotifyPropertyChanged in the setter's of the properties that you bind to - the rest can remain the way they are.

The ObservableCollection on the other hand is very easy to deal with - it simply requires you to use this specific list type in those situations where you want changes to the source list reflected in a binding destination.

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!