Передать на печать

Разработка универсальных приложений Windows 10. Привязка данных. Часть 4

Четвёртая часть статьи, посвящённой привязке данных и её использованию при разработке универсальных приложений Windows 10.

3.4. Реализация оповещений

Если мы сейчас попробуем запустить приложение на выполнение, введём какое-либо число в поле Сантиметры и перенесём фокус ввода, то с удивлением обнаружим, что в поле ввода Дюймы ничего не появилось. И это притом, что, выполнив трассировку кода класса CIData, то заметим, что значение, занесённое в поле ввода Сантиметры, успешно переносится в поле __centimetres этого класса (посредством функции-сеттера свойства Centimetres).

По идее, поле ввода Дюймы должно обращаться к свойству Inches каждый раз, когда значение поля __centimetres изменится. Но не делает этого.

Почему? Потому что наш класс CIData не поддерживает оповещений.

Оповещение в терминологии UWP - это особое событие, которое должно поддерживаться классом-источником. Это событие должно возникать всякий раз, когда состояние источника изменяется (например, когда в свойство источника заносится новое значение, как в нашем случае). Приёмник привязывает к этому событию обработчик и, таким образом, всегда остаётся "в курсе", что состояние источника изменилось, и ему следует повторно обратиться к связанному свойству, дабы извлечь новое значение.

Событие, о котором только что шла речь, должно иметь имя PropertyChanged и относиться к типу PropertyChangedEventHandler. Его обработчики принимают в качестве второго аргумента объект класса PropertyChangedEventArgs. (Первым аргументом, как обычно, указывается объект, в котором возникло данное событие.) В конструкторе этого класса единственным аргументом ставится строковое имя свойства, значение которого изменилось; также можно указать пустую строку (String.Empty) - в этом случае подразумевается, что изменились значения всех свойств.

И, наконец, класс-источник должен реализовывать интерфейс INotifyPropertyChanged. Он-то и содержит вышеупомянутое событие PropertyChanged (других элементов в нём нет).

Все эти типы объявлены в пространстве имён System.ComponentModel. Поэтому в начало функционального кода следует добавить выражение, выполняющее присоединение этого пространства имён.

Давайте перепишем класс CIData таким образом, чтобы он получил поддержку оповещений. Обновлённый код этого класса приведён ниже.

using System.ComponentModel;

. . .

public class CIData : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private double __centimetres = 0;

    public string Centimetres
    {
        get
        {
            return this.__centimetres.ToString();
        }
        set
        {
            double fl = 0;
            if (double.TryParse(value, out fl))
            {
                if (this.__centimetres != fl)
                {
                    this.__centimetres = fl;
                    this.RaisePropertyChanged("Inches");
                }
            }
        }
    }

    public string Inches
    {
        get {
            return (this.__centimetres / 2.54).ToString();
        }
    }

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



Здесь всё довольно просто. Мы объявляем защищённый метод RaisePropertyChanged, который в качестве единственного параметра принимает строковое имя свойства, значение которого изменилось, проверяет, был ли к событию PropertyChanged привязан хотя бы один обработчик, и, если это так, запускает выполнение обработчиков данного события. Таковым мы передаём два аргумента: текущий объект (ведь именно в нём возникло событие) и объект класса PropertyChangedEventArgs, хранящий имя изменившегося свойства. Наконец, при занесении нового значения в поле __centimetres, в коде сеттера свойства Centimetres, мы вызываем метод RaisePropertyChanged, передав ему имя свойства Inches.

Также не забудем вставить в начало кода выражение, выполняющее присоединение пространства имён System.ComponentModel, - иначе получим ошибку компиляции.

Теперь мы можем, наконец, проверить наше полностью готовое приложение в действии. Сохраним проект, запустим его на выполнение, введём в поле ввода Сантиметры какое-то число и переведём фокус ввода на поле Дюймы. Мы сразу увидим в последнем ту же величину, но выраженную в дюймах.


3.5. Управление режимом переноса значения

Ранее говорилось, что, если в приёмнике связанным свойством является Text, перенос значения в "обратном" направлении выполняется только после потери элементом управления фокуса ввода. Мы уже заметили это: в нашем приложении вычисление и вывод величины в дюймах производится только тогда, когда поле ввода Сантиметры теряет фокус ввода. А это довольно неудобно, так как вынуждает пользователя выполнять лишний щелчок мышью или лишнее нажатие клавиши <Tab>.

