Основы привязки данных в WPF

Привязка данных в XAML

Совместное использование объектов в файле XAML также может осуществляться через привязку данных. По сути привязка данных (data binding) связывает между собой два свойства разных объектов. Как будет показано позже, привязки данных чаще всего применяются для связывания визуальных элементов страницы с источниками данных; кроме того, они являются важной составляющей реализации популярного архитектурного паттерна MVVM (Model-View-ViewModel) . Привязки также играют важную роль при определении шаблонов отображения объектов данных.

Привязки данных могут использоваться для связывания свойств двух элементов. Элемент Binding , как и StaticResource, обычно выражается в виде расширения разметки, то есть заключается в фигурные скобки. Однако элемент Binding также допускает альтернативное выражение в виде синтаксиса элементов свойств.

Используйте следующий словарь ресурсов в нашем тестовом проекте:

Настройки кистей

Неявный стиль TextBlock не содержит свойства Foreground. Кисть LinearGradientBrush определяется для первого из четырех элементов TextBlock, использующих эту кисть, а последующие элементы TextBlock ссылаются на эту же кисть через привязку:

У привязки данных имеется источник (source) и приемник (target) . Приемником всегда является свойство, для которого устанавливается привязка, а источником - свойство, к которому оно привязывается. Источником приведенных привязок является элемент TextBlock с именем topTxb; приемниками - три элемента TextBlock, совместно использующих свойство Foreground. Два приемника представляют более стандартный способ выражения объекта Binding как расширения разметки XAML:

{Binding ElementName=topTxb, Path=Foreground}

Расширения разметки XAML всегда размещаются в фигурных скобках. В расширении разметки для Binding обычно необходимо задать пару свойств, разделяемых запятыми. Свойство ElementName обозначает имя элемента, для которого задано желаемое свойство; свойство Path предоставляет имя свойства.

При вводе расширений разметки Binding мне всегда хочется заключить значения свойств в кавычки, но это неправильно. Кавычки в выражениях привязки не нужны.

Последний элемент TextBlock демонстрирует выражение Binding в менее распространенном синтаксисе элементов свойств:

В этом синтаксисе кавычки вокруг значений свойств обязательны, т.к. это обычные атрибуты XML. Вы также можете создать объект Binding в коде и назначить его приемному свойству методом SetBinding() , определенным в FrameworkElement. При этом выясняется, что приемник привязки всегда должен быть свойством зависимости.

Свойство Path класса Binding называется так потому, что оно может содержать несколько имен свойств, разделенных точками. Например, попробуйте заменить одно из значений Text в проекте следующим значением:

Text="{Binding ElementName=topTxb, Path=FontFamily.Source}"

Первая часть Path указывает, что нам нужны данные из свойства FontFamily. Свойству задается объект типа FontFamily, содержащий с именем Source, обозначающим название семейства шрифта. Следовательно, в TextBlock будет выведен текст «Arial».

Попробуйте применить следующую конструкцию к любому элементу TextBlock в нашем проекте:

Text="{Binding RelativeSource={RelativeSource Self}, Path=FontSize}"

Здесь расширение разметки RelativeSource находится внутри расширения разметки Binding и используется для ссылки на свойство того элемента, для которого задается привязка.

Аннотация: В данном разделе рассматриваются основные понятия привязки данных в WPF. На ряде примеров демонстрируются базовые аспекты привязки интерфейсных элементов к визуальным и невизуальным объектам WPF. Более серьезные вопросы, такие как привязка интерфейсных элементов WPF к пользовательским объектам и коллекциям типизированных данных, объектам инфраструктуры ADO.NET и шаблонам, будут рассмотрены позднее.

Часть I

Общие положения

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге .

Привязка данных - это механизм извлечения информации из объектов в интерфейсные элементы для отображения и наоборот - заталкивания информации из элементов управления в объекты. Привязка данных позволяет обойтись минимальным кодированием или вообще без него. Это относительно старая методика, но в WPF она получила дальнейшее развитие и имеет свою специфику. Все сложности поддержки привязки берут на себя библиотечные классы и среда исполнения. Привязка охватывает широкий диапазон задач: от подключения простых интерфейсных элементов друг к другу до соединения базы данных с пользовательскими формами взаимодействия с данными.

