Блог Сергея Байдачного

Мой блог о технологиях

Archive for Октябрь 2011

Введение в Silverlight 5 (часть 5)

leave a comment »

Приступаем к рассмотрению очередного набора возможностей Silverlight 5. Тут мы поговорим о связывании, неявных шаблонах и стилях.

Отладка при связывании с данными

Начнем наш обзор с отладки приложений. Современный отладчик в Visual Studio давно позволяет разработчику получать всю необходимую информацию, если речь идет о коде на C#, C++ или даже JavaScript. Но как только речь заходит о XAML, то тут механизмы отсутствуют. Казалось бы, зачем нужна отладка в XAML, если тут идет декларативное описание интерфейса приложения. Действительно, отладка в XAML не нужна, если речь не идет о связывании с данными. Небольшой кусок кода, связывающий наши данные и интерфейс, может вызвать массу проблем при отладке. Ведь причин для возникновения проблем при связывании может быть множество, это и отсутствие какого-либо свойства, и проблемы с преобразованием либо же несоответствие типа. Но если связывание с данными описывается в XAML, то механизма получить информацию о проблеме не было. В Silverlight 5 возможна отладка XAML кода, описывающего связывание элементов управления и данных.

Рассмотрим небольшой пример кода на XAML:

 

   1:  <UserControl x:Class="SilverlightApplication1.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      Loaded="UserControl_Loaded"
   7:      mc:Ignorable="d"
   8:      d:DesignHeight="300" d:DesignWidth="400">
   9:   
  10:      <Grid x:Name="LayoutRoot" Background="White">
  11:          <Grid.Resources>
  12:              <Style TargetType="TextBox">
  13:                  <Setter Property="Background" Value="AliceBlue"></Setter>
  14:                  <Setter Property="Width" Value="200"></Setter>
  15:                  <Setter Property="Margin" Value="5"></Setter>
  16:              </Style>
  17:              <Style TargetType="TextBlock">
  18:                  <Setter Property="Margin" Value="5"></Setter>
  19:              </Style>
  20:          </Grid.Resources>
  21:          <Grid.RowDefinitions>
  22:              <RowDefinition Height="Auto"></RowDefinition>
  23:              <RowDefinition Height="Auto"></RowDefinition>
  24:              <RowDefinition Height="Auto"></RowDefinition>
  25:          </Grid.RowDefinitions>
  26:          <Grid.ColumnDefinitions>
  27:              <ColumnDefinition Width="Auto"></ColumnDefinition>
  28:              <ColumnDefinition Width="Auto"></ColumnDefinition>
  29:          </Grid.ColumnDefinitions>
  30:          <TextBlock Text="First Name:" Grid.Column="0" Grid.Row="0"></TextBlock>
  31:          <TextBlock Text="First Name:" Grid.Column="0" Grid.Row="1"></TextBlock>
  32:          <TextBlock Text="First Name:" Grid.Column="0" Grid.Row="2"></TextBlock>
  33:          <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FisrtName}"></TextBox>
  34:          <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding LastName}"></TextBox>
  35:          <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Age}"></TextBox>
  36:      </Grid>
  37:  </UserControl>

 

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

 

   1:  private void UserControl_Loaded(object sender, RoutedEventArgs e)
   2:  {
   3:      Person p = new Person();
   4:      p.FirstName = "Sergey";
   5:      p.LastName="Baydachnyy";
   6:      p.Age = 33;
   7:   
   8:      LayoutRoot.DataContext = p;
   9:  }

 

И класс Person:

 

   1:  public class Person
   2:  {
   3:      public string FirstName { get; set; }
   4:      public string LastName { get; set; }
   5:      public int Age { get; set; }
   6:  }

 

Запустив это приложение, можно убедиться, что первое поле не было заполнено данными. Чтобы понять причину ошибки, достаточно установить Breakpoint в XAML файле на строку, описывающую связывание в первом поле и запустить приложение в режиме отладки. Вот что можно увидеть в окне Locals:

 

image

 

Тут и информация об ошибке (я намеренно сделал ошибку в имени поля) и данные об объекте и т. д. То есть, сгенерированный объект BindingState четко позволяет выявить проблему и проверить правильность данных.

Улучшенная поддержка шаблона MVVM

