Предыдущая лекция |   | Следующая лекция :----------------:|:----------:|:----------------: [Ресурсы](./articles/wpf_resource.md) | [Содержание](../readme.md#тема-8-оконные-приложения) | [Элементы управления](./t8_elements.md) # Привязка (Binding). Интерфейс INotifyPropertyChanged. Форматирование значений привязки и конвертеры значений. ## Введение в привязку данных В WPF привязка (**binding**) является мощным инструментом программирования, без которого не обходится ни одно серьёзное приложение. Привязка подразумевает взаимодействие двух объектов: источника и приемника. Объект-приемник создает привязку к определенному свойству объекта-источника. В случае модификации объекта-источника, объект-приемник также будет модифицирован. Например, простейшая форма с использованием привязки: ```xml ``` ![](../img/08028.png) Для определения привязки используется выражение типа: ``` {Binding ElementName=Имя_объекта-источника, Path=Свойство_объекта-источника} ``` То есть в данном случае у нас элемент **TextBox** (поле ввода) является источником, а **TextBlock** (простой текст) - приемником привязки. Свойство *Text* элемента **TextBlock** привязывается к свойству *Text* элемента **TextBox**. В итоге при осуществлении ввода в текстовое поле синхронно будут происходить изменения в текстовом блоке. ### Работа с привязкой в C# Ключевым объектом при создании привязки является объект **System.Windows.Data.Binding**. Используя этот объект мы можем получить уже имеющуюся привязку для элемента: ```cs Binding binding = BindingOperations.GetBinding(myTextBlock, TextBlock.TextProperty); ``` В данном случае получаем привязку для свойства зависимостей _TextProperty_ элемента _myTextBlock_. Также можно полностью установить привязку в коде C#: ```cs public MainWindow() { InitializeComponent(); Binding binding = new Binding(); // элемент-источник binding.ElementName = "myTextBox"; // свойство элемента-источника binding.Path = new PropertyPath("Text"); // установка привязки для элемента-приемника myTextBlock.SetBinding(TextBlock.TextProperty, binding); } ``` Если в дальнейшем нам станет не нужна привязка, то мы можем воспользоваться классом **BindingOperations** и его методами **ClearBinding()**(удаляет одну привязку) и **ClearAllBindings()** (удаляет все привязки для данного элемента) ```cs BindingOperations.ClearBinding(myTextBlock, TextBlock.TextProperty); ``` или ```cs BindingOperations.ClearAllBindings(myTextBlock); ``` Некоторые свойства класса **Binding**: * **ElementName**: имя элемента, к которому создается привязка * **IsAsync**: если установлено в **True**, то использует асинхронный режим получения данных из объекта. По умолчанию равно **False** * **Mode**: режим привязки * **Path**: ссылка на свойство объекта, к которому идет привязка * **TargetNullValue**: устанавливает значение по умолчанию, если привязанное свойство источника привязки имеет значение **null** * **RelativeSource**: создает привязку относительно текущего объекта * **Source**: указывает на объект-источник, если он не является элементом управления. * **XPath**: используется вместо свойства path для указания пути к xml-данным ### Режимы привязки Свойство **Mode** объекта **Binding**, которое представляет режим привязки, может принимать следующие значения: * **OneWay**: свойство объекта-приемника изменяется после модификации свойства объекта-источника. * **OneTime**: свойство объекта-приемника устанавливается по свойству объекта-источника только один раз. В дальнейшем изменения в источнике никак не влияют на объект-приемник. * **TwoWay**: оба объекта - применки и источник могут изменять привязанные свойства друг друга. * **OneWayToSource**: объект-приемник, в котором объявлена привязка, меняет объект-источник. * **Default**: по умолчанию (если меняется свойство **TextBox.Text**, то имеет значение **TwoWay**, в остальных случаях **OneWay**). Применение режима привязки: ```xml ``` ### Обновление привязки. UpdateSourceTrigger Односторонняя привязка от источника к приемнику практически мгновенно изменяет свойство приемника. Но если мы используем двустороннюю привязку в случае с текстовыми полями (как в примере выше), то при изменении приемника свойство источника не изменяется мгновенно. Так, в примере выше, чтобы текстовое поле-источник изменилось, нам надо перевести фокус с текстового поля-приемника. И в данном случае в дело вступает свойство *UpdateSourceTrigger* класса **Binding**, которое задает, как будет присходить обновление. Это свойство в качестве принимает одно из значений перечисления **UpdateSourceTrigger**: * **PropertyChanged**: источник привязки обновляется сразу после обновления свойства в приемнике * **LostFocus**: источник привязки обновляется только после потери фокуса приемником * **Explicit**: источник не обновляется до тех пор, пока не будет вызван метод **BindingExpression.UpdateSource()** * **Default**: значение по умолчанию. Для большинства свойств это значение **PropertyChanged**. А для свойства **Text** элемента **TextBox** это значение **LostFocus** В данном случае речь идет об обновлении источника привязки после изменения приемника в режимах **OneWayToSource** или **TwoWay**. То есть чтобы у нас оба текстовых поля, которые связаны режимом **TwoWay**, моментально обновлялись после изменения одного из них, надо использовать значение **UpdateSourceTrigger.PropertyChanged**: ```xml ``` ### Свойство Source >Модель WPF предлагает очень удобный функционал: возможность хранить данные как ресурс, локально для элемента управления, локально для всего окна либо глобально для всего приложения. Данные могут быть любыми по факту, начиная от текущей информации, заканчивая иерархией элементов WPF. Это позволяет разместить данные в одном месте и после этого использовать их в разных местах, что может пригодится при разработке. > >Этот функционал часто используется для работы со стилями и шаблонами, которые мы еще будем обсуждать в руководстве, но, как будет показано в этой главе - область применения ресурсов очень широкая. > >Ресурсы в WPF имеют ключ (атрибут `x:Key`), с помощью которого становится возможным сослаться на эти ресурсы из любой другой части приложения, используя ключ с выражением разметки _StaticResource_. В этом примере я просто сохранил строку в ресурсах, которую позже использовал в двух разных элементах **TextBlock**. Свойство **Source** позволяет установить привязку даже к тем объектам, которые не являются элементами управления WPF. Например, определим класс **Phone**: ```cs class Phone { public string Title { get; set; } public string Company { get; set; } public int Price { get; set; } } ``` Теперь создадим объект этого класса и определим к нему привязку: ```xml ``` В примере выше ресурсы расположены на уровне окна (Window), так, что будем в состоянии их использовать с любого места в окне. Если Вы нуждаетесь в ресурсе лишь для одного выбранного элемента - можете сделать это локально, путем добавления ресурса к элементу управления, а не всему окну. Это работает так же, как и ресурсы для окна, разница состоит в том, можно ли будет "достучаться" до них с уровня элемента, в котором вы сохранили ресурс. ```xml Items: ``` В этом случае, мы добавили ресурс в **StackPanel** и, после, использовали его с уровня дочернего элемента - **Label**. Другие элементы внутри **StackPanel** также смогут его использовать, как и дочерние элементы **Label**. А вот элементы вне **StackPanel** не будут иметь доступа к введенным ресурсам. Если Вам необходимо иметь доступ к ресурсу с разных окон - это тоже возможно. Файл `App.xaml` может содержать ресурсы так же как и окна (и другие типы элементов). Но когда Вы храните ресурсы в этом файле, то они становятся глобально доступными во всех окнах и UserControl'ах в проекте. Это работает также как и при хранении ресурсов в Window: ```xml Items: ``` ### Свойство TargetNullValue На случай, если свойство в источнике привязки вдруг имеет значение **null**, то есть оно не установлено, мы можем задать некоторое значение по умолчанию. Например: ```xml ``` В данном случае у ресурса *nexusPhone* не установлено свойство **Title**, поэтому текстовый блок будет выводить значение по умолчанию, указанное в параметре **TargetNullValue**. ### Свойство RelativeSource Свойство **RelativeSource** позволяет установить привязку относительно элемента-источника, который связан какими-нибудь отношениями с элементом-приемником. Например, элемент-источник может быть одним из внешних контейнеров для элемента-приемника. Либо источником и приемником может быть один и тот же элемент. Для установки этого свойства используется одноименный объект **RelativeSource**. У этого объекта есть свойство **Mode**, которое задает способ привязки. Оно принимает одно из значений перечисления **RelativeSourceMode**: * **Self**: привязка осуществляется к свойству этого же элемента. То есть элемент-источник привязки в то же время является и приемником привязки. * **FindAncestor**: привязка осуществляется к свойству элемента-контейнера. Например, совместим источник и приемник привязке в самом элементе: ```xml ``` ![](../img/08029.png) Здесь текст и фоновый цвет текстового поля связаны двусторонней привязкой. В итоге мы можем увидеть в поле числовое значение цвета, поменять его, и вместе с ним изменится и фон поля. Привязка к свойствам контейнера: ```xml ``` При использовании режима **FindAncestor**, то есть привязке к контейнеру, необходимо еще указывать параметр **AncestorType** и передавать ему тип контейнера в виде выражения `AncestorType={x:Type Тип_элемента-контейнера}`. При этом в качестве контейнера мы могли бы выбрать любой контейнер в дереве элементов, в частности, в данном случае кроме **Grid** таким контейнером также является элемент **Window**. ### Свойство DataContext У объекта **FrameworkElement**, от которого наследуются элементы управления, есть интересное свойство **DataContext**. Оно позволяет задавать для элемента и вложенных в него элементов некоторый контекст данных. Тогда вложенные элементы могут использовать объект **Binding** для привязки к конкретным свойствам этого контекста. Например, используем ранее определенный класс **Phone** и создадим контекст данных из объекта этого класса: Для WPF этого достаточно, а для Avalonia ещё нужно добавить тип данных: `x:DataType="local:Phone"` ```xml ``` Таким образом мы задаем свойству **DataContext** некоторый динамический или статический ресурс. Затем осуществляем привязку к этому ресурсу. ## Интерфейс INotifyPropertyChanged. Выше использовался объект **Phone** для привязки к текстовым блокам. Однако если мы изменим его, содержимое текстовых блоков не изменится. Например, добавим в окно приложения кнопку: ```xml