XAML и Data Binding: Расширенные возможности разметки и связывания данных в Silverlight

XAML и Data Binding: Расширенные возможности разметки и связывания данных в Silverlight

В этой статье я постараюсь показать, как можно использовать дополнительные возможности XAML-разметки. А также некоторые интересные моменты Data Binding как в XAML, так и в code-behind.

Комментарии в XAML

Начнем с самого простого, но не менее полезного. Комментарии – помогают читать код, отключать/включать временно куски кода при отладке.

Это тоже простой комментарий, но только внутри закомментирован некий код:

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

На заметкуДля того чтобы закомментировать выделенный фрагмент кода, можно нажать сочетание клавиш CTRL+K+C, а для обратного эффекта нужно нажать CTRL+K+U.

Определение констант в XAML

В разметку XAML можно определять константы некоторых простых типов, например, string, int, bool. Для того чтобы можно было сделать требуется добавить namespace System в документ:

Теперь в разметке можно задать константы в Resources :

Теперь это значение можно использовать по такому же принципу как и StaticResource:

На заметкуОднако, по такому принципу нельзя определить в XAML константу типа DateTime.

Перечисление в XAML

Довольно часто приходится применять в XAML перечисления (enum), которые в code-behind должны выглядеть следующим образом. Есть такое перечисление:

В code-behind использование было бы таким образом:

Так вот, в XAML данное определение будет выглядеть так:

DataContext vs Source (Data Binding)

В первую очередь хотелось бы показать применение свойства Source у объекта Binding. Дело в том, что можно использовать и свойство Source и DataContext.

Или вот второй вариант:

Эффект от применения того или иного способа будет одинаковым, за исключением некоторого нюанса. Если используется DataContext, то его “действие” распространяется на все контролы расположенные ниже по иерархии в визуальном дереве (VisualTree). Такого не происходит, если использовать Source, то есть привязка, таким образом, происходит “точечно” или “целенаправленно”. Это оправдано, когда в контексте одной формы требуется использовать несколько поставщиков данных.

ElementName Binding

Название параметра ElementName в классе Binding говорит само за себя. Привязка осуществляется к именованному контролу. Вот простой пример привязки:

Такая привязка приведет к тому, что при изменении текста в поле TextBox с именем FirstTextBox незамедлительно изменится текст в у контрола с именем SecondTextBox. Обратного действа не произойдет, потому что по умолчанию такой важный параметр как Mode у Binding имеет значение OneWay (в одну сторону). Но если установить значение этого параметра TwoWay, то при изменении текста в любом из контролов, второй тут же получит измененное значение.

Еще одним немаловажным свойством, которое чаще всего пишется, но подразумевается по умолчанию – это свойство Path. Разметка типа:

RelativeSource Binding и TemplatedBinding

Хочется верить, что Вы уже не только сталкивались, но и не раз использовали способы привязки данных указанных в заголовке раздела. Внесу пояснения: RelativeSource применяется когда необходимо связать данные с источником относительно цели. Например, для получения ссылки на источник, чтобы привязаться к его свойству, необходимо использовать RelativeSource. Правда, в отличие от WPF, в Silverlight существует некоторое ограничение. Вы не сможете получить доступ к источнику используя AncesstorType, но можете довольствоваться Self и TemplatedBinding.

При связывании с использованием режима Self возвращает контрол (сам себя). Такой режим полезен, когда необходимо связать свойства этого же контрола. Например, требуется у TextBox в выпадающую подсказку (ToolTip) привязать значение свойства Text. Для такой задачи как раз и очень подходит RelativeSource:

В отличие от Self-режима, TemplatedBinding используется только когда связывание идет шаблоне контрола (Control Template) или в шаблоне данных (Data Template). TemplatedBinding возвращает представление контента (content presenter) для этого шаблонного контрола. Например, это выражения привязки получит значение актуальной высоты контента представления (content presenter):

На заметку"" и "" эквивалентны. Единственное отличие, так это то что TemplateBinding всегда использует режим направления связывания OneWay.

Привязка свойства контрола к DataContext

Порой приходится в свойство контрола, а еще чаще в параметр как-либо команды передать DataContext текущего контрола (объекта). Тут, на самом деле, всё просто да безобразия, надо просто указать "пустую" привязку без параметров:

Кстати, если Вы читаете эту статью сначала, то в курсе, что необязательный параметр Path присутствует всегда неявно. И в данном случаи, при такой привязке, он примет такое значение:

Привязка свойств в Code-Behind

Допустим, что требуется привязать какое-то свойство из Code-Behind класса в какому-нибудь свойству какого-то контрола. Существует два варианта решения. Первое, привязать DataContext верхнего в иерархии UI-объекта таким образом:

А потом требуемые свойства привязывать используя тот самый DataContext:

И второй вариант, использовать ресурсы.

Использование Resources для привязки данных

В этом примере для полноты картины я буду использовать подход с применением MVVM, но Вы можете не использовать, а по-прежнему пользовать code-behind концепцию. Итак, чтобы осуществить привязку к какому-либо свойству какого-либо класса, надо этот самый класс создать:

Этот класс может магическим образом наполняется данными где-то в code-behind, а можно и наполнить этот класс данными и в XAML. Итак, нам требуется создать экземпляр этого класса в XAML. Для этого добавим на страниц необходимый namespace:

А теперь в ресурсах UI-элемента самого верхнего уровня (у меня он называется UserControl) создам требуемый экземпляр:

На заметкуТолько некоторые типы данных можно наполнить в XAML. Можно: string, int, double. Для других более комплексных типов, таких как decimal и DateTime, требуется конверторы для применения к свойствам.

