This article has been localized into Polish by the community.
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.