Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений Windows Phone Windows Phone: Связывание с данными RSS

Windows Phone: Связывание с данными

Текущий рейтинг: 0 (проголосовало 0)
 Посетителей: 703 | Просмотров: 932 (сегодня 0)  Шрифт: - +

Практически каждое осмысленное приложение Windows Phone имеет дело с какими-либо данными, и возможность подключения этих данных к UI-элементам (представлению) абсолютно необходима. Существуют разные способы делать это программным путем, но один из наиболее эффективных — связывание данных с элементами управления.

Вот хорошие новости:

  • это несложно понять
  • это нетрудно реализовать

Чтобы проиллюстрировать связывание с данными (data binding), мы создадим страницу, показанную на рис. 1. При загрузке она будет заполняться данными из объекта Person, который вы создадите. Каждое значение в UI будет связано с каким-либо свойством объекта Person, и само связывание данных с элементами управления будет осуществляться автоматически — никакого кода на C# для этого не потребуется.

*
Рис. 1. Представление, связанное с данными, в Windows Phone

Приступаем

Для начала создайте новое приложение Windows Phone в Visual Studio и назовите его DataBinding. Первым делом создайте класс, который будет выступать в роли данных, к которым будет осуществляться привязка (он также известен как DataContext). Щелкните проект правой кнопкой мыши, выберите Add | New | Class и присвойте файлу класса имя Person.cs.

Каждое значение в UI будет связано с каким-либо свойством объекта Person, и само связывание данных с элементами управления будет осуществляться автоматически — никакого кода на C# для этого не потребуется.

Person будет содержать (как минимум) все свойства, которые вы хотите отображать в представлении. Этот класс состоит из перечисления и набора автоматических свойств, как показано на рис. 2.

Рис. 2. Класс Person

public class Person
{
  public enum Sex
  {
    Male,
    Female,
  }
  public string Name { get; set; }
  public bool Moustache { get; set; }
  public bool Goatee { get; set; }
  public bool Beard { get; set; }
  public Sex WhichSex { get; set; }
  public double Height { get; set; }
  public DateTime BirthDate { get; set; }
  public bool Favorite { get; set; }
}

Вы сможете довольно быстро понять, как эти свойства сопоставляются с различными элементами ввода, представленными на рис. 1. Булевым значениям могут соответствовать либо элементы CheckBox, либо элементы RadioButton (в зависимости от того, являются ли они взаимоисключающими).

Создание формы

Следующая задача — создать форму для связывания с данными. Щелкните проект правой кнопкой мыши и выберите Open in Expression Blend. Как правило, я создаю свои UI в Expression Blend, а код пишу в Visual Studio.

В сетке контента создайте шесть строк и два столбца, а затем перетащите соответствующие элементы управления для ввода. На рис. 3 показан XAML, который потребуется вам.

Рис. 3. XAML для создания формы

<Grid
  x:Name="ContentPanel"
  Grid.Row="1"
  Margin="24,0,0,0">
  <Grid.ColumnDefinitions>
    <ColumnDefinition
      Width="0.384*" />
    <ColumnDefinition
      Width="0.616*" />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition
      Height="0.1*" />
    <RowDefinition
      Height="0.1*" />
    <RowDefinition
      Height="0.1*" />
    <RowDefinition
      Height="0.1*" />
    <RowDefinition
      Height="0.1*" />
    <RowDefinition
      Height="0.1*" />
    <RowDefinition
      Height="0.2*" />
  </Grid.RowDefinitions>
  <TextBlock
    x:Name="NamePrompt"
    TextWrapping="Wrap"
    Text="Name"
    Grid.Row="0"
    HorizontalAlignment="Left"
    VerticalAlignment="Center" />
  <TextBlock
    x:Name="SexPrompt"
    Grid.Row="2"
    TextWrapping="Wrap"
    HorizontalAlignment="Left"
    VerticalAlignment="Center"
    Text="Sex" />
  <TextBlock
    x:Name="HeightPrompt"
    TextWrapping="Wrap"
    Text="Height, StringFormat=F3"
    HorizontalAlignment="Left"
    Grid.Row="3"
    d:LayoutOverrides="Height"
    VerticalAlignment="Center" />
  <TextBlock
    x:Name="FavoritePrompt"
    TextWrapping="Wrap"
    Text="Favorite"
    HorizontalAlignment="Left"
    Grid.Row="4"
    d:LayoutOverrides="Height"
    VerticalAlignment="Center" />
  <TextBox
    x:Name="Name"
    TextWrapping="Wrap"
    d:LayoutOverrides="Height"
    Grid.Column="1"
    HorizontalAlignment="Left"
    Width="200"
    VerticalAlignment="Center"
    Text="{Binding Name}" />
  <StackPanel
    x:Name="BeardStackPanel"
    Grid.ColumnSpan="2"
    Grid.Row="1"
    Orientation="Horizontal">
    <CheckBox
      x:Name="Moustache"
      Content="Moustache"
      HorizontalAlignment="Left"
      VerticalAlignment="Center"
      IsChecked="{Binding Moustache}" />
    <CheckBox
      x:Name="Goatee"
      Content="Goatee"
      IsChecked="{Binding Goatee}" />
    <CheckBox
      Content="Beard"
      IsChecked="{Binding Beard}"/>
  </StackPanel>
  <StackPanel
    x:Name="SexStackPanel"
    Grid.Column="1"
    Grid.Row="2"
    Orientation="Horizontal">
    <RadioButton
      x:Name="Male"
      Content="Male"
      IsChecked="True"
      GroupName="Sex" />
    <RadioButton
      x:Name="Female"
      Content="Female"
      GroupName="Sex" />
  </StackPanel>
  <StackPanel
    x:Name="HeightStackPanel"
    Grid.Column="1"
    Grid.Row="3"
    Orientation="Horizontal">
    <TextBlock
      TextWrapping="Wrap"
      Text="{Binding Height}"
      VerticalAlignment="Center"
      HorizontalAlignment="Left"
      Margin="0,0,0,0" />
    <TextBlock
      VerticalAlignment="Center"
      HorizontalAlignment="Left"
      Margin="5,0,0,0"
      Text="meters" />
  </StackPanel>
  <ToggleButton
    x:Name="Favorite"
    Content="Favorite"
    Grid.Column="1"
    Grid.Row="4"
    d:LayoutOverrides="Width, Height"
    HorizontalAlignment="Left"
    VerticalAlignment="Center"
    IsChecked="{Binding Favorite}" />
</Grid>

Связывание

Каждое поле для ввода текста теперь имеет свое значение, установленное с использованием синтаксиса связывания. Например, чтобы указать привязку TextBox, определите, какие из его атрибутов потребуют данные (в данном случае это атрибут Text), и используйте синтаксис связывания, показанный ранее.

Привязки задаются в фигурных скобках, и для них применяется ключевое слово Binding, за которым обычно следует имя свойства, с которым вы связываете данный атрибут. Например, в следующем XAML задается, что текст для TextBox будет приниматься из открытого свойства Name:

<TextBox
  x:Name="Name"
  TextWrapping="Wrap"
  d:LayoutOverrides="Height"
  Grid.Column="1"
  HorizontalAlignment="Left"
  Width="200"
  VerticalAlignment="Center"
  Text="{Binding Name}" />

Аналогично вы поступаете и с флажками (checkboxes), которые указывают наличие усов, — свойство IsChecked связывается с соответствующим свойством объекта-источника:

<CheckBox
  x:Name="Moustache"
  Content="Moustache"
  HorizontalAlignment="Left"
  VerticalAlignment="Center"
  IsChecked="{Binding Moustache}" />

Пока вам не известно, у какого объекта будут эти свойства (Name и Moustache). Как отмечалось ранее, объект, содержащий связываемое свойство (bindable property), называется DataContext. Он может быть каким угодно, но в данном случае вы создадите экземпляр класса Person, а затем укажете объект Person в качестве DataContext для всего представления.

Заметьте, что вы можете задать DataContext для контейнера, в данном случае для страницы, и все элементы управления внутри контейнера будут иметь этот общий DataContext — при этом вам ничто не мешает присвоить другие контексты данных одному или нескольким индивидуальным элементам управления.

Вы можете создать экземпляр Person в обработчике события Loaded страницы отделенного кода (codebehind page). Событие Loaded вызывается после загрузки страницы и инициализации ее элементов управления, как показано на рис. 4.

Рис. 4. Событие Loaded вызывается после загрузки страницы

