Главная » Разработка для Windows Phone 7 » Привязка данных Windows Phone 7

0

Предположим, требуется, чтобы текущее значение Slider, перемещаемого пользователем, отображалось в TextBlock, как это было в приложении ColorScroll. Пара пустяков. Просто установим обработчик события ValueChanged объекта Slider и при каждом вызове обработчика будем получать значение свойства Value этого Slider, преобразовывать его в строку и задавать эту строку в качестве значения свойства Text элемента TextBlock.

Подобные задачи настолько широко распространены, что Silverlight обеспечивает элегантный механизм для их реализации. Он известен как привязка данных или просто привязка. Привязка данных – это связь, устанавливаемая между двумя свойствами двух объектов, благодаря которой при изменении свойства одного объекта связанное с ним свойство другого объекта обновляется его значением. Привязки также могут быть двунаправленными. В этом случае изменение любого из связанных свойств ведет к изменению другого.

Реализация привязки данных вполне ожидаема. Устанавливается обработчик событий, который обеспечивает обновление одного свойства значением другого с применением возможного преобразования данных. Обычно привязка данных описывается полностью в XAML, т.е. для нее не нужен вообще никакой код.

Проще всего продемонстрировать привязку данных на примере двух элементов, таких как Slider и TextBlock. С этого мы и начнем. Но намного большая мощь привязки данных обнаруживается при связывании визуальных элементов с источниками данных.

Цель этой главы – избежать применения явных обработчиков событий в файлах выделенного кода (только в примере в конце главы я был просто вынужден использовать их). Конечно, часто для поддержки привязки данных в XAML приходится написать немного кода, но этот код правильнее будет классифицировать как бизнес-объекты, а не элементы пользовательского интерфейса.

Источник и цель

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

Источнику привязки данных обычно дают имя:

<Slider Name="slider" … />

Целевое свойство можно вынести как свойство-элемент и задать в качестве его значения объект типа Binding (Привязка):

<TextBlock … >

<TextBlock.Text>

<Binding ElementName="slider" Path="Value" /> </TextBlock.Text> </TextBlock>

В свойстве ElementName (Имя элемента) указывается имя элемента-источника; в свойстве Path (Путь) – имя свойства-источника, каковым в данном случае является свойство Value

объекта Slider. Такой тип привязки иногда называют привязка к элементу, потому что источником привязки является визуальный элемент, ссылка на который выполняется по имени.

Чтобы сделать синтаксис немного более понятным, Silverlight предоставляет расширение разметки Binding, в котором вся привязка описывается в пределах фигурных скобок. (Это одно из расширений разметки Silverlight for Windows Phone. Мы уже познакомились с расширением StaticResource в главе 7 и в главе 16 рассмотрим TemplateBinding (Привязка шаблона).) Получаем более лаконичный синтаксис:

<TextBlock … Text="{Binding ElementName=slider, Path=Value}" … />

Обратите внимание, что параметры ElementName и Path разделены запятой, и что имена slider и Value больше не заключены в кавычки. Кавычки никогда не используются в фигурных скобках расширения разметки.

Приложение SliderBindings (Привязки ползунка) включает эту привязку и позволяет немного поэкспериментировать. Все описывается в XAML-файле:

Проект Silverlight: SliderBindings Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions>

<RowDefinition Height="*" /> <RowDefinition Height="*" /> <RowDefinition Height="*" /> </Grid.RowDefinitions>

<Slider Name="slider" Value="90" Grid.Row="0" Maximum="18 0" Margin="24" />

<TextBlock Name="txtblk"

Text="{Binding ElementName=slider, Path=Value}"

Grid.Row="1"

FontSize="4 8"

HorizontalAlignment="Center" VerticalAlignment="Center" />

<Rectangle Grid.Row="2"

Width="{Binding ElementName=slider, Path=Value}" RenderTransform0rigin="0.5 0.5" Fill="Blue"> <Rectangle.RenderTransform>

<RotateTransform x:Name="rotate" Angle="90" /> </Rectangle.RenderTransform> </Rectangle> </Grid>

На странице располагается объект Slider с диапазоном значений от 0 до 180, объект TextBlock, свойство Text которого связано со свойством Value объекта Slider, и Rectangle, свойство Width которого связано все с тем же свойством Value. Для Rectangle также задано RotateTransform, что обеспечивает поворот элемента на 90°, которые заданы как константа.

При перемещении ползунка TextBlock выводит на экран его текущее значение, и соответствующим образом меняется высота Rectangle. (Целью Binding является свойство Width объекта Rectangle, но Rectangle повернут на 90°.)

Порядок перечисления свойств в расширении разметки Binding не имеет значения. Свойство Path можно поставить первым:

<TextBlock … Text="{Binding Path=Value, ElementName=slider}"

Кстати, если Path идет первым, можно опустить часть «Path=» и просто использовать имя свойства:

<TextBlock … Text="{Binding Value, ElementName=slider}"

Далее в этой и последующих главах я буду использовать такой сокращенный синтаксис, но мне не нравится применять его для привязок к элементу, потому что теряется понимание того, как работает привязка. Классу Binding надо прежде всего найти элемент с именем slider в дереве визуальных элементов и после этого с помощью технологии отражения найти свойство Value в этом элементе. Я предпочитаю синтаксис, в котором порядок свойств повторяет последовательность операций процесса:

<TextBlock … Text="{Binding ElementName=slider, Path=Value}"

Почему это свойство класса Binding называется Path, а не Property? В конце концов, у класса Style есть свойство Property. В чем же дело с Binding?

