Использование DataBinding и DataTemplate при помощи Expression Blend в Silverlight

ОГЛАВЛЕНИЕ

Данная статья детально изучает привязку данных к элементам управления и в ней мы конкретно рассмотрим две темы, представляющие наибольший интерес:

  • Привязка сложных объектов к элементам управления типа List
  • Использование Expression Blend в противоположность к привязке вручную в Xaml

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

Финальный продукт

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

Рисунок 7-1. ListBox с данными о книгах

Рисунок 7-1 демонстрирует список книг, отображая название в красном цвете с типом шрифта Comic Sans MS и размером в 16 точек, при этом остальная часть строки форматирована в шрифте Verdana черного цвета и 14-ым размером. Пока мы не знаем, как это реализовано, а также мы не в курсе об источнике данной информации (названии книги, ISBN или ценее).

Все станет более интересным, когда вы нажмете на запись о какой-то определенной книге в списке , как это показано на рисунке 7-2

Рисунок 7-2. Детали о книге

Когда пользователь нажимает на книгу, то запись о ней подсвечивается, и список в буквальном смысле уменьшается для того, чтобы предоставить немного места для отображения информации о книге. Теперь нам видно 6 строк информации о книге, включая список авторов, привязанный к более мелкому списковому полю, несколько строк со сроковыми и численными переменными, а также рейтингом, отображенным в TextBlock в виде ползунка.  


Использование Expression Blend вместе с Visual Studio

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

Создание проекта

Для начала создайте новый проект в Blend, назвав его Silverlight Data, как это показано на рисунке 7-3

Рисунок 7-3. Создание нового проекта в Blend

Как только вы создадите проект, установите размеры внешнего элемента управления в 680 x 550 и создайте три строки, как это показано на рисунке 7-4

Рисунок 7-4. Создание трех строк в Blend

Если вы не привыкли создавать строки в Blend, тогда вам стоит прочитать статью про Expression Blend for Developers.

Обратите внимание на то, что верхняя строка имеет фиксированную высоту, 1. Если вы хотите установить точные размеры, то вам стоит открыть окошко Xaml и установить их вручную, хотя это не так обязательно:

<Grid x:Name="LayoutRoot" Background="White" >
   <Grid.RowDefinitions>
      <RowDefinition Height="90"/>
      <RowDefinition Height="200"/>
      <RowDefinition Height="*"/>
   </Grid.RowDefinitions> 

Добавьте заголовок к верхней строке перетащив TextBlock и установив его свойства, как это показано на рисунке 7-5

Рисунок 7-5. Установка свойств заголовка 

Перетаскивание элемента управления ListBox во вторую строку

Перетащите элемент ListBox из панели Assets во вторую строку и установите его свойства выравнивания (Alignments) в значение stretch и отступы в 25,50,25,50 ,как это показано на рисунке 7-6

Рисунок 7-6. Добавление и позиционирование элемента управления ListBox 

Заполнение элемента ListBox жестко заданными строками

Одним из способов разделения настройки внешнего вида элементов ListBoxItems от привязки ListBoxItems будет жесткое задание некоторых значений при помощи ListBoxItem Collection Editor (Редактор набора). Это займет всего пару минут. Вот как это можно реализовать:

Начните с щелчка по ListBox в панели Interaction и затем нажмите на Item (Collection) в закладке Common Properties (Основные свойства), как это показано на рисунке 7-7

Рисунок 7-7. Добавление в набор элементов

Это откроет довольно пугающий редактор наборов (Collection Editor), но паниковать не стоит. Нажмите по кнопке, которая гласит "Add another item" (Добавление нового элемента). Также, вам не стоит паниковать при появлении пустого диалогового окна. Щелкните по кнопке с независимой фиксацией "Show System Assemblies".

На самом деле мы уже сделали многое. Теперь вам надо отыскать то, что вам нужно - введите ListBoxItem в поле поиска, как это показано на рисунке 7-8

Рисунок 7-8. Выбор класса, добавляемого к набору списка

Щелкните по ListBoxItem -- и откроется специальный редактор, который позволит вам отредактировать содержимое ListBoxItem, что дает вам немалый контроль над внешним видом элементов, добавляемых вручную,

Рисунок 7-9. Property Editor for ListBoxItem

Заполните свойства для вашего первого элемента List Box, установив текст так, как это показано на рисунке 7-9. Как только вы добавили три - четыре элемента, нажмите OK --и вы вернетесь на страницу, но список уже не будет пустым. Запустите программу, и вы увидите свои значения, жестко заданные в элементе ListBox, как это показано на рисунке 7-10

