Опубликован: 13.12.2011 | Доступ: свободный | Студентов: 1021 / 34 | Оценка: 4.29 / 4.57 | Длительность: 13:56:00
Лекция 6:

Привязка данных в технологиях WPF и Silverlight

< Лекция 5 || Лекция 6: 12 || Лекция 7 >
Аннотация: В лекции описываются принципы работы с данными, основные принципы связывания, разбираются интерфейсы INotifyPropertyChanged и INotifyCollectionChanged.

Цель лекции: рассмотреть основы привязки данных, узнать, как извлечь информацию из одного элемента и отобразить в другом, не написав ни единой строки кода. Научиться реализовывать интерфейсы INotifyPropertyChanged, INotifyCollectionChanged.

Платформа Windows Presentation Foundation отличается от многих других каркасов для создания пользовательских интерфейсов в части обработки данных. Хотя WPF/Silverlight и позволяет смешивать данные и пользовательский интерфейс, модели, управляемые данными, все же обеспечивают большую гибкость и возможность совместной работы программистов и дизайнеров.

Принципы работы с данными

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

Модель данных в .NET

Модель данных описывает контракт между источником и потребителем данных. Исторически сложилось так, что каждый каркас нес с собой новую модель данных: В Visual Basic это сначала была DAO (Data Access Objects), потом RDO (Remote Data Objects) и, наконец, ADO (ActiveX Data Objects). На платформе .NET произошел переход от API зависимых моделей к единой для всего каркаса.

В .NET имеется некая объектная модель, содержащаяся классы, интерфейсы, структуры, перечисления, делегаты и т.д. Но сверх того включена очень простая модель данных. Списки в .NET представляются интерфейсами из пространства имен System.Collections: IEnumerable и IList. Свойства определяются либо с помощью встроенных в CLR свойств, либо путем реализации интерфейса ICustomTypeDescriptor. Эта базовая модель осталась неизменной, несмотря на появление новых технологий доступа к данным.

В .NET также имеется и несколько конкретных способов работы с данными, например: ADO.NET (пространство имен System.Data), XML (пространство имен System.Xml), контракт о данных (пространство имен System.Runtime.Serialization) и разметка (пространство имен System.Windows.Markup). И это далеко не все. Самое важное, что все они построены поверх базовой модели данных .NET.

Поскольку все операции с данными в WPF/Silverlight базируются на фундаменте модели данных .NET, то элементы управления WPF могут получать данные от любого объекта CLR:

ListBox listBox1 = new ListBox();
listBox1.ItemsSource = new string[] { "Hello", "World" };

Всепроникающее связывание

Из всех нововведений, появившихся в WPF, для программиста, пожалуй, самым важным является полная интеграция связывания в систему. Шаблоны элементов, ресурсы и даже базовая "модель содержимого", лежащая в основе таких элементов управления, как Button и ListBox, – все может быть привязано к данным. Идея привязки свойств к источникам данным, отслеживания зависимостей и автоматического обновления экрана пронизывает все части WPF/Silverlight.

У связывания в WPF много названий. Например, для привязки к шаблонам в разметке применяется обозначение TemplateBinding вместо стандартной нотации Binding, используемой для привязки к данным. Привязка к шаблону работает лишь в контексте некоего шаблона, и при этом допускается привязывать лишь свойства шаблонного элемента управления. Это ограничение обеспечивает очень высокую эффективность привязки, что абсолютно необходимо, так как в WPF/Silverlight очень многие элементы управления привязываются к шаблонам. Привязка позволяет синхронизировать два представления данных. В WPF это означает, что некие данные (то есть свойство источника данных) привязываются к свойству элемента пользовательского интерфейса. Привязка прекрасно годится для поддержания синхронизации между двумя объектами, но, если типы данных этих объектов согласуются не идеально, начинают возникать проблемы.

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

Преобразование данных

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

WPF поддерживает два основных вида преобразований: конвертацию значений и шаблоны данных. Конвертеры значений просто преобразовывают данные из одного формата в другой (например, строку "Red" в Brushes.Red). Это весьма мощный механизм, поскольку конвертер может осуществлять двустороннее преобразование. Шаблоны данных, с другой стороны, позволяют "на лету" создавать элементы управления для представления данных. В модели шаблонов данных элементы управления могут с помощью привязок обращаться к данным, то есть читать и потенциально даже записывать данные обратно в источник.

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

Ресурсы

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

В программе на языке C# можно определять переменные для последующего использования. Часто это делается ради удобства чтения кода, а иногда в целях обобществления:

public class Window1 : Window 
{
    public Window1()
    {
         Title = "Resources";
         Brush toShare = new SolidColorBrush(Colors.Yellow);
         Button b = new Button();
         b.Content = "My Button";
         b.Background = toShare;
         Content = b;
    }
}

