Привязка к объектам
22WPF --- Привязка, команды и стили WPF --- Привязка к объектам
До сих пор добавлялись привязки, которые устанавливали связь между двумя элементами. Однако в приложениях, управляемых данными, чаще создаются выражения привязки, которые извлекают данные из невизуальных объектов. Единственное требование, которое должно при этом соблюдаться — информация, которую необходимо отобразить, должны храниться в общедоступных свойствах. Инфраструктура привязки данных WPF не может извлекать приватную информацию или читать общедоступные поля.
При привязке к объекту, не являющемуся элементом, следует отказаться от свойства Binding.ElementName и применять вместо него одно из следующих свойств:
- Source
Ссылка, указывающая на исходный объект; другими словами, это объект, поставляющий данные.
- RelativeSource
Указывает на исходный объект, использующий объект RelativeSource, который позволяет базировать ссылку на текущем элементе. Это специализированный инструмент, который удобен при написании шаблонов элементов управления и шаблонов данных.
- DataContext
Если источник не был указан с помощью свойства Source или RelativeSource, то среда WPF производит поиск в дереве элементов, начиная с текущего элемента. Она проверяет свойство DataContext каждого элемента и использует первый из них, который не равен null. Свойство DataContext исключительно полезно, когда нужно привязать несколько свойств одного объекта к разным элементам, потому что можно установить свойство DataContext высокоуровневого объекта контейнера, вместо его установки непосредственно на целевой элемент.
Ниже эти варианты описаны более подробно.
Свойство Source
Свойство Source достаточно прямолинейно. Единственный момент, который следует учитывать — объект данных должен быть сделан удобным для привязки. Как будет показано, для получения объекта данных существует несколько подходов: извлечь его из ресурса, генерировать программно или получить от поставщика данных.
Простейший вариант — установить Source в некоторый готовый и доступный статический объект. Например, можно создать статический объект в коде и использовать его. Или же можно применить ингредиент из библиотеки классов .NET, как показано ниже:
<TextBox Name="txt" Margin="10" MinHeight="26" VerticalContentAlignment="Center"
FontFamily="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=Source}"
Text="Текст"></TextBox>
Это выражение привязки получает объект FontFamily, который предоставлен свойством SystemFonts.IconFontFamily. (Обратите внимание, что для установки свойства Binding.Source понадобится помощь расширения разметки Static.) Затем свойство Binding.Path устанавливается в свойство FontFamily.Source, которое выдает имя семейства шрифтов. Результатом будет единственная строка текста. В Windows Vista или Windows 7 имя шрифта выглядит как Segoe UI.
Другой вариант состоит в привязке к объекту, который ранее создавался в виде ресурса. Например, следующая разметка создает объект FontFamily, указывающий на шрифт Calibri:
<Window.Resources>
<FontFamily x:Key="CustomFont">Calibri</FontFamily>
</Window.Resources>
...
<TextBox Name="txt" Margin="10" MinHeight="26" VerticalContentAlignment="Center"
FontFamily="{Binding Source={StaticResource CustomFont}, Path=Source}"
Text="Текст"></TextBox>
Свойство RelativeSource
Свойство RelativeSource позволяет установить его в исходный объект на основе его отношения к целевому объекту. Например, свойством RelativeSource можно воспользоваться для привязки элемента к самому себе или для привязки к родительскому элементу, который находится в неизвестном количестве уровней выше в дереве элементов.
Для установки свойства Binding.RelativeSource применяется объект RelativeSource. Это несколько усложняет синтаксис, поскольку нужно создать объект Binding и внутри него — вложенный объект RelativeSource. Один вариант состоит в использовании синтаксиса установки свойства вместо расширения разметки Binding.
Например, в следующем коде создается объект Binding для свойства TextBlock.Text. Объект Binding использует RelativeSource, которое ищет родительское окно и отображает заголовок окна:
<TextBlock Name="txb" Margin="10,5,0,10"
FontSize="{Binding ElementName=sld, Path=Value, Mode=TwoWay}"
Foreground="{Binding ElementName=lst, Path=SelectedItem.Tag, Mode=OneWay}">
<TextBlock.Text>
<Binding Path="Title">
<Binding.RelativeSource>
<RelativeSource Mode="FindAncestor" AncestorType="{x:Type Window}"></RelativeSource>
</Binding.RelativeSource>
</Binding>
</TextBlock.Text>
</TextBlock>
Для объекта RelativeSource выбран режим FindAncestor, который заставляет его осуществлять поиск вверх по дереву элементов до тех пор, пока не будет найден тип элемента, определенный свойством AncestorType.
Наиболее общий способ записи этой привязки состоит в комбинировании ее в одну строку, используя расширения разметки Binding и RelativeSource, как показано ниже:
Text="{Binding Path=Title, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Режим FindAncestor — один из четырех возможных вариантов при создании объекта RelativeSource. Все варианты кратко описаны ниже:
- Self
Выражение привязывается к другому свойству того же элемента
- FindAncestor
Выражение привязывается к родительскому элементу. WPF будет проводить поиск вверх по дереву элементов, пока не найдет нужный родительский элемент. Чтобы указать родителя, необходимо также установить свойство AncestorType для индикации типа родительского элемента, который должен быть найден. Дополнительно с помощью свойства AncestorLevel можно пропустить определенное количество совпадений указанного элемента. Например, если требуется привязка к третьему элементу типа ListBoxItem при восхождении вверх по дереву, то следует установить AncestorType={x:Type ListBoxItem} и AncestorLevel=3, тем самым пропуская первые два ListBoxItem. По умолчанию AncestorLevel равен 1, и поиск прекращается на первом найденном элементе.
- PreviousData
Выражение осуществляет привязку к предыдущему элементу данных в списке, привязанном к данным. Это можно использовать в элементе списка
- TemplatedParent
Выражение осуществляет привязку к элементу, к которому применен шаблон. Этот режим работает, только если привязка находится внутри шаблона элемента управления или шаблона данных
На первый взгляд свойство RelativeSource может показаться излишним усложнением разметки. В конце концов, почему бы просто не привязаться непосредственно к необходимому источнику, используя свойство Source или ElementName? Однако, это не всегда возможно, и обычно потому, что объект-источник и целевой объект находятся в разных частях разметки. Так получается при создании шаблонов элементов управления и шаблонов данных. Например, при построении шаблона данных, который изменяет способ представления элементов в списке, может понадобиться доступ к объекту ListBox верхнего уровня, чтобы прочитать какое-то его свойство.
Свойство DataContext
В некоторых случаях имеется множество элементов, привязанных к одному объекту. Например, рассмотрим следующую группу элементов TextBlock, каждый из которых использует исходное выражение привязки для извлечения различных деталей о шрифте, значков по умолчанию, включая промежутки между строками, стиль и вес первой гарнитуры (то и другое — просто Regular). Можете воспользоваться свойством Source для каждого из них, но это приводит к довольно длинной разметке:
<StackPanel Margin="10">
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=Source}"></TextBlock>
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=LineSpacing}"></TextBlock>
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=FamilyTypefaces[0].Weight}"></TextBlock>
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=FamilyTypefaces[0].Style}"></TextBlock>
</StackPanel>
В такой ситуации было бы яснее и удобнее определить источник привязки один раз с помощью свойства FrameworkElement.DataContext. В данном примере имеет смысл установить свойство DataContext элемента StackPanel, содержащего в себе все элементы TextBlock. (Можно было бы также установить свойство DataContext на еще более высоком уровне, например, на уровне всего окна, но лучше определить его насколько возможно уже, чтобы яснее выразить намерения.)
Установить свойство DataContext элемента можно таким же образом, как устанавливается свойство Binding.Source. Другими словами, можно встроить объект, извлечь его из статического свойства либо получить из ресурса, как показано ниже:
<StackPanel DataContext="{х:Static SystemFonts.IconFontFamily}">
После этого выражения привязки упрощаются за счет исключения некоторой информации об источнике:
<TextBlock Margin="5" Text="{Binding Path=Source}"></TextBlock>
Когда информация об источнике отсутствует в выражении привязки, WPF проверяет свойство DataContext элемента. Если оно равно null, WPF ищет в дереве элементов первый контекст данных, отличный от null. (Изначально свойства DataContext всех элементов равны null.) Если подходящий контекст данных обнаружен, то он используется для привязки. Если же нет, то выражение привязки не передает никакого значения целевому свойству.