В привязке данных всегда участвуют две стороны: источник и приемник (целевой элемент) информации. Привязка данных может обеспечивать однонаправленный или двунаправленный обмен данными связанных свойств объектов. Чаще всего применяется однонаправленная привязка, целью которой является извлечение информации из источника и отображение ее на приемнике. Но в некоторых случаях различия между источником и приемником стираются, а иногда даже их роли меняются местами - приемник начинает поставлять данные источнику.

Синтаксис привязки данных, как и в случае с ресурсами, также имеет два варианта: расширения разметки и элементов свойств , но отличается деталями. Ключевым элементом привязки для любого варианта является определение объекта Binding из пространства имен System.Windows.Data . Этот элемент всегда устанавливается на стороне приемника привязки, кроме режима Mode=OneWayToSource . Приемник должен быть производным от класса DependencyObject и привязываемое свойство (целевое свойство) должно быть свойством зависимости . В свойства зависимостей встроена способность посылать или принимать уведемления об изменениях.

К источнику привязки предъявляется гораздо меньше требований. Связываемое свойство источника не обязано быть зависимым свойством. Главное, чтобы источник имел оповещающее событие, указывающее на изменение связываемого свойства. Источником привязки может быть любое открытое свойство, в том числе свойства других элементов управления, объекты среды CLR, элементы XAML, наборы данных ADO.NET, фрагменты XML и т.д. Для правильного применения привязки к сложным объектам данных технология WPF предоставляет два специализированных класса - XmlDataProvider и ObjectDataProvider .

Свойства зависимостей еще называют присоединенными . В версии XAML , предназначенной для WPF , присоединенные свойства работают только в том случае, если и тип, в котором свойство определено, и тип, к которому оно присоединяется, оба наследуют классу DependencyObject . С помощью свойств зависимостей язык XAML имеет возможность расширять типы за счет свойств, предоставляемых другими типами. При использовании в разметке по синтаксису элементов свойств присоединенному свойству всегда предшествует имя предоставляющего его типа, даже если такое свойство употребляется в качестве атрибута.

Направления привязки

Тип привязки элемента Binding определяется его свойством Mode, которое может принимать одно из значений перечисления BindingMode из пространства имен System.Windows.Data :

  • Default - установлен по умолчанию и зависит от типа привязываемого свойства на стороне приемника (целевого свойства). Действует как режим двухсторонней привязки TwoWay для свойств, доступных для редактирования в пользовательском интерфейсе, таких как TextBox.Text или CheckBox.Checked, либо - как односторонняя привязка OneWay для иных свойств. Чтобы не полагаться на настройки по умолчанию, следует взять себе за правило - всегда явно устанавливать параметр направления привязки.
  • OneTime - односторонняя начальная привязка, когда значение целевого свойства устанавливается по значению источника только один раз: при инициализации, программной замены привязанного объекта-источника на новый, при изменении свойства DataContext или в результате программного вызова метода BindingExpression.UpdateTarget (). Иные поступающие уведомления об изменениях на стороне источника приемником учитываться не будут
  • OneWay - односторонняя привязка, когда целевое свойство обновляется при изменении свойства источника. Каждый раз при изменении на стороне источника поток данных направлен от источника к целевому объекту.
  • OneWayToSource - организует однонаправленную привязку, как и OneWay, только выражение привязки помещается в источник. Этот трюк может понадобиться в том случае, когда привязываемое свойство-приемник не является свойством зависимости, а свойство источника все-таки наследует классу DependencyObject .
  • TwoWay - двухсторонняя привязка, когда целевое свойство обновляется при изменении свойства источника и свойство-источник обновляется при изменении целевого свойства. Иными словами, режим привязки TwoWay отправляет данные от источника к целевому объекту, а в случае изменения значения свойства целевого объекта данные отправляются обратно от целевого объекта к источнику

Упражнение 1. Привязка элемента к элементу

В данном упражнении на ряде примеров рассмотрим вопросы создания канала связи между свойствами зависимости визуальных элементов WPF пользовательского интерфейса.

