Привязка данных в XAML. Расширения привязки и xaml-разметки на примере локализации

Привязка данных в XAML. Расширения привязки и xaml-разметки на примере локализации

07.05.2019

Аннотация: В данном разделе рассматриваются основные понятия привязки данных в 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 не исключает обычных расширений разметки, но в некоторых случаях является даже более безопасным и простым вариантом. Также всё это не ограничивается одной лишь локализацией, а пригодно для множества других целей...

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

    Последнее обновление: 7.02.2016

    В WPF привязка (binding) является мощным инструментом программирования, без которого не обходится ни одно серьезное приложение.

    Привязка подразумевает взаимодействие двух объектов: источника и приемника. Объект-приемник создает привязку к определенному свойству объекта-источника. В случае модификации объекта-источника, объект-приемник также будет модифицирован. Например, простейшая форма с использованием привязки:

    Для определения привязки используется выражение типа:

    {Binding ElementName=Имя_объекта-источника, Path=Свойство_объекта-источника}

    То есть в данном случае у нас элемент TextBox является источником, а TextBlock - приемником привязки. Свойство Text элемента TextBlock привязывается к свойству Text элемента TextBox. В итоге при осуществлении ввода в текстовое поле синхронно будут происходить изменения в текстовом блоке.

    Работа с привязкой в C#

    Ключевым объектом при создании привязки является объект System.Windows.Data.Binding . Используя этот объект мы можем получить уже имеющуюся привязку для элемента:

    Binding binding = BindingOperations.GetBinding(myTextBlock, TextBlock.TextProperty);

    В данном случае получаем привязку для свойства зависимостей TextProperty элемента myTextBlock.

    Также можно полностью установить привязку в коде C#:

    Public MainWindow() { InitializeComponent(); Binding binding = new Binding(); binding.ElementName = "myTextBox"; // элемент-источник binding.Path = new PropertyPath("Text"); // свойство элемента-источника myTextBlock.SetBinding(TextBlock.TextProperty, binding); // установка привязки для элемента-приемника }

    Если в дальнейшем нам станет не нужна привязка, то мы можем воспользоваться классом BindingOperations и его методами ClearBinding() (удаляет одну привязку) и ClearAllBindings() (удаляет все привязки для данного элемента)

    BindingOperations.ClearBinding(myTextBlock, TextBlock.TextProperty);

    BindingOperations.ClearAllBindings(myTextBlock);

    Некоторые свойства класса Binding :

      ElementName : имя элемента, к которому создается привязка

      IsAsync : если установлено в True, то использует асинхронный режим получения данных из объекта. По умолчанию равно False

      Mode : режим привязки

      TargetNullValue : устанавливает значение по умолчанию, если привязанное свойство источника привязки имеет значение null

      RelativeSource : создает привязку относительно текущего объекта

      Source : указывает на объект-источник, если он не является элементом управления.

      XPath : используется вместо свойства path для указания пути к xml-данным

    Режимы привязки

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

      OneWay : свойство объекта-приемника изменяется после модификации свойства объекта-источника.

      OneTime : свойство объекта-приемника устанавливается по свойству объекта-источника только один раз. В дальнейшем изменения в источнике никак не влияют на объект-приемник.

      TwoWay : оба объекта - применки и источник могут изменять привязанные свойства друг друга.

      OneWayToSource: объект-приемник, в котором объявлена привязка, меняет объект-источник.

      Default : по умолчанию (если меняется свойство TextBox.Text , то имеет значение TwoWay, в остальных случаях OneWay).

    Применение режима привязки:

    Обновление привязки. UpdateSourceTrigger

    Односторонняя привязка от источника к приемнику практически мгновенно изменяет свойство приемника. Но если мы используем двустороннюю привязку в случае с текстовыми полями (как в примере выше), то при изменении приемника свойство источника не изменяется мгновенно. Так, в примере выше, чтобы текстовое поле-источник изменилось, нам надо перевести фокус с текстового поля-приемника. И в данном случае в дело вступает свойство UpdateSourceTrigger класса Binding, которое задает, как будет присходить обновление. Это свойство в качестве принимает одно из значений перечисления UpdateSourceTrigger :

      PropertyChanged: источник привязки обновляется сразу после обновления свойства в приемнике

      LostFocus: источник привязки обновляется только после потери фокуса приемником

      Explicit: источник не обновляется до тех пор, пока не будет вызван метод BindingExpression.UpdateSource()

      Default: значение по умолчанию. Для большинства свойств это значение PropertyChanged . А для свойства Text элемента TextBox это значение LostFocus

    В данном случае речь идет об обновлении источника привязки после изменения приемника в режимах OneWayToSource или TwoWay . То есть чтобы у нас оба текстовых поля, которые связаны режимом TwoWay, моментально обновлялись после изменения одного из них, надо использовать значение UpdateSourceTrigger.PropertyChanged:

    Свойство Source

    Свойство Source позволяет установить привязку даже к тем объектам, которые не являются элементами управления WPF. Например, определим класс Phone:

    Class Phone { public string Title { get; set; } public string Company { get; set; } public int Price { get; set; } }

    Теперь создадим объект этого класса и определим к нему привязку:

    Свойство TargetNullValue

    На случай, если свойство в источнике привязки вдруг имеет значение null, то есть оно не установлено, мы можем задать некоторое значение по умолчанию. Например:

    В данном случае у ресурса nexusPhone не установлено свойство Title, поэтому текстовый блок будет выводить значение по умолчанию, указанное в параметре TargetNullValue.

    Свойство RelativeSource

    Свойство RelativeSource позволяет установить привязку относительно элемента-источника, который связан какими-нибудь отношениями с элементом-приемником. Например, элемент-источник может быть одним из внешних контейнеров для элемента-приемника. Либо источником и приемником может быть один и тот же элемент.

    Для установки этого свойства используется одноименный объект RelativeSource . У этого объекта есть свойство Mode , которое задает способ привязки. Оно принимает одно из значений перечисления RelativeSourceMode :

      Self: привязка осуществляется к свойству этого же элемента. То есть элемент-источник привязки в то же время является и приемником привязки.

      FindAncestor: привязка осуществляется к свойству элемента-контейнера.

    Например, совместим источник и приемник привязке в самом элементе:

    Здесь текст и фоновый цвет текстового поля связаны двусторонней привязкой. В итоге мы можем увидеть в поле числовое значение цвета, поменять его, и вместе с ним изменится и фон поля.

    Привязка к свойствам контейнера:

    При использовании режима FindAncestor, то есть привязке к контейнеру, необходимо еще указывать параметр AncestorType и передавать ему тип контейнера в виде выражения AncestorType={x:Type Тип_элемента-контейнера} . При этом в качестве контейнера мы могли бы выбрать любой контейнер в дереве элементов, в частности, в данном случае кроме Grid таким контейнером также является элемент Window.

    Свойство DataContext

    У объекта FrameworkElement , от которого наследуются элементы управления, есть интересное свойство DataContext . Оно позволяет задавать для элемента и вложенных в него элементов некоторый контекст данных. Тогда вложенные элементы могут использовать объект Binding для привязки к конкретным свойствам этого контекста. Например, используем ранее определенный класс Phone и создадим контекст данных из объекта этого класса:

    Таким образом мы задаем свойству DataContext некоторый динамический или статический ресурс. Затем осуществляем привязку к этому ресурсу.



    © 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows