Компилируемые прив'язки даних в додатках Windows 10


Одним з нововведень Windows UAP є те, що стало можливим створювати биндинги які будуть скомпільовані. Таке нововведення значно покращує продуктивність (в тому числі і швидкість завантаження) програми. Раніше прив'язки даних були засновані на рефлексії, а тому повільні. Плюс до всього, стало набагато зручніше проводити налагодження коду компилируемых біндінгів.

Для того, щоб створити биндинг, який буде сформований при компіляції програми необхідно використовувати {x:Bind} замість {Binding}.
Прив'язку можна робити як до code-behind класу, так і до інших елементів керування сторінки.
Наприклад:

<Grid x:Name="grd" Tag="Приклад биндинга">
<TextBlock FontSize="40" Text="{x:Bind grd.Tag}" />
</Grid>

Якщо значенням {x:Bind} вказати просто Tag, то прив'язка буде відбуватися до атрибуту Tag об'єкта Page.

Компилируемые биндинги є суворо типізований (можна прив'язати лише до об'єктів з конкретним типом) і перевіряються під час компіляції, а значить, якщо десь буде виявлена помилка, то компіляція буде перервана.
Аналогічно биндингам WPF можливі різні режими прив'язки: OneTime, OneWay і TwoWay. Режимом за замовчуванням є OneTime.
Порівняння продуктивності скомпільованих біндінгів і звичайних ви можете побачити на наступному графіку:



Для того щоб контрол вікна програми змінювався при змінах у прив'язаному об'єкті, підтримуються і давно відомі розробникам інтерфейси: INotifyPropertyChanged, IObservableVector, INotifyCollectionChanged.

Розглянемо простий приклад використання x:Bind
Створимо простий клас Employee:

public class Employee 
{
private string _name;
private string _organization;
private int? _age;

public string Name
{
get { return _name; }
set { _name = value;}
}

public string Organization
{
get { return _organization; }
set { _organization = value; }
}

public int? Age
{
get { return _age; }
set { _age = value; }
}
}

В клас нашої сторінки (за замовчуванням це MainPage) додамо простір імен:

using System.Collections.ObjectModel;

Воно потрібне нам для використання ObservableCollection. Застосуємо саме колекцію ObservableCollection так як вона містить реалізацію INotifyCollectionChanged, а значить, кожен раз при додаванні і видаленні елементів колекції буде оновлено і елемент керування до якого прив'язані дані колекції.
Оголосимо колекцію:

ObservableCollection<Employee> manager = new ObservableCollection<Employee>();

Додамо кілька елементів в колекцію. Зробимо це після ініціалізації сторінки:

public MainPage()
{
this.InitializeComponent();

manager.Add(new Employee { Age = 45, Name = "Ольга", Organization = "ТОВ Роги і Копита" });
manager.Add(new Employee { Age = 25, Name = "Тетяна", Organization = "ВАТ Шаркон" });
manager.Add(new Employee { Age = 22, Name = "Ганна", Organization = "ТОВ Роги і Копита" });

}

Тепер ми можемо в XAML код нашої сторінки використовувати наступну конструкцію:

<ListView ItemsSource="{x:Bind manager}" Width="200" Height="250"
BorderThickness="1" BorderBrush="Black">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Employee">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Тут під local:Employee використовується посилання на простір імен з заголовка сторінки:

<Page
x:Class="CompiledBindingsDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CompiledBindingsDemo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

В результаті отримаємо:



Значення {x:Bind} можуть бути наступні: Converter, ConverterLanguage, ConverterParameter, FallbackValue, Mode, Path, TargetNullValue
На відміну від прив'язок даних за допомогою {Binding}, не використовуються наступні значення: Source, ElementName, RelativeSource, UpdateSourceTrigger
Вони стали не потрібні і замінено іншим функціоналом. Скажімо, RelativeSource замінюється ім'ям елемента і його атрибутом (дивіться перший приклад), то що реалізувалося за допомогою UpdateSourceTrigger тепер можна зробити в PropertyChanged.

Ще однією новою чудовою можливістю є атрибут x:Phase, який дозволяє виробляти прогресивне завантаження елементів.
Розглянемо на нашому прикладі:

<DataTemplate x:DataType="local:Employee">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Name}" x:Phase="1" />
<TextBlock Text="{x:Bind Organisation}" x:Phase="3" />
<TextBlock Text="{x:Bind Age}" x:Phase="2" />
</StackPanel>
</DataTemplate>

В даному випадку черговість завантаження/промальовування елементів задана примусово. Першим буде завантажений перший TextBlock з ім'ям, другим третій з віком і останнім буде завантажений текст з назвою організації. У даному випадку порядок не особливо істотне, але іноді, особливо при використанні медіа даних він буває важливий. Якщо вказати значення x:Phase=«0», то це буде означати що цього елементу значення порядку завантаження не задано.

Для ініціалізації прив'язок даних Bindings.Initialize(), але примусова ініціалізація не потрібно, так як вона сама відбувається під час завантаження сторінки.
Можливий виклик Bindings.Update() для оновлення асинхронних даних. Цей же метод можна використовувати для прив'язок типу OneTime, які споживають найменше ресурсів.
Для того щоб поставити прив'язку «на паузу» і не відслідковувати зміни даних можна викликати Bindings.StopTracking(). А для того, щоб продовжити відстеження викликається Update().

Крім того, тепер стало можливим використовувати прив'язки для подій. Стандартно подія кліка оголошується так:

<Button Click="PokeEmployee">Poke Employee</Button>

Можна оголосити подію, що знаходиться всередині класу, з допомогою такої ось прив'язки події:

<Button Click="{x:Bind SomeDataClass.Poke}">Poke Employee</Button>

Англо-російський словник на слово Poke одним із значень видає «запис елемента даних». А значить, судячи з усього, основним призначенням прив'язок подій є імперативне зміна даних.
Модифікуємо наш приклад:

<DataTemplate x:DataType="local:Employee">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Name}" x:Phase="1" />
<TextBlock Text="{x:Bind Organization}" x:Phase="3" />
<TextBlock Text="{x:Bind Age,Mode=OneWay}" x:Phase="2" />
<Button Click="{x:Bind Poke}" Content="Додати рік"/>
</StackPanel>
</DataTemplate>

І в наш клас Employee додамо:

public void Poke()
{
this.Age = this.Age+1;
}

Але от невдача, при налагодженні помітно, що значення Age збільшується, як годиться, а ось інтерфейс не оновлюється. Це тому, що колекція ObservableCollection оновлює свою прив'язку тільки при додаванні або видаленні елементів. Для того щоб оновлення відбувалося і при зміні даних необхідно реалізувати інтерфейс INotifyPropertyChanged. Робиться це так само, як і робилося раніше. Додаванням простору імен:

using System.ComponentModel;

Оголошенням інтерфейсу:

public class Employee : INotifyPropertyChanged

І реалізацією події зміни властивості:

public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}

Тепер можна викликати це подія в сеттере після установки значення Age:

public int? Age
{
get { return _age; }
set { _age = value;
RaisePropertyChanged("Age");
}
}

Весь код класу:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ComponentModel; 

namespace CompiledBindingsDemo
{

public class Employee : INotifyPropertyChanged
{
private string _name;
private string _organization;
private int? _age;

public string Name
{
get { return _name; }
set { _name = value;}
}

public string Organization
{
get { return _organization; }
set { _organization = value; }
}

public int? Age
{
get { return _age; }
set { _age = value;
RaisePropertyChanged("Age");
}
}

public void Poke()
{
this.Age = this.Age+1;
}

public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}

}

}


Прив'язане подія може бути викликано як без параметрів void Poke(),
так і з параметрами void Poke(object sender, RoutedEventArgs e)
або з параметрами базового типу події void Poke(object sender, object e)
Перевантаження не підтримуються.
Зрозуміло, що назва методу не обов'язково має бути Poke.
Події {x:Bind} можуть замінити такі події MVVM як ICommand або EventToCommand, але такий параметр, як CanExecute в них не підтримується.

Компільовані прив'язки підтримують старі добрі конвертери. Тобто можна створити клас, який реалізує інтерфейс IValueConverter і в ньому здійснити перетворення.

Клас може виглядати наступним чином:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

Windows using.UI.Xaml.Data;

namespace CompiledBindingsDemo
{
class ConverterExample : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null) return string.Empty;

// якісь перетворення зі значенням value
string новий текст = value.ToString();
новий текст = новий текст.ToUpper();

return новий текст;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
// використовується рідко
throw new NotImplementedException();
}
}
}


Якщо у нас в додатку є такий клас, то ми можемо додати його в ресурси XAML сторінки:

<Page.Resources>
<local:ConverterExample x:Name="ThatsMyConverter"/>
</Page.Resources>

Тепер ми можемо використовувати конвертер при прив'язці даних:

<TextBlock Text="{x:Bind Name,Converter={StaticResource ThatsMyConverter}}" />

І тоді регістр тексту, який міститься в Name, при відображенні буде змінено конвертером на верхній (великі літери).

Компилируемые биндинги підходять не для всіх ситуацій. У деяких краще використовувати класичні прив'язки даних за допомогою {Binding} замість {x:Bind}
{Binding} може працювати разом з JSON або якимось іншим нетипизированным словником об'єктів. {x:Bind} не працює без інформації про конкретний тип даних.
Duck Typing (якщо що-то ходить як качка, крякає як качка, то будемо ставитися до цього як до качці) – однакові імена властивостей різних об'єктів відмінно працюють з {Binding}, але не з {x:Bind}. наприклад Text="{Binding Age}" буде працювати з класом Person і з класом Wine. Для використання x:Bind доведеться створити базовий клас або інтерфейс.
Програмне створення зв'язків можливе лише з {Binding}. При використанні {x:Bind} немає можливості додати або видалити прив'язки в runtime.
{x:Bind} не може бути використаний в стилі для сеттерів, але зате він може бути використаний в шаблоні DataTemplate (як це розглядалося вище).

Приклад ви можете знайти на GitHub

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.