В разметке эта проблема не так проста. Модель грамматического разбора XAML требует, чтобы все создаваемые объекты были свойствами чего-либо. Захоти мы получить кисть, общую для двух кнопок, пришлось бы создавать какой-то вид привязки одного элемента к другому, а это, очевидно, куда сложнее, чем просто завести переменную. Поэтому при разработке WPF было решено ввести единое свойство Resources, в котором можно хранить список именованных объектов. Эти объекты стали бы тогда доступны любому дочернему элементу. В следующем фрагменте кисть определяется в свойстве Resources окна, а затем используется в кнопке:

<Window
    Text=’Resources’
    xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
    xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
    <Window.Resources>
         <SolidColorBrush x:Key=’toShare’>Yellow</SolidColorBrush>
    </Window.Resources>
    
    <Button Background=’{StaticResource toShare}’>
        My Button
    </Button>
</Window>

Путь поиска ресурса несколько сложнее, чем просто проход вверх по иерархии. Просматривается также объект приложения, системная тема и тема по умолчанию для типов. Порядок просмотра таков:

  1. Иерархия элементов.
  2. Application.Resources.
  3. Тема типа.
  4. Системная тема.

Ранее мы говорили, что у класса типа Application есть свойство Resources, позволяющее определить ресурсы, глобальные для всего приложения. Рассмотрим на примере:

<Application x:Class=’EssentialWPF.MyApp’
    xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
    xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
    <Application.Resources>
        <SolidColorBrush x:Key=’toShare’>Purple</SolidColorBrush>
    </Application.Resources>
</Application>

Эта техника позволяет создавать ресурсы, общие для всех страниц, окон и элементов управления. На любом уровне приложения можно переопределить глобальное значение ресурса. В общем случае рекомендуется определять ресурс на самом нижнем возможном уровне. Если некий ресурс используется только в одной панели, то для этой панели его и надо определить. Если же ресурс используется в нескольких окнах, то определить его следует на уровне приложения. Определяя ресурс, важно помнить, что использовать его можно в разных местах. Поскольку каждый элемент WPF может присутствовать только в одном месте дерева отображения, мы не можем надежно использовать элемент в качестве ресурса:

<Window
    x:Class=’EssentialWPF.Resources’
    Title=’Resources’
    xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
    xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
    <Window.Resources>
        <TextBox x:Key=’sharedTextBox’ />
    </Window.Resources>
    <Button Content=’{StaticResource sharedTextBox}’/>
</Window>

Эта разметка будет работать только, если на элемент sharedTextBox есть только одна ссылка. Попытайся мы воспользоваться этим ресурсом еще раз, приложение завершится с ошибкой:

<Window
    x:Class=’EssentialWPF.Resources’
    Title=’Resources’
    xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
    xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
    <Window.Resources>
        <TextBox x:Key=’sharedTextBox’ />
    </Window.Resources>
    <StackPanel>
        <!— это ошибка! —>
        <Button Content=’{StaticResource sharedTextBox}’/>
        <Button Content=’{StaticResource sharedTextBox}’/>
    </StackPanel>
</Window>

Когда ресурс требуется использовать более одного раза, необходимо прибегнуть к классу FrameworkElementFactory. Для элементов, принадлежащих шаблонам, мы создаем фабрику, а не сами элементы. Большинству визуальных объектов (кистей, перьев, сеток и т.д.) фабрика не нужна, так как многократное использование обеспечивается наследованием классу Freezable.

Почему в лекции о привязке к данным мы говорим о ресурсах? Дело в том, что, используя ссылки на статические ресурсы, мы по существу выполняем присваивание переменной, как в приведенном выше фрагменте на C#. Когда эта переменная используется, никакой связи с исходной переменной уже нет. Рассмотрим следующий код:

Brush someBrush = Brushes.Red;
Button button1 = new Button();
button1.Background = someBrush;
someBrush = Brushes.Yellow;

// button1.Background здесь будет красным

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

<Window
    Title=’Resources’
    xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’
    xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’>
    <Window.Resources>
        <SolidColorBrush x:Key=’toShare’>Yellow</SolidColorBrush>
    </Window.Resources>
    <Button Background=’{DynamicResource toShare}’>
        My Button
    </Button>
</Window>

Поскольку на этот раз мы воспользовались динамическим связыванием, то можем изменить цвет кнопки, присвоив новое значение свойству Resources окна:

<!— window1.xaml —>

...
<Button Background=’{DynamicResource toShare}’ Click=’Clicked’>
    My Button
</Button>
...

// window1.xaml.cs
...
void Clicked(object sender, RoutedEventArgs e)
{
    Brush newBrush = new SolidColorBrush(Colors.Blue);
    this.Resources["toShare"] = newBrush;
}
...

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

Чтобы выполнить динамическое связывание ресурса программно, нам понадобится метод FrameworkElement.SetResourceReference:

button1.SetResourceReference(Button.BackgroundProperty,"toShare");

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

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

Ресурсы – это особая форма привязки к данным, оптимизированная в расчете на большое число привязок, которые редко обновляются. В общем случае, механизм привязки к данным оптимизирован в предположении умеренного числа привязок (в том числе и двусторонних) с высокой частотой обновления. Этот более общий вид привязки получил и более простое название; в WPF он называется просто связыванием или привязкой.

< Лекция 5 || Лекция 6: 12 || Лекция 7 >