private Person _currentPerson;
private Random _rand = new Random();
public MainPage()
{
  InitializeComponent();
  Loaded += MainPage_Loaded;
}
void MainPage_Loaded( object sender, RoutedEventArgs e )
{
  _currentPerson = new Person
  {
    Beard = false,
    Favorite = true,
    Goatee = false,
    Height = 1.86,
    Moustache = true,
    Name = "Jesse",
    WhichSex = Person.Sex.Male
  };
}

Теперь вы можете указать для каждого элемента управления в ContentPanel, что его DataContext — это только что созданный вами объект _currentPerson (в обработчике события Loaded):

ContentPanel.DataContext = _currentPerson;

Как только TextBox становится известен его DataContext, он может разрешить свойство Name и получить значение для отображения («Jesse»). То же самое относится к остальным элементам управления, каждый из которых связан с каким-либо свойством в новом объекте Person.

Запустите приложение и вы должны увидеть, что все поля связаны должным образом.

Смена DataContext

Чтобы довести до конца привязку, давайте создадим ряд объектов Person и будем отображать их по одному. Для этого модифицируйте MainPage.xaml.cs, чтобы создать список случайно создаваемых Person, а затем пройдите по списку, используя новую кнопку Next в UI, которую вы должны добавить в нижнюю строку:

<Button
  Name="Next"
  Content="Next"
  Grid.Row="5"
  HorizontalAlignment="Center"
  VerticalAlignment="Center" />

Как правило, я создаю свои UI в Expression Blend, а код пишу в Visual Studio.

Ниже приведен код, модифицированный для взаимодействия с кнопкой Next:

void MainPage_Loaded( object sender, RoutedEventArgs e )
{
  SetDataContext();
  Next.Click += Next_Click;
}
private void SetDataContext()
{
  ContentPanel.DataContext = GeneratePerson();
}
void Next_Click( object sender, RoutedEventArgs e )
{
  SetDataContext();
}

Заметьте, что DataContext должен быть установлен в обработчиках событий загрузки страницы и щелчка кнопки Next, поэтому я выделил эту операцию в отдельный метод — SetDataContext. Этот метод в свою очередь вызывает метод GeneratePerson, который отвечает за случайное создание объектов Person.

Теперь можно внести все изменения в отделенный код. Для начала отмените «зашитую» в код привязку к текущему Person и вместо этого указывайте привязку вызовом GeneratePerson.

Генерация случайного Person

Ниже показан весь метод GeneratePerson (заметьте, что я выделил задачу выбора true или false в метод FlipCoin):

private Person GeneratePerson()
{
  var newPerson = new Person
  {
    Beard = FlipCoin(),
    Favorite = FlipCoin(),
    Goatee = FlipCoin(),
    Height = _rand.NextDouble() + 1,
    Moustache = FlipCoin(),
    Name = names[_rand.Next(0, names.Count - 1)]
  };
  return newPerson;
}

FlipCoin использует генератор случайных чисел и в 50% случаев возвращает true:

private  bool FlipCoin()
{
  return _rand.Next( 1, 3 ) % 2 == 0;
}

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

private readonly List<string> names = new List<string>()
{
  "Stacey",
  "Robbie",
  "Jess",
  "Robin",
  "Syd",
  "J.J.",
  "Terri",
  "Moonunit",
};

Запустите приложение и щелкните кнопку Next. По мере создания каждый объект Person устанавливается как DataContext, а его свойства связываются с UI-элементами.

INotifyPropertyChanged

Что будет, если одно из свойств в объекте Person изменяется? Это может происходить с высокой вероятностью, если данный объект содержится в базе данных и к нему могут обращаться другие пользователи. Вы наверняка захотите, чтобы ваш UI соответственно обновлялся.

Для этого ваш класс (используемый в качестве DataContext) должен реализовать INotifyPropertyChanged — простой интерфейс, позволяющий каждому свойству при изменении его значения уведомлять UI. Обычно создается вспомогательный метод, который проверяет, чтобы на соответствующее событие был зарегистрирован минимум один метод. Если такой метод есть, вспомогательный метод генерирует событие, передавая имя свойства, значение которого изменилось.

Чтобы увидеть это в действии, добавьте новую кнопку в UI — Change. При щелчке кнопки Change изменяйте значение свойства Name на «Jacob»:

