Урок 2. Привязка

Чтобы обратиться ко мне напрямую, посетите мой блог http://michaelcrump.net/ или мой микроблог в Twitter http://twitter.com/mbcrump.

Это вторая статья из серии «10 уроков по Silverlight 5».

Сначала вспомним, что такое Silverlight.

Microsoft Silverlight — это платформа создания полнофункциональных интернет-приложений. Среда выполнения Silverlight доступна для большинства веб-браузеров в виде подключаемого модуля и работает под управлением различных операционных систем, включая Windows, Mac и Linux.

На предыдущем уроке мы:

  • изучили общие сведения о Silverlight;
  • ознакомились со списком уроков этой серии;
  • узнали об истории развития Silverlight;
  • загрузили необходимое ПО;
  • узнали, как создавать новый проект Silverlight 5.

В этой статье я расскажу, как использовать в Silverlight 5 привязку к предку с помощью RelativeSource и неявные шаблоны данных.

Привязка к предку с помощью RelativeSource

Новой возможностью подсистемы привязки в Silverlight стала привязка к предку с помощью RelativeSource. Хотя в WPF эта возможность была доступна и раньше, в Silverlight она не поддерживалась. Эта возможность позволяет привязывать свойство дочернего объекта к свойству объекта-родителя. Кроме того, новые свойства позволяют определить тип родительского объекта и число уровней наследования между родительским и дочерним объектами.

Сначала мы рассмотрим пример, перенесенный с WPF и показывающий, как извлечь значения свойств FontFamily и FontSize класса TextBlock.

Добавление в существующий проект

Если вы не выполняли задания первого урока серии, создайте проект Silverlight 5, назовите его BeginnerSilverlight5 и откройте в проводнике решений. Дважды щелкните файл MainPage.xaml и замените код раздела Grid следующим кодом.

<Grid x:Name="LayoutRoot" Background="White" >
          <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center" >
             <TextBlock Text="This TextBlock has a FontFamily of "/>
             <TextBlock Text="{Binding RelativeSource={RelativeSource self}, Path=FontFamily}"/>
             <TextBlock Text="&#x0a;This TextBlock has a FontSize of "/>
             <TextBlock Text="{Binding RelativeSource={RelativeSource self}, Path=FontSize}"/>
         </StackPanel>
  </Grid>

Как видно, присвоив свойству RelativeSource значение self, а свойству Path значение FontFamily или FontSize, мы могли определить значение свойств FontFamily и FontSize класса TextBlock, не используя выделенный код.

Но это еще не все. Свойство RelativeSource предка содержит свойство AncestorType, позволяющее указать, что именно нужно привязывать. Давайте рассмотрим пример, в котором устанавливается значение свойства AncestorType. Замените код, добавленный на предыдущем шаге, следующим кодом.

<Grid x:Name="LayoutRoot" Background="White"> 
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <TextBlock Text="This TextBlock is inside a StackPanel with " />
            <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}, Path=Orientation}"/>
            <TextBlock Text=" orientation" />
        </StackPanel>      
  </Grid>

В данном примере свойству AncestorType присваивается значение StackPanel, а свойству Path — значение Orientation. После запуска это приложение отображает слово Horizontal. Если присвоить свойству Orientation объекта StackPanel значение Vertical, отобразится слово Vertical.

А теперь рассмотрим свойства AncestorType и AncestorLevel. Замените код, добавленный на предыдущем шаге, следующим кодом.

<Grid x:Name="LayoutRoot" Background="White" Margin="5,5,5,5"  >
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Grid, AncestorLevel=1}, Path=Margin}"/>
        </StackPanel>
 </Grid>

Как видно из этого кода, мы присваиваем свойству AncestorType значение Grid (показывая, какой элемент нужно привязывать), свойству AncestorLevel — значение 1, а свойству Path — значение Margin. В результате при работе приложения отображаются величины полей сетки.

Давайте подведем итоги.

Вопрос. Что произойдет, если в последнем примере присвоить свойству AncestorLevel значение 2?
Ответ. Ничего не отобразится, поскольку приложение не найдет элемент Grid на уровне 2.

Вопрос. Что произойдет, если в последнем примере присвоить свойству Path значение VerticalAlignment?
Ответ. В окне отобразится слово Stretch.

Неявные шаблоны данных

Еще одна новая возможность подсистемы привязки Silverlight — использование неявных шаблонов данных. Хотя в WPF эта возможность была доступна и раньше, в Silverlight она не поддерживалась. Неявные шаблоны данных позволяют создавать объекты DataTemplate, неявным образом сопоставленные указанному типу данных.

Давайте разберем это на примере. Создайте новый проект Visual Studio 2010 и назовите его SLShowImplicitDataTemplateDemo.

В моем проекте два класса.

  1. Podcast.cs — содержит описание (Description), дату выхода (ReleaseData) и ссылку на подкаст (Link).
  2. PodcastAuthor.cs — этот класс будет наследовать от класса Podcast и добавлять автора соответствующего подкаста.

