TOC

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

Powiązanie danych:

Responding to changes

Dotychczas w tym poradniku, zazwyczaj tworzyliśmy wiązania między interfejsem użytkownika(UI) i istniejącymi klasami ale zazwyczaj w aplikacjach będziesz tworzył wiązania do własnych obiektów. To bardzo proste ale zanim zaczniesz je tworzyć jest coś co niestety Cię rozczaruje. Poczynione zmiany nie są automatycznie uwzględniane jak w poprzednich przykładach. W tym artykule dowiesz się jak zrobić aby działały tak samo jak w poprzednich przykładach. Na szczęście WPF sprawia że jest to całkiem proste.

Responding to data source changes

Są dwa różne scenariusze, którymi możesz się spotkać w przypadku gdy dane źródłowe się zmieniają. Zmiany na liście obiektów oraz zmiany na liście powiązań właściwości w każdym z tych obiektów. Jak je kontrolować aby sposób postępowania był zależny od tego co robisz i co chcesz osiągnąć? WPF zawiera dwa proste rozwiązania a mianowicie interfejsy ObservableCollection i INotifyPropertyChanged

Poniższy przykład pokaże Ci dlaczego powinniśmy używać tych dwóch interfejsów

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

Spróbuj uruchomić powyższy kod i zaobserwuj że nawet jeżeli dodasz coś do listy albo zmienisz nazwę jednego z użytkowników na liście interfejs użytkownika pozostanie bez zmian Przykład jest bardzo prosty: Klasa User przechowuje imiona i nazwiska użytkownika, Listbox pokazuje je a przyciski manipulują listą i jej zawartością. ItemsSource listy jest przydzielony do listy kilku użytkowników którą stworzyliśmy w konstruktorze okna. Problem polega na tym że żaden z przycisków nie działa. Naprawmy to w dwóch prostych krokach.

Odbicie zmian w liście źródła danych

Pierwszym krokiem jest zapewnienie interfejsowi użytkownika odpowiedzi na zmiany w liście z danymi na przykład kiedy dodajemy albo usuwamy użytkownika. Potrzebujemy aby lista odnotowywała jakiekolwiek zmiany w zawartości. WPF zapewnia rodzaj listy, który to potrafi. Nazywa się ObservableCollection i możesz go używać tak jak zwykłą List<T> ale z kilkoma różnicami.

W ostatnim przykładzie, który znajdziesz poniżej, znajdziemy proste zamianę List<User> na ObservableCollection<User> - to wszystko. To sprawi że przycisk Add i Delete będą działały ale przycisk "Change name" nie będzie działał ponieważ zmiana jest na poziomie przypisania obiektu a nie listy. W następnym kroku zajmiemy się tym.

Reflecting changes in the data objects

The second step is to let our custom User class implement the INotifyPropertyChanged interface. By doing that, our User objects are capable of alerting the UI layer of changes to its properties. This is a bit more cumbersome than just changing the list type, like we did above, but it's still one of the simplest way to accomplish these automatic updates.

The final and working example

With the two changes described above, we now have an example that WILL reflect changes in the data source. It looks like this:

<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!