Рисунок 7-10. Жестко заданные элементы ListBox 


Реализация приложения и его интерфейса

Нашей целью является создание n-уровнего приложения, где у нас будет три основных уровня:

  • Уровень пользовательского интерфейса
  • Бизнес уровень
  • Уровень хранилища данных

Уровень пользовательского интерфейса является той частью, которую видит пользователь и с которой он взаимодействует - мы создадим данный уровень в Expression Blend при помощи элементов управления Silverlight.

Бизнес-уровень управляет логикой вашего приложения - мы создадим его в Visual Studio при помощи C#.  

Для данного приложения бизнес-уровень будет инкапсулирован в три относительно простых класса:

  1. Library представляет собой набор книг, полученных из более объемного набора. Больший набор может быть представлен в виде базы данных, веб-сервиса либо какого нибудь другого механизма хранения.
  2. Book является неким уровнем представления книги, хотя он может также представлять другие версии печати. Он содержит такие свойства, как автор (author), заголовок (title), издатель (publisher), дату публикации (publication date).
  3. Author представляет собой то, что нам необходимо знать об авторе книги. Автор также необходим для книги, поэтому он вынесен в отдельный класс.

Нашими целями являются:

  1. Привязка данных (Data Binding): привязка элементов списка к набору Books в Library
  2. Шаблон данных (Data Template): указание элементу ListBox как отображать (некоторые) свойства каждой книги в списке

Создание классов данных

Начните с удаления ListBox (и его содержимого) из LayoutRoot (мы сделаем другой по ходу статьи).

Blend не самое лучшее место для создания классов бизнес-уровня, поэтому щелкните по File-Save all (Файл - Сохранить все)и затем щелкните правой кнопкой мыши по приложению и выберите Edit in Visual Studio (Редактировать в Visual Studio), как это показано на рисунке 7-11.

 

Рисунок 7-11. Переход от Blend к Visual Studio для создания классов

Когда откроется Visual Studio, вам необходимо будет создать три класса, как это уже оговаривалось. Ниже вам предоставлен полный код, который мы разберем чуть позже.

// Author.cs
public class Author
{
  private string privateName;
  public string Name
  {
  get
  {
  return privateName;
  }
  set
  {
  privateName = value;
  }
  }

  // Book.cs
  public class Book : INotifyPropertyChanged
  {
  public event PropertyChangedEventHandler PropertyChanged;

  public Book()
  {

  }

  public Book(string title, ObservableCollection authorList, double coverPrice, string isbn10, string publisher, int edition, int printing, string pubYear, double rating)
  {

  this.Title = title;

  this.Authors = new ObservableCollection();
  foreach (Author auth in authorList)
  {
  this.Authors.Add(auth);
  }

  this.CoverPrice = coverPrice;
  this.ISBN10 = isbn10;
  this.Publisher = publisher;
  this.Edition = edition;
  this.Printing = printing;
  this.PubYear = pubYear;
  this.Rating = rating;

  }

  private string privateTitle;
  public string Title
  {

  get
  {
  return privateTitle;
  }
  set
  {
  privateTitle = value;
  NotifyPropertyChanged("Title");
  }
  }

  public string NumAuthors
  {
  get
  {
  return privateAuthors.Count.ToString();
  }
  }

  private ObservableCollection privateAuthors;
  public ObservableCollection Authors
  {
  get
  {
  return privateAuthors;
  }
  internal set
  {
  privateAuthors = value;
  NotifyPropertyChanged("Authors");
  }
  }

  private double privateCoverPrice;
  public double CoverPrice
  {
  get
  {
  return privateCoverPrice;
  }
  set
  {
  privateCoverPrice = value;
  NotifyPropertyChanged("CoverPrice");
  }
  }

  private string privateISBN10;
  public string ISBN10
  {
  get
  {
  return privateISBN10;
  }
  set
  {
  privateISBN10 = value;
  NotifyPropertyChanged("ISBN10");
  }
  }

  private string privatePublisher;
  public string Publisher
  {
  get
  {
  return privatePublisher;
  }
  set
  {
  privatePublisher = value;
  NotifyPropertyChanged("Publisher");
  }
  }

  private int privateEdition;
  public int Edition
  {
  get
  {
  return privateEdition;
  }
  set
  {
  privateEdition = value;
  NotifyPropertyChanged("Edition");
  }
  }