Ответ прост – значение Path может быть составлено из имен нескольких свойств. Например, у Slider нет имени. Если известно, что этот Slider является первым элементом коллекции Children элемента ContentPanel, на него можно сослаться косвенно следующим образом:

Text="{Binding ElementName=ContentPanel, Path=Children[0].Value}"

Или перемещаясь вверх по дереву визуальных элементов:

Text="{Binding ElementName=LayoutRoot, Path=Children[1].Children[0].Value}"

Составляющие компоненты пути должны быть свойствами или индексами. Между ними ставятся точки.

Цель и режим

У привязок есть источник и цель. Цель привязки – это свойство, для которого задается привязка. Это свойство всегда должно быть продублировано свойством-зависимостью. Всегда, без каких-либо исключений. Это ограничение особенно очевидно, когда привязка создается в коде.

Чтобы попробовать это в SliderBindings, удалим привязку, заданную для свойства Text элемента TextBlock. В файле MainPage.xaml.cs с помощью директивы using необходимо подключить пространство имен System.Windows.Data, в котором располагается класс Binding. В конструкторе после вызова InitializeComponent создадим объект типа Binding и зададим его свойства:

Binding binding = new Binding(); binding.ElementName = "slider"; binding.Path = new PropertyPath("Value");

Свойства ElementName и Path ссылаются на источник привязки. Но посмотрите на код, описывающий свойство Text элемента TextBlock как цель привязки:

txtblk.SetBinding(TextBlock.TextProperty, binding);

Метод SetBinding описывается классом FrameworkElement, и его первый аргумент является свойством-зависимостью. Это и есть целевое свойство. Цель – это элемент, для которого вызывается SetBinding. Или как альтернативный вариант привязку для цели можно задать, используя статический метод BindingOperations.SetBinding:

Binding0perations.SetBinding(txtblk, TextBlock.TextProperty, binding);

Но нам по-прежнему необходимо свойство-зависимость. Это еще одна причина, почему свойства визуальных объектов должны быть зависимыми свойствами. Это позволяет не только применять к этим свойствам стили и назначать их целями анимаций, но также использовать их в качестве целей привязок данных.

С точки зрения приоритетности свойств зависимостей привязки приравнены к локальным параметрам.

Метод BindingOperations.SetBinding подразумевает возможность задания привязки для любого свойства-зависимости. Но это не так в Silverlight for Windows Phone. Целью привязки может быть только свойство класса FrameworkElement.

Например, можно заметить, что свойству RenderTransform элемента Rectangle в MainPage.xaml задан RotateTransform. Попробуем задать для свойства Angle ту же привязку, что и для свойства Text объекта TextBlock и свойства Width объекта Rectangle:

<RotateTransform x:Name="rotate"

Angle="{Binding ElementName=slider, Path=Value}" />

Все выглядит нормально, но не работает. Во время выполнения будет сформировано исключение XamlParseException. Angle продублировано свойством-зависимостью, все правильно, а вот RotateTransform не наследуется от FrameworkElement, поэтому не может быть целью привязки. (Если задать Binding для свойства Angle класса RotateTransform, в Silverlight 4 все будет работать, но Silverlight for Windows Phone – это преимущественно Silverlight 3)

Давайте поэкспериментируем и удалим эту привязку для свойства Angle RotateTransform и любой код, который мог быть добавлен в MainPage.xaml.cs. Исходное значение свойства Value объекта Slider – 90:

<Slider Name="slider"

Value="90" … />

Целью привязки является свойство Text объекта TextBlock:

<TextBlock Name="txtblk"

Text="{Binding ElementName=slider, Path=Value}" … />

Теперь изменим ситуацию на противоположную. Зададим для свойства Text объекта TextBlock исходное значение 90:

<TextBlock Name="txtblk" Text="90" … />

И сделаем целью привязки свойство Value объекта Slider:

<Slider Name="slider"

Value="{Binding ElementName=txtblk, Path=Text}" … />

Сначала кажется, что все работает. При запуске приложения ползунок Slider располагается в центре шкалы, указывая на значение 90, полученное от TextBlock, и размер Rectangle тоже все еще связан со Slider. Но при перемещении ползунка прямоугольник меняет высоту, а вот выводимое TextBlock значение остается неизменным. Объект Binding, заданный для Slider, ожидает изменения свойства Text объекта TextBlock, а оно остается неизменным.

Теперь добавим в привязку для Slider параметр Mode, чтобы обозначить двунаправленную привязку:

<Slider Name="slider"

Value="{Binding ElementName=txtblk, Path=Text, Mode=TwoWay}" … />

Заработало! Целью привязки по-прежнему считается свойство Value объекта Slider. Любые изменения свойства Text объекта TextBlock отражаются в свойстве Value объекта Slider, но теперь и изменения Slider также приводят к изменению TextBlock.

В качестве значений свойства Mode (Режим) используются члены перечисления BindingMode (Режим привязки). Значением по умолчанию является BindingMode.OneWay (Однонаправленный). Кроме него имеется еще два значения: BindingMode.TwoWay (Двунаправленный) и BindingMode.OneTime (Однократно), обеспечивающий передачу данных от источника к цели только один раз.

Применяя эту же технику, можно установить привязку к свойству Angle объекта RotateTransform. Сначала вернем в TextBlock исходную привязку:

<TextBlock Name="txtblk"

Text="{Binding ElementName=slider, Path=Value}" … />

Теперь зададим для Slider двунаправленную привязку к свойству Angle объекта RotateTransform:

<Slider Name="slider"

Value="{Binding ElementName=rotate, Path=Angle, Mode=TwoWay}" … />

И это работает! Элемент Rectangle поворачивается при перемещении Slider:

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии