TOC

This article has been localized into Polish by the community.

Powiązanie danych:

Reagowanie na zmiany

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

Reagowanie na zmiany w danych źródłowych

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 rozwiązań

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

Ukazanie zmian na liście danych źródłowych

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 prostą 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ż jego zmiana jest dokonywana wewnątrz obiektu a nie na poziomie listy obiektów. W następnym kroku zajmiemy się tym.

Ukazanie zmian dokonanych we wnętrzu danej źródłowej

Drugim krokiem jest pozwolenie, aby nasza klasa User zaimplementowała interfejs INotifyPropertyChanged. W ten sposób nasze obiekty typu User są w stanie powiadomić warstwę interfejsu użytkownika o zmianach dokonanych w ich właściwościach. Jest to nieco bardziej kłopotliwe niż tylko zmiana typu kolekcji, jak to zrobiliśmy powyżej, ale jest to nadal jeden z najprostszych sposobów aby osiągnąć automatyczne aktualizacje.

Ostateczny przykład reagujący na wszelkie zmiany w danych źródłowych

Wprowadzając dwie opisane wyżej zmiany otrzymaliśmy przykład, który będzie reagował na wszelkie zmiany dokonywane w danych źródłowych. Wygląda on następująco:

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

Podsumowanie

Jak widać, implementacja interfejsu INotifyPropertyChanged jest dość prosta, ale tworzy trochę dodatkowego kodu w definicji klasy i dodaje trochę więcej logiki w jej właściwościach. Jest to cena, którą będziesz musiał zapłacić, jeśli chcesz powiązać dane z własnymi klasami i mieć wprowadzane zmiany automatycznie odzwierciedlane w interfejsie użytkownika. Oczywiście wystarczy tylko wywołać INotifyPropertyChanged w seterze właściwości, które chcesz powiązać - reszta może pozostać taka, jaka jest.

Z drugiej strony kolekcja "ObservableCollection" jest bardzo łatwa w obsłudze - po prostu wymaga użycia tego konkretnego typu kolekcji w tych sytuacjach, w których chcesz aby zmiany dokonane w kolekcji obiektów były automatycznie odzwierciedlane w powiązanym z nimi miejscu.


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!