void Change_Click( object sender, RoutedEventArgs e )
{
  _currentPerson.Name = "Jacob";
}

Это не будет иметь никакого эффекта, пока вы не реализуете в Person интерфейс INotifyPropertyChanged и пока свойство Name не будет генерировать событие PropertyChanged (рис. 5).

Рис. 5. Интерфейс INotifyPropertyChanged

public class Person : INotifyPropertyChanged
{
  public string _name;
  public string Name
  {
    get { return _name; }
    set
    {
      _name = value;
      PropChanged( "Name" );
    }
  }
// Другие свойства
  public event PropertyChangedEventHandler PropertyChanged;
  private void PropChanged(string propName)
  {
    if (PropertyChanged != null)
    {
      PropertyChanged( this, new PropertyChangedEventArgs( propName ) );
    }
  }
}

После этого, когда вы щелкаете кнопку Change, имя, показываемое в UI, будет меняться на новое («Jacob»).

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

Двухстороннее связывание

Как быть, если пользователь взаимодействует с UI и напрямую изменяет в нем какое-либо значение (например, вводит новое имя в поле ввода Name)? Обычно в таких случаях нужно передавать это значение обратно в нижележащие данные (объект DataContext). Для этого применяется двухстороннее связывание (two-way binding).

Чтобы модифицировать программу на использование двухстороннего связывания для свойства Name, найдите привязку Name и измените ее так:

{Для верстки: сохраните в следующем листинге выделение п/ж}

<TextBox
  x:Name="Name"
  TextWrapping="Wrap"
  d:LayoutOverrides="Height"
  Grid.Column="1"
  HorizontalAlignment="Left"
  Width="200"
  VerticalAlignment="Center"
  Text="{Binding Name, Mode=TwoWay}" />

Существует три режима связывания.

  1. Одностороннее связывание, при котором данные никогда не обновляются, даже если эти исходные данные изменяются пользователем.
  2. Одностороннее связывание по умолчанию, при котором данные извлекаются из источника и при изменении обновляются в UI, но не передаются обратно в источник.
  3. Двухстороннее связывание, при котором становится возможным полноценный обмен данными между источником и UI.

Привязка элемента

Переместите кнопку Next в шестую строку, а в пятую строку перетащите элемент управления Slider. У этого элемента управления есть ряд важных свойств, в том числе Minimum, Maximum, Value, LargeChange и SmallChange. Они отражены в коде на рис. 6.

Рис. 6. Добавление Slider

<Slider
  x:Name="Likability"
  Grid.Row="5"
  Grid.Column="0"
  BorderBrush="White"
  BorderThickness="1"
  Background="White"
  Foreground="Blue"
  LargeChange="10"
  SmallChange="1"
  Minimum="0"
  Width="199"
  Maximum="100"
  Value="50"
  Height="90" />

Minimum и Maximum задают диапазон для линейки с ползунком. В данном случае я использую проценты и поэтому установил эти свойства соответственно в 0 и 100.

Value — это текущее значение позиции ползунка, и оно всегда будет укладываться в диапазон от Minimum до Maximum.

LargeChange и SmallChange во многом используются так же, как и на полосах прокрутки; они соответственно указывают, как поведет себя ползунок при щелчках на линейке и при использовании другого элемента управления, например кнопок-стрелок.

Настройка элементов управления TextBlock

В правом столбце вы будете использовать три TextBlock; первый и третий будут представлять собой фиксированные метки (со значениями «Likeability:» и «%» соответственно). Средний TextBlock отображает числовое представление позиции ползунка на линейке.

С этой целью свяжите свойство Text Value среднего TextBlock со свойством Value линейки с ползунком, обозначив каждый связываемый элемент ключевым словом ElementName, как показано на рис. 7.

Рис. 7. Связывание свойства Value элемента TextBlock

<StackPanel
  x:Name="LikeabilityPercentStackPanel"
  Grid.Row="5"
  Grid.Column="1"
  Orientation="Horizontal">
  <TextBlock
    Text="Likeability: "
    HorizontalAlignment="Left"
    VerticalAlignment="Center"
    Margin="20,0,5,0" />
  <TextBlock
    x:Name="SliderValue"
    Text="{Binding Value, ElementName=Likeability,
      StringFormat=F3}"
    HorizontalAlignment="Left"
    VerticalAlignment="Center"
    Margin="5,0,0,0"/>
  <TextBlock
    Text="%"
    HorizontalAlignment="Left"
    VerticalAlignment="Center" />