  private int privatePrinting;
  public int Printing
  {
  get
  {
  return privatePrinting;
  }
  set
  {
  privatePrinting = value;
  NotifyPropertyChanged("Printing");
  }
  }

  private string privatePubYear;
  public string PubYear
  {
  get
  {
  return privatePubYear;
  }
  set
  {
  privatePubYear = value;
  NotifyPropertyChanged("PubYear");
  }
  }

  private double privateRating;
  public double Rating
  {
  get
  {
  return privateRating;
  }
  set
  {
  privateRating = value;
  NotifyPropertyChanged("Rating");
  }
  }

  private void NotifyPropertyChanged(string propertyName)
  {
  if (PropertyChanged != null)
  PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }
  }
}

// Library.cs
public class Library : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  public Library()
  {
  PrivateName = string.Empty;
  PrivateBooks = new ObservableCollection();

  if (HtmlPage.IsEnabled == false)
  {
  GenerateDummyData();
  }

  }

  public void GenerateDummyData()
  {
  Books = new ObservableCollection();

  ObservableCollection Authors = new ObservableCollection();
  Author Author1 = new Author();
  Author Author2 = new Author();
  Author1.Name = "Jesse Liberty";
  Author2.Name = "Mark Twain";
  Authors.Add(Author1);
  Authors.Add(Author2);

  Book newBook = new Book("Programming Silverlight", Authors, 49.99, "0123456789", "O'Reilly", 1, 1, "2000", 5.0);
  Books.Add(newBook);

  Author2.Name = "Cormac McCarthy";
  newBook = new Book("Programming Without Shoes", Authors, 149.99, "0123456789", "O'Reilly Media", 1, 1, "1955", 4.5);
  Books.Add(newBook);

  Author2.Name = "Ian McEwan";
  newBook = new Book("Programming Without A Spec", Authors, 149.99, "0123456789", "O'Reilly Media", 1, 1, "1955", 4.3);
  Books.Add(newBook);

  Author2.Name = "Stephen King";
  Books.Add(new Book("Programming Without A Conscience", Authors, 149.99, "0123456789", "O'Reilly Media", 1, 1, "1955", 4.1));
  }

  private string PrivateQuery;
  public string Query
  {
  get
  {
  return PrivateQuery;
  }
  set
  {
  PrivateQuery = value;
  if (PropertyChanged != null)
  PropertyChanged(this, new PropertyChangedEventArgs("Query"));
  }
  }

  private string PrivateName;
  public string Name
  {
  get
  {
  return PrivateName;
  }
  set
  {
  PrivateName = value;
  if (PropertyChanged != null)
  PropertyChanged(this, new PropertyChangedEventArgs("Name"));
  }
  }

  private ObservableCollection PrivateBooks;  
  public ObservableCollection Books
  {
  get
  {
  if (PrivateBooks.Count < 1)
  {
  GetData();
  }
  return PrivateBooks;
  }
  set
  {
  PrivateBooks = value;
  if (PropertyChanged != null)
  PropertyChanged(this, new PropertyChangedEventArgs("Books"));
  }
  }

  public void GetData()
  {
  Name = "Liberty Books";
  ObservableCollection Authors = new ObservableCollection();
  Author Author1 = new Author();
  Author Author2 = new Author();
  Author Author3 = new Author();

  Author1.Name = "Jesse Liberty";
  Author2.Name = "Tim Heurer";
  Authors.Add(Author1);
  Authors.Add(Author2);

  Book newBook = new Book("Programming Silverlight", Authors, 49.99, "TBD", "O'Reilly Media", 1, 1, "2009", 5.0);

  PrivateBooks.Add(newBook);

  Authors.Remove(Author2);
  Author2 = new Author();
  Author2.Name = "Alex Horovitz";
  Authors.Add(Author2);

  newBook = new Book("Programming .NET 3.5", Authors, 49.99, "0-596-51039-X", "O'Reilly Media", 1, 2, "2008", 4.7);
  PrivateBooks.Add(newBook);

  Authors.Remove(Author2);
  Author2 = new Author();
  Author2.Name = "Dan Hurwitz";
  Author3.Name = "Brian Macdonald";
  Authors.Add(Author2);
  Authors.Add(Author3);

  newBook = new Book("Learning ASP.NET 3.5", Authors, 44.99, "0-596-51845-5", "O'Reilly Media", 2, 1, "2008", 4.8);
  PrivateBooks.Add(newBook);

  Authors.Remove(Author2);
  Authors.Remove(Author3);
  Author2 = new Author();
  Author2.Name = "Donald Xie";
  Authors.Add(Author2);
  PrivateBooks.Add(new Book("Programming C# 3.0", Authors, 44.99, "0-596-51845-5", "O'Reilly Media", 5, 2, "2008", 4.3));

  Authors.Remove(Author2);
  Author2 = new Author();
  Author2.Name = "Dan Hurwitz";
  Authors.Add(Author2);
  PrivateBooks.Add(new Book("Programming .NET Windows Apps", Authors, 49.95, "0596003218", "O'Reilly Media", 1, 1, "2003", 3.7));

  Authors.Remove(Author2);
  Author2 = new Author();
  Author2.Name = "Brad Jones";
  Authors.Add(Author2);
  PrivateBooks.Add(new Book("Teach Yourself C++ in 1 Hour", Authors, 44.99, "0672327112", "Sams", 6, 1, "2008", 2.9));

  Authors.Remove(Author2);
  Author2 = new Author();
  Author2.Name = "David Horvath";
  Authors.Add(Author2);
  PrivateBooks.Add(new Book("Teach Yourself C++ in 24 Hours", Authors, 34.99, "0672326817", "Sams", 4, 1, "2004", 2.4));
   
  Authors.Remove(Author2);
  PrivateBooks.Add(new Book("Programming VB.NET", Authors, 39.95, "0596004389 ", "O'Reilly Media", 2, 1, "2003", 3.2));
  PrivateBooks.Add(new Book("Visual C# 2005 Dev Notebook", Authors, 29.95, "059600799X", "O'Reilly Media", 1, 1, "2005", 3.7));
  PrivateBooks.Add(new Book("Clouds To Code", Authors, 41.95, "1861000952", "Wrox", 1, 1, "1997", 4.1));

  }
}

Сохраните проект в Visual Studio и до того, как вы вернетесь в Blend давайте распакуем данные классы и полностью в них разберемся.

ObservableCollection и INotifyPropertyChanged

Вы наверняка заметили следующие объявления,

public class Library : INotifyPropertyChanged
{
   //...
   private ObservableCollection PrivateBooks;

public class Book : INotifyPropertyChanged
{
   //....
   private ObservableCollection privateAuthors;

Данный набор более похож на список, за исключением того, что он реализовывает событие INotifyPropertyChanged, запуская данное событие при каждом изменении набора. Это делается без каких-либо усилий и наиболее полезно, потому-то все пользовательские элементы управления (UIControls) имеют в себе встроенный код регистрации для данного события. Если необходимо обновлять элементы управления при изменениях в нижележащем наборе, то вам не надо писать и строки кода - это все делается автоматически, что очень удобно.

Класс Library содержит конструктор, который всего лишь устанавливает в пустое значение и определяет место для пустого просматриваемого набора объектов типа Book.

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

if (HtmlPage.IsEnabled == false)
{
   GenerateDummyData();

За конструктором Library следуют два свойства - Name и Books, где последний является очень умным набором объектов observableCollection. Умный он потому, что когда вы пытаетесь вернуть набор, он проверяет существование объектов Book, и если их нет, то он вызывает вспомогательный метод GetBooks

public ObservableCollection<Book> Books
{
  get
  {
   if (PrivateBooks.Count < 1)
   {
    GetData();
   }
   return PrivateBooks;
  }
   set
   {
     PrivateBooks = value;
     if (PropertyChanged != null)
       PropertyChanged(this, new PropertyChangedEventArgs("Books"));
   }

GetBooks свободно может перейти к веб-сервису и выполнить запрос или считать книги из XML-файла, или же вообще делать все что хочет. Тем не менее, он заполнит набор PrivateBooks выбранными объектами Book. Это удобно, потому что позволяет нам сфокусироваться на привязке данных, а не на получении объектов Book.


Привязка бизнес-объекта к элементу управления Listbox

Сохраните все изменения и вернитесь к проекту в Blend, где вы будете предупреждены об изменениях в проекте (ведь он изменился), как это показано на рисунке 7-12. Ответьте положительно о том, что вы хотите его перезагрузить (нажав на кнопку "yes").

Рисунок 7-12. Перезагрузка Blend

Оказывается привязка набора Books к ListBox является простейшей процедурой и вот как она реализовывается. Найдите кнопку "+Clr Object" в закладке Data (обычно она расположена под панелями Project/Properties/Resources). Щелкните по ней.

Рисунок 7-13. Щелчок по CLR Object в закладке Data

Это откроет окошко добавления источника данных для Add CLR объекта ("Add CLR Object Data Source"), которое отобразит все потенциальные объекты источников данных в вашем проекте. Отсюда вы можете выбрать Library в качестве источника данных, как это показано на рисунке 7-14

Рисунок 7-14. Привязка класса Library в качестве источника данных

Как только был выбран Library, результат будет отображен в окошке Data, и щелкнув по стрелочке рядом с Library вы увидите свойства, включающие в себя свойство Books, как это показано на рисунке 7-14. Перетащите объект Books в Listbox!

Рисунок 7-15. Объект Books в окошке Data

Когда вы отпустите объект вас спросят о привязке набора Books к элементу ListBox, как это показано на рисунке 7-16 

Рисунок 7-16. Привязка набора Books к ListBox

Как только вы решите привязать набор Books к ListBox, вас спросят о поле, по которому необходимо осуществить привязку. В случае с ListBox по умолчанию будет предложено привязать список источника к ItemsSource, как это показано на рисунке 7-17

Рисунок 7-17. Создание привязки данных

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

Рисунок 7-18. Отображение книг

Проблема, которая присутствует на рисунке 7-17, это то, что вы привязываете к очень сложному объекту (a Book), который имеет много свойств. Пока что ему совсем неизвестно то, что вы собираетесь отображать, поэтому он отображает тип объекта. Все, что вам необходимо представить, так это шаблон для каждой строки, и затем оно осуществит проход по всему набору, применяя ваш шаблон к каждому элементу. Вы можете написать шаблон вручную либо в Blend.


Написание шаблона ListItemTemplate вручную

Написать шаблон для элементов списка не так сложно. Для этого откройте проект Visual Studio и найдите ListBox в Page.xaml, где вы заметите, что он также имеет закрывающий тег.

<ListBox x:Name="BookListBox"
   Margin="50,25,50,25"
   Grid.Row="1" 
   HorizontalAlignment="Stretch"
   VerticalAlignment="Stretch"
   ItemsSource="{Binding Mode=OneWay, Path=Books,
   Source={StaticResource LibraryDS}}">
 </ListBox> 

Между открывающим и закрывающим тегом мы расположим ListBox.ItemTemplate, а в его пределах DataTemplate.

<ListBox x:Name="BookListBox"
   Margin="50,25,50,25"
   Grid.Row="1" 
   HorizontalAlignment="Stretch"
   VerticalAlignment="Stretch"
   ItemsSource="{Binding Mode=OneWay, Path=Books,
   Source={StaticResource LibraryDS}}">
     <ListBox.ItemTemplate>
       <DataTemplate> 
       </DataTemplate>         
     </ListBox.ItemTemplate>
   </ListBox> 

Теперь нам остается всего лишь заполнить DataTemplate элементами управления, которые вы хотите использовать для того, чтобы привязать к свойствам каждой книги. Обычно начинают с какого-либо элемента управления внешним видом. Я решил использовать StackPanel и 5 текстовых полей для простоты: первое, третье и пятое я буду использовать для того, чтобы привязать к свойствам каждой книги, а оставшиеся два -- в качестве подсказок.

<ListBox.ItemTemplate>
   <DataTemplate>
     <StackPanel x:Name="DisplayListData"
     Orientation="Horizontal"
     VerticalAlignment="Bottom"
     Margin="5" >
       <TextBlock x:Name="Title"
       Text="{Binding Title}"
       Margin="5,0,0,0"
       VerticalAlignment="Bottom"
       HorizontalAlignment="Left"
       FontFamily="Comic Sans MS"
       FontSize="18" />
       
