TOC

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 е много лесен за работа - той просто изисква да използвате този конкретен тип списък в онези ситуации, в които искате промените в изходния списък да бъдат отразени в обвързаната дестинация.


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!