Указанные выше классы должны выглядеть следующим образом.

public class Podcast
 {
     public string Description { get; set; }
     public DateTime ReleaseDate { get; set; }
     public Uri Link { get; set; }
 }
 public class PodcastAuthor : Podcast
 {
     public string Author { get; set; }
 }

А теперь давайте поработаем с файлом MainPage.xaml.

Первое, что нужно сделать, — настроить пространство имен clr-namespace для нашего локального проекта. В моем примере ниже я присвоил ему имя local и настроил таким образом, чтобы оно указывало на имя моего текущего проекта.

xmlns:local="clr-namespace:SLShowImplicitDataTemplateDemo"

Теперь необходимо определить шаблоны данных (класс DataTemplate) в разделе UserControl, как показано ниже.

<UserControl.Resources>
     <DataTemplate DataType="local:Podcast">
         <StackPanel Orientation="Vertical">
         <TextBlock Text="{Binding Description}" />
         <TextBlock Name="txtReleaseDate" Text="{Binding ReleaseDate}" />
             <HyperlinkButton Content="Listen to this Episode" NavigateUri="{Binding Link}" TargetName="_blank" />          
     </StackPanel>
     </DataTemplate>
  
     <DataTemplate DataType="local:PodcastAuthor">
         <StackPanel Orientation="Vertical">
             <TextBlock Text="{Binding Author}" />
             <TextBlock Foreground="Red" Text="{Binding Description}" />
             <TextBlock Name="txtReleaseDate" Text="{Binding ReleaseDate}" />
             <HyperlinkButton Content="Listen to this Episode" NavigateUri="{Binding Link}" TargetName="_blank" />
         </StackPanel>
     </DataTemplate>
 </UserControl.Resources>

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

А теперь добавим в сетку элемент ListBox с именем listBox1.

<Grid x:Name="LayoutRoot" Background="White">
    <ListBox Name="listBox1"  />
 </Grid>

Дважды щелкните файл MainPage.xaml.cs и добавьте в него следующий фрагмент кода.

public MainPage()
  {
      InitializeComponent();
      Loaded += new RoutedEventHandler(MainPage_Loaded);
  }
  
  void MainPage_Loaded(object sender, RoutedEventArgs e)
  {
      var Podcast = new List<Podcast>();
  
      Podcast.Add(new Podcast()
                      {
                          Description = "This Developer's Life - Typo",
                          ReleaseDate = new DateTime(2011, 9, 19),
                          Link = new Uri("http://www.thisdeveloperslife.com/post/2-0-5-typo", UriKind.Absolute)
                      });
  
      Podcast.Add(new PodcastAuthor()
      {
          Author = "Scott Hanselman",
          Description = "Digging into the Kinect SDK with Dan Fernandez",
          ReleaseDate = new DateTime(2011, 7, 14),
          Link = new Uri("http://hanselminutes.com/275/digging-into-the-kinect-sdk-with-dan-fernandez", UriKind.Absolute)
      });
  
      listBox1.ItemsSource = Podcast;
  }

В этом фрагменте кода мы создаем событие Loaded и добавляем данные в приложение, а затем устанавливаем в качестве значения свойства ItemsSource элемента ListBox универсальный список Podcast.

Если запустить проект, появится следующее изображение.

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

Что еще нужно знать о привязке?

Подсистема привязки Silverlight 5 поддерживает множество новых возможностей, о которых нужно знать. Поэтому ниже приведены несколько кратких определений и ссылки.

ICustomTypeProvider — позволяет выполнять привязку к объектам, структура которых определяется только при работе приложения.

Ссылки с дополнительными сведениями.

Пользовательские расширения разметки — позволяют выполнять при синтаксическом анализе XAML пользовательский код для свойств и обработчиков событий. Расширения разметки поддерживаются также в Silverlight 4 (Binding, StaticResource, TemplateBinding, NullExtension и RelativeSource).

Следующая инструкция привязки представляет собой расширение разметки в Silverlight 4.

<TextBlock Name="txtReleaseDate" Text="{Binding ReleaseDate}" />

Привязка в Style Setter — в Style Setter можно использовать привязку любого типа.

Событие DataContextChanged — порождается при изменении объекта DataContext для элемента управления.

Значением UpdateSourceTrigger теперь является PropertyChanged — источник привязки мгновенно изменяется при изменении свойства назначения привязки.

Заключение

На этом уроке мы узнали, как пользоваться в приложениях Silverlight 5 привязкой к предку с помощью RelativeSource и неявными шаблонами данных, и рассмотрели еще несколько возможностей, поддерживаемых новой подсистемой привязки Silverlight 5. На следующем уроке я планирую рассмотреть новые и улучшенные возможности графического стека в Silverlight 5, включая API XNA 3D и многие другие. Еще раз спасибо за внимание. Встретимся на следующем уроке.

Перевод статей от Michael Crump.