This article is currently in the process of being translated into Bulgarian (~98% done).
Responsiveness при промени
Досега в това ръководство ние най-вече създавахме обвързвания между елементи на потребителския интерфейс (UI) и съществуващи класове, но в реални приложения очевидно ще се обвързвате към вашите собствени обекти с данни. Това е също толкова лесно, но след като започнете да го правите, може да откриете нещо, което да ви разочарова: промените не се отразяват автоматично, както беше в предишните примери. Както ще научите в тази статия, има нужда от малко допълнителна работа, за да се случи това, и за щастие WPF го прави достатъчно просто.
Отразяване на промени в данните подадени чрез кода
Има два различни сценария, с които можете да искате или да не искате да се справите, когато работите с промени в данните подадени чрез код: промени в списъка с елементи и промени в обвързаните свойства във всеки от обектите в данните. Как ще се справите с тях зависи от това какво правите и какво искате да постигнете, но WPF идва с две много лесни решения, които можете да използвате: класа ObservableCollection и интерфейса INotifyPropertyChanged.
Следният пример ще ви покаже защо имаме нужда и от двете неща:
<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; }
}
}
Опитайте да го стартирате сами и вижте как, въпреки че добавите нещо към списъка или промените името на един от потребителите, нищо в потребителския интерфейс не се актуализира. Примерът е доста прост, с потребителски клас, който ще запази името на потребителя, ListBox, в който да ги покаже, и някои бутони за манипулиране както на списъка, така и на неговото съдържание. ItemsSource на списъка се присвоява към кратък списък от няколко потребители, които създаваме в конструктора на прозореца. Проблемът е, че никой от бутоните не работи. Нека поправим това в две лесни стъпки.
Отразяване на промени в подадения списък
Първата стъпка е да накарате потребителския интерфейс да реагира на промените в списъка подаден като източник (ItemsSource), например когато добавяме или изтриваме потребител. Това, от което се нуждаем, е списък, който уведомява всички дестинации за промени в съдържанието му и за щастие WPF предоставя точно такъв тип списък. Нарича се ObservableCollection и можете да го ползвате като обикновен List<T>, само с няколко разлики.
В долния пример, просто заменяме List<User> с ObservableCollection<User> - това е всичко, което е необходимо! Това ще накара бутоните Add и Delete да работят, но няма да направи нищо за бутона „Change 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 е доста лесно, но създава малко допълнителен код във вашите класове и добавя малко допълнителна логика към вашите свойства. Това е цената, която ще трябва да платите, ако искате да се свържете със собствените си класове и промените да бъдат отразени незабавно в потребителския интерфейс. Очевидно трябва само да извикате NotifyPropertyChanged в настройката на свойствата, към които се свързвате - останалите могат да останат такива, каквито са.
От друга страна, ObservableCollection е много лесен за работа - той просто изисква да използвате този конкретен тип списък в онези ситуации, в които искате промените в изходния списък да бъдат отразени в обвързаната дестинация.