Однако мы можем сделать так, чтобы "обратный" перенос значения выполнялся в определённый нами самими момент времени. Например, сразу после изменения значения в поле ввода.

Режимом переноса значения управляет свойство UpdateSourceTrigger класса Binding. В качестве значения оно принимает один из элементов перечисления UpdateSourceTrigger:

  • Default - режим по умолчанию (перенос выполняется сразу после изменения значения свойства; в случае свойства Text - после потери элементом управления фокуса ввода);
  • PropertyChanged - то же самое, что и Default;
  • Explicit - перенос выполняется явно и инициируется в функциональном C#-коде.


Отметим сразу, что режим переноса значения имеет смысл выставлять только в случае двунаправленной привязки. У привязок прочих разновидностей этот параметр не имеет смысла.

Итак, первое, что мы сделаем, - зададим свойству UpdateSourceTrigger привязки, заданной для поля ввода txtCentimetres, значение Explicit.

Если мы создавали привязку первым способом, то сможем указать нужный режим в раскрывающемся списке UpdateSourceTrigger окна Создание привязки данных (см. рис. 11). Если же привязка создавалась вторым способом, нам придётся ввести соответствующий XAML-код вручную:

<TextBox x:Name="txtCentimetres" . . . Text="{Binding . . . UpdateSourceTrigger=Explicit}"/>



Чтобы программно инициировать перенос значения, нужно выполнить следующие действия:

  • вызвать у приёмника метод GetBindingExpression. Его единственным аргументом должно быть обозначение свойства зависимости приёмника, к которому была выполнена привязка; в качестве результата метод вернёт объект класса BindingExpression, который представит описание соответствующей привязки;
  • у полученного ранее объекта класса BindingExpression вызвать не принимающий аргументов и не возвращающий результата метод UpdateSource, который запустит перенос значения.


Нам нужно сделать так, чтобы результат вычисления обновлялся сразу после изменения значения, занесённого в поле ввода Сантиметры. Следовательно, инициирование переноса мы выполним в коде обработчика события TextChanged класса TextBox.

Выберем поле ввода txtCentimetres. Обратимся к панели Свойства, найдём расположенную правее поля ввода Имя кнопку со значком молнии и щёлкнем на ней. В панели вместо свойств появится список событий, поддерживаемых полем ввода. Найдём событие TextChanged и дважды щёлкнем на поле ввода, что находится правее имени этого события. Visual Studio сгенерирует объявление метода страницы - обработчика вышеупомянутого события.

Вот полный код этого метода-обработчика:

private void txtCentimetres_TextChanged(object sender, TextChangedEventArgs e)
{
    (sender as TextBox).GetBindingExpression(TextBox.TextProperty).UpdateSource();
}



Каждый метод - обработчик события в качестве первого параметра получает объект, в котором возникло это событие (источник события). В нашем случае значением этого параметра будет поле ввода txtCentimetres; этим мы и воспользуемся, чтобы получить к нему доступ (только предварительно приведём его к типу TextBox). Остальной код не требует пояснений.

Чтобы вернуться к списку свойств в панели Свойства, следует нажать кнопку со значком гаечного ключа - она находится правее поля ввода Имя.

На этом всё. Мы можем сохранить проект и запустить приложение на выполнение. Теперь преобразование введённой величины в дюймы будет выполняться непосредственно в процессе её ввода. Что очень удобно!


3.6. Необъектная привязка

Ещё в начале этой главы мы узнали, что каждая привязка, что мы создали в приложении, является объектом класса Binding, предоставляемого самой платформой. Такой объект создаётся при запуске приложения, отслеживает изменение значения в свойстве источника (и приёмника, если была создана двунаправленная привязка) и выполняет перенос значения. Поэтому такая привязка носит название объектной.

Особо отметим тот факт, что при создании такой привязки в исполняемый файл приложения не добавляется никакого дополнительного кода. Объектная привязка, как уже говорилось, полностью создаётся во время выполнения.

Однако платформа UWP поддерживает ещё и так называемую необъектную привязку. Во многих случаях её использование может оказаться выгоднее.


3.6.1. Необъектная привязка: описание, преимущества и недостатки

Необъектная привязка, как ясно из названия, не является объектом класса Binding. Для каждой такой привязки компилятор Visual Studio создаёт в исполняемом файле программный код, который и выполняет все задачи по отслеживанию изменения значения и его переносу. Другими словами, такая привязка полностью создаётся во время компиляции приложения.

В необъектной привязке в качестве источника всегда выступает объект текущей страницы. Это нужно иметь в виду.

Необъектная привязка поддерживает большую часть возможностей объектной, в частности, указание направления и использование преобразователей значений. Однако управление режимом переноса значения не поддерживается.

Для создания необъектной привязки применяется объектное синтаксическое расширение x:Bind. Оно имеет практически такой же синтаксис, что и знакомое нам расширение Binding.

Необъектная привязка имеет ряд преимуществ при объектной:

  • поскольку при запуске приложения нет нужды создавать какие-либо дополнительные объекты, такая привязка требует существенно меньше системных ресурсов;
  • приложение быстрее запускается - по той же самой причине;
  • лучший контроль типов - поскольку привязка создаётся на этапе компиляции.


Однако имеются и недостатки:

  • невозможно задать произвольный источник привязки (таковым всегда является страница);
  • невозможно управлять режимом переноса значения - поскольку такая привязка не является объектом, мы не можем программно инициировать перенос вызовом методов этого объекта;
  • если выполняется привязка к какому-либо объекту, являющемуся значением свойства источника, и точный класс этого объекта становится известным только на этапе выполнения, необъектная привязка вообще не может быть создана, - поскольку компилятор просто "не знает" класс объекта (с такой ситуацией мы столкнёмся позже, когда будет заниматься привязкой списков);
  • Visual Studio не предоставляет никаких визуальных инструментов для создания необъектных привязок, и необходимый XAML-код приходится вводить вручную.


Тем не менее, в большинстве случаев имеет смысл создавать необъектную привязку, поскольку она не требует времени на инициализацию при запуске приложения и не отнимает дополнительных системных ресурсов. И только в том случае, если её возможностей не хватает для реализации логики приложения, следует задействовать объектную привязку.


3.6.2. Создание необъектной привязки

Создадим ещё один проект под именем Bindings3. У начальной страницы нового приложения сделаем тот же интерфейс, что и у предыдущих двух приложений, опять же, пока что без всяких привязок.

Добавим в проект новый файл программного кода с именем CIData.cs. Напишем в нём объявление класса CIData - то же, что записали при разработке предыдущей редакции приложения.

Поскольку у необъектной привязки источником всегда выступает объект страницы, нам следует сделать так, чтобы объект класса CIData был доступен через свойство класса страницы. И, разумеется, написать код, который создаст объект класса CIData и сохранит его в этом свойстве.

Откроем файл функционального кода страницы MainPage.xaml.cs и дополним код объявления её класса, чтобы он выглядел следующим образом:

public sealed partial class MainPage : Page
{
    public CIData CI { get; set; }

    public MainPage()
    {
        this.InitializeComponent();
        this.CI = new CIData();
    }
}



Здесь всё понятно без комментариев.

Переключимся на интерфейсный код страницы. Создадим у поля ввода txtCentimetres необъектную привязку свойства Text к свойству Centimetres объекта класса CIData, что хранится в свойстве CI страницы. Для этого следует дополнить XAML-код, создающий это поле ввода, таким фрагментом:

<TextBox x:Name="txtCentimetres" . . . Text="{x:Bind CI.Centimetres, Mode=TwoWay}"/>



Как видим, этот код практически аналогичен тому, что формирует объектную привязку.

Точно таким же образом создадим привязку свойства Text второго поля ввода к свойству Inches объекта класса CIData, хранящегося в свойстве CI страницы. Это будет однонаправленная привязка.

Сохраним проект, запустим приложение на выполнение, введём какое-либо число в поле Сантиметры и перенесём фокус ввода на поле Дюймы. Если мы всё сделали правильно, там появится величина, выраженная в дюймах.

Кстати, автор этой статьи заметил, что даже будучи запущенным в режиме отладки, из среды Visual Studio, приложение Bindings3 стартует несколько быстрее, чем Bindings2.

Дополнительные материалы



Продолжение следует...

Владимир Дронов, MSInsider.ru Team
Май 2017

  Передать на печать





Все права принадлежат © MSInsider.ru и TheVista.ru, 2013
Сайт является источником уникальной информации о семействе операционных систем Windows и других продуктах Microsoft. Перепечатка материалов возможна только с разрешения редакции.
Работает на WMS 1.1 (Страница создана за 0.087 секунд)