</StackPanel>

Запустите программу. При перемещении ползунка на линейке значение в TextBlock мгновенно обновляется.

Связывание с данными позволяет создавать весьма функциональные приложения Windows Phone, способные надежно управлять зависимостью между нижележащими данными и элементами управления/представлениями, которые отображают эти данные.

Конвертеры данных

Некоторые свойства без преобразований не удастся должным образом связать с конкретным UI-элементом или вам может потребоваться больший контроль над тем, как отображается значение. В качестве простого примера попробуем показывать дату рождения, сместив кнопки на строку вниз и вставив строку с текстом «Birth date» и значением свойства BirthDate объекта Person.

Для этого нужно модифицировать метод GeneratePerson в MainPage.xaml.cs так, чтобы он генерировал допустимые BirthDate. А с этой целью добавьте строку кода, которая создает случайное значение BirthDate в пределах последних 20 лет:

BirthDate = DateTime.Now - TimeSpan.FromDays(_rand.Next(1,365*20)),

Если вы осуществили привязку свойства BirthDate, то увидите дату и время рождения. Но время нам не требуется — только дата в кратком формате. Для этого потребуется DataConverter.

DataConverter — это классы, реализующие интерфейс IValueConverter. Данный интерфейс требует реализации двух методов (рис. 8).

Рис. 8. Интерфейс IValueConverter

public object Convert(
  object value,
  Type targetType,
  object parameter,
  System.Globalization.CultureInfo culture )
{
  throw new NotImplementedException();
}
public object ConvertBack(
  object value,
  Type targetType,
  object parameter,
  System.Globalization.CultureInfo culture )
{
  throw new NotImplementedException();
}

В нашем примере достаточно первого из двух методов (второй не будет вызываться). Этот метод довольно прост в реализации: проверяйте, чтобы целевой тип был строкой, а тип значения — DateTime. Если эти условия соблюдены, принимайте значение, преобразовывайте его в DateTime, а затем вызывайте ToShortDateString, как показано на рис. 9.

Рис. 9. Метод Convert для перевода DateTime в краткий формат

public object Convert(
  object value,
  Type targetType,
  object parameter,
  System.Globalization.CultureInfo culture )
{
  if (targetType == typeof( string ) &&
    value.GetType() == typeof( DateTime ))
  {
    return (( DateTime ) value).ToShortDateString();
  }
  else  // не удалось преобразовать
  {
    return value;
   }
}
public object ConvertBack(
  object value,
  Type targetType,
  object parameter,
  System.Globalization.CultureInfo culture )
{
  throw new NotImplementedException();
}

Теперь нам нужен какой-то способ, с помощью которого можно было бы обращаться из XAML к конвертеру значений. Для этого конвертер можно сделать ресурсом. Откройте App.xaml и добавьте пространство имен для своего конвертера на основе пространства имен приложения:

xmlns:mine="clr-namespace:DataBinding">

Далее в том же файле найдите раздел <Application.Resource> и добавьте ресурс для своего конвертера:

<Application.Resources>
  <mine:DateConverter   x:Key="dateConverter" />
</Application.Resources>

Этот ключ (key) теперь можно использовать в вашем XAML-файле. Обновите Binding для BirthDate, чтобы задействовать данный ресурс:

{Для верстки: сохраните в следующем листинге выделение п/ж}

<TextBlock
  Grid.Row="6"
  Grid.Column="1"
  VerticalAlignment="Center"
  Text="{Binding BirthDate, Converter={
    StaticResource dateConverter}}" />

Запустите программу и вы должны увидеть дату, отображаемую в кратком формате.

Заключение

Связывание с данными позволяет создавать весьма функциональные приложения Windows Phone, способные надежно управлять зависимостью между нижележащими данными и элементами управления/представлениями, которые отображают эти данные. В этой статье я объяснил, как создавать одно- и двухстороннюю привязку данных, как осуществлять связывание с элементами и использовать конвертеры для приведения данных к нужному вам формату.

Автор: Джесси Либерти  •  Иcточник: MSDN Magazine  •  Опубликована: 03.08.2012
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   Windows Phone.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.