>

  • Программирование
  • Одним из ключевых моментов в разработке xaml -ориентированных приложений является использование привязок (Bindings ). Привязка - это медиатор (посредник), с помощью которого синхронизируются значения свойств между связанными объектами.

    Стоит отметить не очевидный, но важный нюанс: хотя привязка так или иначе ссылается на взаимодействующие объекты, она не удерживает их от сборки мусора!

    Наследование от класса Binding разрешено, но в целях безопасности кода переопределение метода ProvideValue , который связан с основной логикой работы, не допускается. Это так или иначе провоцирует разработчиков на применение паттерна Converter , который тесно переплетается с темой привязок.

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


    Объявлять привязки в xaml допустимо двумя образами:



    Очевидно, что первый способ выглядит не очень лаконично, второй же, основанный на применении расширений разметки , применяется наиболее часто. На платформе WPF существует возможность создавать пользовательские расширения разметки . Например, их удобно использовать для локализации.


    В простейшем случае нужно унаследоваться от класса MarkupExtension и реализовать метод ProvideValue , в котором по ключу получить нужное значение.

    Но такая реализация не поддерживает горячую смену языка во время выполнения программы. Чтобы произвести это усовершенствование необходимо, во-первых, хранить ссылку на локализуемый элемент интерфейса, во-вторых, что менее очевидно, так или иначе иметь в приложении ссылку на сам экемпляр класса Localizing , чтобы защитить его от сборки мусора, и, в третьих, требуется грамотно реализовать подписку и отписку от события смены языка.

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

    Более того, на xaml -платформах Windows Phone , Windows Store и Xamarin.Forms нет возможности создавать пользовательские расширения разметки, что наталкивает на идею использования привязок в качестве расширений разметки

    Не будем ходить вокруг да около, вот то, что нам нужно:

    Public abstract class BindingExtension: Binding, IValueConverter { protected BindingExtension() { Source = Converter = this; } protected BindingExtension(object source) // set Source to null for using DataContext { Source = source; Converter = this; } protected BindingExtension(RelativeSource relativeSource) { RelativeSource = relativeSource; Converter = this; } public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture); public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
    Примечательно, что привязка является конвертером для самой себя. В результате мы получаем поведение очень похожее, как при наследовании от класса MarkupExtension , но, кроме того, остаётся возможность использовать стандартные механизмы контроля сборки мусора!

    Теперь логика для локализации выглядит проще некуда:

    Public partial class Localizing: Base.BindingExtension { public static readonly Manager ActiveManager = new Manager(); public Localizing() { Source = ActiveManager; Path = new PropertyPath("Source"); } public Localizing(string key) { Key = key; Source = ActiveManager; Path = new PropertyPath("Source"); } public string Key { get; set; } public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var key = Key; var resourceManager = value as ResourceManager; var localizedValue = resourceManager == null || string.IsNullOrEmpty(key) ? ":" + key + ":" : (resourceManager.GetString(key) ?? ":" + key + ":"); return localizedValue; } }
    public partial class Localizing { public class Manager: INotifyPropertyChanged { private ResourceManager _source; public ResourceManager Source { get { return _source; } set { _source = value; PropertyChanged(this, new PropertyChangedEventArgs("Source")); } } public string Get(string key, string stringFormat = null) { if (_source == null || string.IsNullOrWhiteSpace(key)) return key; var localizedValue = _source.GetString(key) ?? ":" + key + ":"; return string.IsNullOrEmpty(stringFormat) ? localizedValue: string.Format(stringFormat, localizedValue); } public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { }; } }
    Легко добавить возможность для смены регистра букв:

    Public partial class Localizing: Base.BindingExtension { public enum Cases { Default, Lower, Upper } public static readonly Manager ActiveManager = new Manager(); public Localizing() { Source = ActiveManager; Path = new PropertyPath("Source"); } public Localizing(string key) { Key = key; Source = ActiveManager; Path = new PropertyPath("Source"); } public string Key { get; set; } public Cases Case { get; set; } public override string ToString() { return Convert(ActiveManager.Source, null, Key, Thread.CurrentThread.CurrentCulture) as string ?? string.Empty; } public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var key = Key; var resourceManager = value as ResourceManager; var localizedValue = resourceManager == null || string.IsNullOrEmpty(key) ? ":" + key + ":" : (resourceManager.GetString(key) ?? ":" + key + ":"); switch (Case) { case Cases.Lower: return localizedValue.ToLower(); case Cases.Upper: return localizedValue.ToUpper(); default: return localizedValue; } } }
    В xaml запись выглядит удобно и красиво, но есть некоторые ограничения парсеров разметки на различных платформах:


    Чтобы избавиться на WPF от обязательного префикса m: нужно поместить расширение разметки в отдельную сборку и в Properties/AssemblyInfo.cs указать следующие директивы:


    Для регулирования имени префикса на Windows Phone или Store :


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

    Продемонстрированный подход интенсивно используется в библиотеке