А теперь к созданному в ресурсе объекту можно осуществить привязку. Более того, все данные привязанные таким образом, будут доступны в desing-time в привязанных контролах:

Привязка вложенных свойств (Nested Properties Binding)

Как уже было сказано выше, осуществить привязку к свойству объекта можно так:

Однако, мой класс обертка на RIA-Service имеет вложенные свойства, например, TotalRecordsLoaded имеет свойствами значения для всех таблиц. То есть, я могу запросив у класса программно значение свойства TotalRecordsLoaded.Person получить количество записей в таблице Persons. Такой же принцип можно использовать и в XAML:

Примем "путешествовать" вниз по иерархии можно на несколько уровней и на столько далеко, на сколько Вам нужно.

Привязка к индексируемым свойствам (Indexed properties)

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

Более того, если у индексируемое свойство типа string (такое как Dictionary<string, string>), то можно привязку осуществить даже так:

То есть использовать вместо индекса именование ключа.

Привязка коллекции (Collection Binding)

Привязку свойства к коллекции можно осуществить используя Resources, о котором говорилось выше. Пример Binding к коллекции. Для начала создадим простой класс Person, который будет "участвовать" в коллекции, и класс SampleData, который будет держать коллекцию:

А теперь создадим экземпляр коллекции в ресурсах:

На заметкуВместо PagedCollectionView можно воспользоваться классом CollectionViewSource как прокси-классом от коллекции PagedCollectionView, для того чтобы осуществлять привязку прямо в XAML.

Дополнительные параметры привязки (Extending Binding)

В Silverlight 4 появился параметр StringFormat. Использование этого параметра позволяет отформатировать привязанные данные в соответствие со стандартами языка приложения. Ознакомьтесь с примерами применения.

Дата и время специальный формат:

Дата и время со специальным символами необходимо брать в одинарные кавычки:

Или использовать специальный символ:

Числовой и с плавающей точкой:

Еще один полезный, на мой взгляд, параметр TargetNullValue. Если значение свойства может получать null, то этот параметр автоматически подменит null на указанное Вами значение. Пример применения:

Здесь, если значение Total не задано изначально, то по идеи должно быть null, так вот благодаря этому параметру вы увидете не пустое место, а цифру 0.

Еще одним полезным параметром в связывании является FallbackValue, принцип применения которого аналогичен принципу применения предыдущего параметра, только здесь речь идёт о значение null в контексте DataContext.

Следующий по порядку в нашем списке параметр по имени UpdateSourceTrigger. Как гласит описание параметра на MSDN: "Получает или задает значения, определяющего график обновления источника привязки." По умолчанию значение свойства - Default, что означает автоматически обновлять значение источника привязки. В Silverlight доступно еще и значение Explicit, которые возложит обязанности по обновлению источника на программиста. А теперь примеры:

В таком варианте привязки:

Связанные источники будут обновлены автоматически. Но если на потребуется перед обновлением источника проделать какие-то операции с новым значением, например, проверить на валидность, то следует использовать другой параметр:

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

Конверторы (IValueConverter)

Предположим, что у вас какой-то класс имеет свойство IsVisible, которое имеет тип Boolean. UIElement реализует видимость объекта немного по-другому, у него есть свойство Visibility, которое принимает значения Visible и Collapsed. Таким образом, привязать видимость свойство из вашего класса к объекту типа UIElement не получится. Тут на помощь и приходят конвертеры. Конверторы, это классы, которые реализуют интерфейс IValueConverter, который в свою очередь, предоставляет два метода: Convert и ConvertBack.

Пример конвертора (BoolToVisibility)

Использование конвертора предполагает регистрацию его в ресурсах:

при необходимости следует добавить namespace:

и далее его можно подключать как один из параметров привязки:

К вышесказанному можно добавить, что конверторы принимают параметры (ConverterParameter), которые на самом деле, очень полезны. Потому что именно значения параметров для конвертора можно использовать для выбора типа, направления, формата и т.д. возвращаемого значения.

Привязка данных из кода (Data Binding in Code)

Рано или поздно, в жизни любого программиста Silverlight наступает время когда требуется привязку данных осуществить в коде. И тут на помощь приходит GetBindingExpression и SetBinding методы у соответствующего контрола. Создадим новый Binding для свойства Text контрола TextBox:

Этот простой код создаёт двунаправленную привязку. Конечно же вы можете использовать все остальные параметры (StringFormat, TargetNullValue, FallbackValue и Source) класса Binding и в коде.

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

Обратите внимание, что вы получаете не Binding объект, а именно BindingExpression, в котором есть два свойства ParentBinding и DataItem. Так вот свойство ParentBinding как раз и есть тот самый Binding, который мы создали выше.

Получение и установка значений присоединенных свойств

В коде также не составить труда получить значение присоединённых свойств (Attached Properties). Примером может послужить следующий код:

В данном примере мы получаем значение ряда разметки, в которой TextBox находится. Для того чтобы установить присоединенное свойство контролу воспользуемся кодом:

Синтаксис привязки (Binding using Property Element)

Ну, и на последок, хотелось бы еще немного показать другой формат привязки данных, о котором частенько забывают. Это, так называемый, синтаксис на основе свойств (в отличии от общепринятого "на основе атрибутов"). Возможно писанины он несет в себе больше, причем в некоторых случаях, гораздо больше чем обычный, но тем не менее тоже бывает очень полезно знать об его наличии:

В этом примере, свойство Text у контрола TextBox привязывается значение свойства Name. В обычной схеме это выглядит, например, таким образом:

📎📎📎📎📎📎📎📎📎📎