       <TextBlock x:Name="ISBNPrompt"
       Text="(ISBN: " 
       Margin="10,0,0,0"
       VerticalAlignment="Bottom"
       HorizontalAlignment="Left"
       FontFamily="Verdana"
       FontSize="14" />
       
       <TextBlock x:Name="ISBN"
       Text="{Binding ISBN10}" 
       VerticalAlignment="Bottom"
       HorizontalAlignment="Left"
       FontFamily="Verdana"
       FontSize="14" />
       
       <TextBlock x:Name="CoverPrompt"
       Text=") Cover Price: $" 
       VerticalAlignment="Bottom"
       HorizontalAlignment="Left"
       FontFamily="Verdana"
       FontSize="14" />
       
       <TextBlock x:Name="Cover"
       Text="{Binding CoverPrice}" 
       VerticalAlignment="Bottom"
       HorizontalAlignment="Left"
       FontFamily="Verdana"
       FontSize="14" />
     </StackPanel>
   </DataTemplate>        
</ListBox.ItemTemplate> 

В шаблоне присутствует очень простой StackPanel, а в его пределах - пять простых TextBlock. У первого размер шрифта равен 16 и сам шрифт типа Comic Sans MS, а все остальные имеют шрифт Verdana 14-го размера. Первый, третий и пятый элементы привязаны к свойствам Title, ISBN10 и CoverPrice соответственно. При запуске ListBox будет выглядеть следующим образом 

Рисунок 7-19. Запущенное приложение, обладающее ListItemTemplate


Создание шаблона для элементов списка в Blend

До того, как мы продолжим, давайте удалим шаблон для элементов списка и заново создадим его в Blend. Мы просто удалим его и заново закроем ListBox. Затем мы заново обработаем приложение и откроем его в Blend. В Blend мы щелкнем на ListBox и затем выберем Object > EditOtherTemplates > EditItemTemplate > Create Empty, как это показано на рисунке 7-20

Рисунок 7-20. Создание ListItemTemplate в Blend

Как только вы выберете данный пункт, вам предоставят диалоговое окно ресурсов шаблона данных (DataTmeplate Resource), как показано на рисунке 7-21, где вы можете решить, стоит ли вам создавать шаблон данных для всего приложения, или же для конкретного документа (чаще всего выбирается стандартная опция сохранения шаблона в документе)

Рисунок 7-21. Диалоговое окно создания DataTemplate (Create DataTemplate)

Обратите внимание на то, что в бета-версии существует ошибка и если вы откроете нижний выпадающий список и выберете ListBox вместо стандартного “user Control”, то ваш код будет скомпилированневерно

Нажав на кнопку OK, вы создадите пустой шаблон, который затем сможете заполнить, как и любой другой элемент пользовательского интерфейса (более того, Blend начинает с элемента Grid). Мы добавим элемент StackPanel к нему , как это показано на рисунке 7-22. Расположив StackPanel, мы можем перетащить текстовые поля точно так же, как мы бы сделали это с любыми другими элементами управления.

Рисунок 7-22. Создание DataStackPanel для ListItem

При создании шаблона элементов списка нам необходимо привязать набор Books в качестве источника данных к текстовым полям, а также поля к свойствам класса Book, как это сделано на рисунке 7-23

Рисунок 7-23. Привязка свойств класса Book

Обратите внимание на то, что вы создаете BookDS точно так же, как вы создали LibraryDS, нажав на кнопку +CLR object. 


Реализация LibraryList_SelectionChanged

Когда пользователь щелкает по книге, вызывается обработчик события LibraryList_SelectionChanged, который выполнит следующие действия:

  1. Изменение размера списка, путем уменьшения его для отображения одной строки
  2. Установка видимости табличной сетки деталей
  3. Установка объекта Book (selectedBook) в значение книги, выбранной пользователем
  4. Установка dataContext для всех элементов управления для выбранной книги путем установки DataContext в DetailsGrid
  5. Вызов события PropertyChanged из Book для того, чтобы обновить пользовательский интерфейс.

Создание второго элемента Grid

Вернитесь в Blend и скройте список LibraryList щелкнув по иконке в виде глаза в закладке Objects and Timeline, как это показано на рисунке 7-24

Рисунок 7-24. Сокрытие LibraryList

Вставтье табличную сетку, а именно DetailsGrid, в третью строку существующей табличной сетки и используйте свойства stretch и margin (=0) для того, чтобы новая табличная сетка занимала полностью строку. Разделите DetailsGrid на шесть строк и две колонки и добавьте элементы управления, как это показано на рисунке 7-25

Рисунок 7-25. DetailsGrid, разделенный на шесть строк и две колонки

Привязка элементов управления в табличной сетке DetailsGrid

В правой части DetailsGrid в шести строках расположены восемь элементов управления, хотя на рисунке 7-24 шесть из них не видны (это элементы TextBlock без текста). Самым легким путем описания их позиции, атрибутов и привязки будет демонстрация Xaml, хотя их создавать вы будете не в Xaml, а скорее перетащив элементы TextBlock в Artboard и установив их свойства.

Давайте предположим следующее:

VerticalAlignment=”Bottom”
HorizontalAlignment=”Right”
FontFamaily=”Verdana”
FontSize=”18”
FontWeight="Medium" 

Хотя Xaml гласит иначе.

<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" >
   <TextBlock x:Name="NumAuthorsPrompt" 
   Text="{Binding NumAuthors, Mode=OneWay}"
   Margin="0,0,15,15"/>
   <TextBlock x:Name="AuthorsPrompt" Text="Authors"
   Margin="0,0,15,15"/>
</StackPanel>
          
<ListBox x:Name="AuthorsListBox"
  Height="Auto" Width="Auto"
  HorizontalAlignment="Stretch"
  VerticalAlignment="Stretch"
  Grid.Column="1" Grid.Row="0" Margin="10,0,20,0"
  ItemsSource="{Binding Authors, Mode=OneWay}">
   <ListBox.ItemTemplate>
     <DataTemplate>
       <StackPanel Orientation="Horizontal" Margin="5">
         <TextBlock
           FontSize="14"
           Foreground="Black"
           Text="{Binding Name}"/>
       </StackPanel>
     </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>
      
<TextBlock x:Name="PublisherPrompt" Grid.Row="1" Grid.Column="0"
Text="Publisher" Margin="0,0,15,15"/>
          
<TextBlock x:Name="Publisher" Grid.Row="1" Grid.Column="1"
HorizontalAlignment="Left" Height="Auto" Width="Auto" Margin="10,0,15,15"
Text="{Binding Publisher}" />
   
<TextBlock x:Name="EditionPrompt" Grid.Row="2" Grid.Column="0"
Text="Edition" Margin="0,0,15,15"/>

<TextBlock x:Name="Edition" Grid.Row="2" Grid.Column="1"
HorizontalAlignment="Left" Height="Auto" Width="Auto" Margin="10,0,15,15"
Text="{Binding Edition}" />
  
<TextBlock x:Name="PrintingPrompt" Grid.Row="3" Grid.Column="0"
Text="Printing" Margin="0,0,15,15"/>

<TextBlock x:Name="Printing" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
Height="Auto" Width="Auto" Margin="10,0,15,15"  
Text="{Binding Printing}" />

<TextBlock x:Name="YearPrompt" Grid.Row="4" Grid.Column="0"
Text="Publication Year" Margin="0,0,15,15"/>

<TextBlock x:Name="Year" Grid.Row="4" Grid.Column="1"
HorizontalAlignment="Left" Height="Auto" Width="Auto"
Margin="10,0,15,15" Text="{Binding PubYear}" />  

<TextBlock x:Name="RatingPrompt" Grid.Row="5" Grid.Column="0"
Text="Rating" Margin="0,0,15,15"/>  

<StackPanel Grid.Row="5" Margin="0,0,0,15" Grid.Column="1"
Orientation="Horizontal" > 

<Slider x:Name="RatingSlider" Width="150"
HorizontalAlignment="Left" Margin="5,0,5,0"
LargeChange="1.0" SmallChange="0.1"
Minimum="0" Maximum="5.0" ValueChanged="RatingSlider_ValueChanged"
Value="{Binding Rating, Mode=TwoWay }" /> 

<TextBlock x:Name="SliderValueDisplay" Margin="5,0,0,0"
HorizontalAlignment="Left" />

</StackPanel>
</Grid> 

Сокрытие DetailsGrid

Вам необходимо сделать данную сетку невидимой тогда, когда внешняя табличная сетка (LayoutControl) отображается первой, а потому измените свойство видимости (Visibility) (line 65) в Visibility="Visible" на Visibility="Collapsed" и для удобства спрячьте список Library в закладке Objects and Timeline.

Создание обработчиков события

Вам необходим обработчик события для перехода из одного режима в другой, а также вам необходим обработчик события для изменений ползунка (обратите внимание на внутреннее событие "ValueChanged").

Изменение размера элементов List Box

Установка размера зависит от корректной установки отступов. Отступы, как оказалось, являются экземплярами класса Thickness. Что касается установки ListBox на отображение всего одной строки, то тут мы должны использовать свойство элемента Grid, и для этого мы применим метод SetValue, передавая свойство элемента Grid и значение, которое мы хотим установить.

private void LibraryList_SelectionChanged(
   object sender,
    System.Windows.Controls.SelectionChangedEventArgs e)
{
    LibraryList.Margin = new Thickness(50, 5, 20, 5);
    LibraryList.SetValue(Grid.RowSpanProperty, 1); 

Если вам интересно посмотреть, как это работает, найдите LayoutRoot (верхний элемент Grid) и добавьте ShowGridLines="True" - и затем запустите программу и щелкните по книжке. Вы должны увидеть уменьшенный список между пунктирными линиями, как это показано на рисунке 7-26

Рисунок 7-26. Уменьшенный ListBox в первой строке

Вернувшись в обработчик события, Details Grid обладает свойством видимости, при этом Intellisense поможет вам установить свойства в перечисляемое значение
Visibility.Visible

Установка объекта Book (selectedBook) в выбранную пользователем книгу

Выбранная книга перейдет в ваш метод посредством SelectionChangedEventArgs в качестве первого члена набора AddedItems. Это будет тем объектом, чей нижележащий тип является Book, и вы можете преобразовать его соответственно.

Book selectedBook = e.AddedItems[0] as Book;

Установка dataContext для всех элементов управления в выбранную книгу путем установки DataContext элемента DetailsGrid

Все элементы управления в пределах контейнера наследует DataContext самого контейнера в случае, если они их не перегружают. Потому нет особых причин в написании,

Printing.Text = selectedBook.Printing.ToString();
PublicationYear.Text = selectedBook.PubYear.ToString(); 

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

DetailsGrid.DataContext = selectedBook;

Запустите программу и наблюдайте все в действии

Рисунок 7-27. Запущенная программа 


Количество авторов

Мы быстро рассмотрим два свойства. В первой строке подсказка была создана в StackPanel

<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Bottom">
        
<TextBlock x:Name="NumAuthorsPrompt" Text="{Binding NumAuthors, Mode=OneWay}"
VerticalAlignment="Bottom" HorizontalAlignment="Right" FontFamily="Verdana"
FontSize="18" FontWeight="Medium" Margin="0,0,15,15"/>
        
<TextBlock x:Name="AuthorsPrompt" Text="Authors"
VerticalAlignment="Bottom" HorizontalAlignment="Right" FontFamily="Verdana"
FontSize="18" FontWeight="Medium" Margin="0,0,15,15"/>

</StackPanel> 

Первый элемент TextBlock привязан к мистическому свойству NumAuthors. Мы знаем, что все эти привязки осуществляются к объекту Book. Так содержит ли класс Book свойство NumAuthors, которое указывает количество авторов, написавших книгу? Исследовав Book можно заметить, что это было инкапсулировано в классе Book (там, где и положено):

public string NumAuthors
{
   get
   {
    return privateAuthors.Count.ToString();
   }

Наконец-то элемент управления ползунком инициализирован с рейтингом из книги (обратите внимание на привязку), но позволяет пользователю установить любое значение от 0 до 5, и данное значение посылается назад в бизнес-объект. Это реализуется при помощи двусторонней привязки,

<Slider x:Name="RatingSlider" Width="150" VerticalAlignment="Bottom"
   HorizontalAlignment="Left" Margin="5,0,5,0" LargeChange="1.0" SmallChange="0.1" Minimum="0" Maximum="5.0"
   ValueChanged="RatingSlider_ValueChanged" Value="{Binding Rating, Mode=TwoWay}" />

Поскольку ползунок не отображает какое-либо значение, мы добавили TtextBlock, который будет выводить значение ползунка
<StackPanel Grid.Row="5" Margin="0,0,0,15"  Grid.Column="1" Orientation="Horizontal" >
   
 <Slider x:Name="RatingSlider" Width="150" VerticalAlignment="Bottom"
   HorizontalAlignment="Left" Margin="5,0,5,0" LargeChange="1.0" SmallChange="0.1" Minimum="0" Maximum="5.0"
   ValueChanged="RatingSlider_ValueChanged" Value="{Binding Rating, Mode=TwoWay }" />
   
<TextBlock x:Name="SliderValueDisplay" Margin="5,0,0,0"
VerticalAlignment="Bottom"
HorizontalAlignment="Left" FontFamily="Verdana" FontSize="18"
/>
   
</StackPanel>

Обратите внимание на то, что TextBlock не привязан к значению ползунка. Вместо этого он обновляется в результате реализации события ValueChanged ползунка, тем самым гарантируется его своевременное обновление при изменении позиции ползунка, что обогащает функциональность,

private void RatingSlider_ValueChanged(
   object sender,
    System.Windows.RoutedPropertyChangedEventArgs e)
{
    Slider s = sender as Slider;
    SliderValueDisplay.Text = s.Value.ToString("N");

Вкратце, мы должны преобразовать отправляемое (которое является объектом типа) в тип ползунка (Slider), тем самым мы сможем получить доступ к значению его свойства, которое мы впоследствии обрабатываем в качестве строки, вызывая ToString. При этом передается стандартная форматирующая строка "N", которая заставляет передаваемую строку обрабатываться с двумя цифрами после запятой, чтобы у нас книга не имела рейтинг, равный 4.7383928.

Скачать исходники примеров 

Jesse Liberty
Источник