Данная поддержка состоит в расширении перечислимого типа UpdateSourceTrigger значением PropertyChanged. Данное значение влияет на обновление источника данных, происходящее при обновлении цели (элемента, куда данные выгружаются). Ранее этот тип поддерживал лишь два значения, это LostFocus и Explicit. Иными словами, если Вы связываете данные с элементом управления TextBox, то в случае двунаправленного связывания, при изменении элемента TextBox, источник мог быть обновлен лишь при потере фокуса или явным образом. Значение PropertyChanged позволяет реагировать на любые изменения в текстовом поле «на лету». При этом нет необходимости обрабатывать нажатия клавиш и т .д. Вот пример кода:

 

   1:  <UserControl x:Class="SilverlightApplication1.MainPage"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:      Loaded="UserControl_Loaded"
   7:      mc:Ignorable="d"
   8:      d:DesignHeight="300" d:DesignWidth="400">
   9:   
  10:      <Grid x:Name="LayoutRoot" Background="White">
  11:          <Grid.Resources>
  12:              <Style TargetType="TextBox">
  13:                  <Setter Property="Background" Value="AliceBlue"></Setter>
  14:                  <Setter Property="Width" Value="200"></Setter>
  15:                  <Setter Property="Margin" Value="5"></Setter>
  16:              </Style>
  17:              <Style TargetType="TextBlock">
  18:                  <Setter Property="Margin" Value="5"></Setter>
  19:              </Style>
  20:          </Grid.Resources>
  21:          <Grid.RowDefinitions>
  22:              <RowDefinition Height="Auto"></RowDefinition>
  23:              <RowDefinition Height="Auto"></RowDefinition>
  24:          </Grid.RowDefinitions>
  25:          <Grid.ColumnDefinitions>
  26:              <ColumnDefinition Width="Auto"></ColumnDefinition>
  27:              <ColumnDefinition Width="Auto"></ColumnDefinition>
  28:          </Grid.ColumnDefinitions>
  29:   
  30:          <TextBlock Text="First Name:" Grid.Column="0" Grid.Row="0"></TextBlock>
  31:          <TextBox Grid.Row="0" Grid.Column="1" Text=
  32:            "{Binding FirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
  33:          </TextBox>
  34:          <TextBlock Text="Your First Name:" Grid.Column="0" Grid.Row="1"></TextBlock>
  35:          <TextBlock Text="{Binding FirstName}" Grid.Column="1" Grid.Row="1"></TextBlock>
  36:   
  37:      </Grid>
  38:  </UserControl>

 

Ну и код модифицированного класса (чтобы работало связывание в двух направлениях):

 

   1:  public class Person: INotifyPropertyChanged 
   2:  {
   3:      public string firstName;
   4:   
   5:      public string FirstName {
   6:          get { return firstName; }
   7:          set { 
   8:              firstName = value;
   9:              OnPropertyChanged(new PropertyChangedEventArgs("FirstName"));
  10:          } 
  11:      }
  12:   
  13:      public event PropertyChangedEventHandler PropertyChanged;
  14:   
  15:      public void OnPropertyChanged(PropertyChangedEventArgs e)
  16:      {
  17:          if (PropertyChanged != null)
  18:              PropertyChanged(this, e);
  19:      }
  20:   
  21:  }

 

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

Использование связывания в стилях

Наконец-то, свершилось! Теперь в стилях также можно применять связывание. Это упрощает «натягивание» уникальной оболочки для каждого пользователя, позволяя загружать стили динамически, а также предусматривать установку и модификацию характеристик интерфейса.

Вот кусочек кода из предыдущего примера:

 

   1:  <Grid.Resources >
   2:      <Style TargetType="TextBox">
   3:          <Setter Property="Background" Value="{Binding background}"></Setter>
   4:          <Setter Property="Width" Value="{Binding width}"></Setter>
   5:          <Setter Property="Margin" Value="{Binding margin}"></Setter>
   6:      </Style>
   7:      <Style TargetType="TextBlock">
   8:          <Setter Property="Margin" Value="{Binding margin}"></Setter>
   9:      </Style>
  10:  </Grid.Resources>

 

Кроме этого я создал простой класс и мысленно представил, что данные мы выгружаем с сервераJ

 

   1:  public class TextStyle
   2:  {
   3:      public string width { get; set; }
   4:      public string background { get; set; }
   5:      public string margin { get; set; }
   6:  }

 

И связывание:

 

   1:  LayoutRoot.DataContext = new TextStyle() 
   2:  { width = "200", background = "Blue", margin = "5" };

 

Работает…J

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

Сергей Лутай уже написал об этой теме, поэтому я не буду себя утруждать. Читаем тут: http://lutay.uneta.com.ua/post/2011/04/14/Silverlight-5-Implicit-DataTemplate.aspx.

И другие изменения

Для полноты статьи приведем еще несколько нововведений, которые сами по себе интересны, но не стоят отдельного раздела из-за своей прозрачности (понятностиJ).

Особое внимание стоит обратить на новое событие DataContextChanged, которое представлено в базовом классе FrameworkElement. Теперь элемент способен отреагировать на изменение источника данных.

Если связывание происходит с другими элементами в модели, то теперь можно использовать свойство RelativeSource совместно с FindAncestor. Это позволяет указать вместо имени элемента, его тип и уровень вложенности. В результате нужный элемент будет найден в реальном времени и выполнено связывание.

Последнее нововведение, это доступность интерфейса ICustomTypeProvider, описывающего метод GetCustomType. Данный интерфейс может быть полезен, когда Вы создаете объекты с динамически сгенерированными свойствами. В этом случае Вы генерируете и возвращаете объект типа Type, описывающий все свойства, которые Вы хотели бы использовать при связывании. Аналогичным образом можно поступить, когда Вы хотите скрыть некоторые свойства от элементов управления, генерирующих свою структуру автоматически. Например, DataGrid, генерирующий свои колонки автоматически.

Реклама

Written by Sergiy Baydachnyy

20.10.2011 at 15:31

Опубликовано в SilverLight

Tagged with