- •А.А. Волосевич
- •1. Общее описание wpf
- •2. ПРостейшее Приложение wpf
- •4. Базовые концепции wpf Иерархия классов
- •Свойства зависимостей и присоединённые свойства
- •Маршрутизируемые события
- •Многопоточность в wpf
- •5. СтруктуРа Оконного приложения wpf
- •Класс Window
- •Класс Application
- •6. Компоновка
- •Размер и выравнивание
- •Основные контейнеры компоновки
- •Прокрутка и декорирование содержимого
- •7. Обзор элеменТов управления
- •Цвета и окантовка:
- •Шрифт содержимого:
- •Элементы управления содержимым
- •Списковые элементы управления
- •Прочие элементы управления
- •8. Фигуры
- •9. Цвет, кисти, прозрачность Представление цвета в wpf
- •Лучшие кисти
- •Прозрачность
- •10. Трансформации и эффекты
- •11. Классы drawing и visual
- •12. Ресурсы
- •Двоичные ресурсы
- •Логические ресурсы
- •13. Привязка данных Базовые концепции привязки данных
- •Практическое использование привязки данных
- •Конвертеры значений
- •Проверка данных
- •14. Стили и триггеры
- •15. ПрИвязка к коллекциям и шаблоны данных
- •16. Представления Данных
- •17. ШАблоны элементов управления
15. ПрИвязка к коллекциям и шаблоны данных
Рассмотрим следующий пример. Пусть имеется класс, описывающий задание для программиста, и класс с набором заданий:
public class Task
{
public string Name { get; set; }
public string Note { get; set; }
public int Priority { get; set; }
public TaskType Type { get; set; }
}
public enum TaskType { Coding, Testing, Support }
public class Tasks : List<Task>
{
public Tasks()
{
Add(new Task{ Name = "Data loading",
Note = "Need to test work with DB",
Priority = 2, Type = TaskType.Testing });
Add(new Task{ Name = "Log class",
Note = "Finish this work",
Priority = 2, Type = TaskType.Coding });
Add(new Task{ Name = "IoC Usage",
Note = "Find more info",
Priority = 4, Type = TaskType.Coding });
Add(new Task{ Name = "Urgent bug fixing",
Note = "Problem with class C",
Priority = 1, Type = TaskType.Support });
Add(new Task{ Name = "UI development",
Note = "Make markup for Main Window",
Priority = 1, Type = TaskType.Coding });
Add(new Task{ Name = "Help Doc",
Note = "Write technical documentation",
Priority = 3, Type = TaskType.Support });
Add(new Task{ Name = "New project!",
Note = "Plan the meeting",
Priority = 1, Type = TaskType.Support });
}
}
Необходимо создать приложение WPF, отображающее задания. Список заданий будет показан в элементе ListBox, связанном с объектом класса Tasks. Класс ItemsControl, являющийся предком всех списковых элементов управления, определяет два свойства, которые будут использоваться при привязке:
-
ItemsSource – указывает на коллекцию, содержащую все объекты, которые будут показаны в списке. В свойство ItemsSource можно поместить любой объект, реализующий интерфейс IEnumerable или его универсальную версию.
-
DisplayMemberPath – путь к свойству, которое будет применяться для создания отображаемого текста каждого элемента коллекции.
Детальная информация о выбранном задании будет отображаться в наборе текстовых полей. Для этого применяется привязка к свойству DataContext контейнера, обрамляющего поля.
<Window x:Class="TaskView.MainWindow" Title="List of Tasks"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Margin="5" Orientation="Horizontal">
<ListBox Name="lstTasks" DisplayMemberPath="Name" Width="250"/>
<Grid Margin="5" DataContext="{Binding ElementName=lstTasks,
Path=SelectedItem}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="7" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="11" />
<RowDefinition Height="Auto" />
<RowDefinition Height="11" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name:" />
<TextBox Grid.Row="0" Grid.Column="2"
Text="{Binding Name}" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Note:" />
<TextBox Grid.Row="2" Grid.Column="2"
Text="{Binding Note}" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Priority:" />
<TextBox Grid.Row="4" Grid.Column="2"
Text="{Binding Priority}" />
</Grid>
</StackPanel>
</Window>
// файл Code Behind
namespace TaskView
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lstTasks.ItemsSource = new Tasks();
}
}
}
Рис. 39. Привязка списку, форма «главная - подробности».
Поддержка, которую даёт интерфейс IEnumerable, ограничена привязкой только для чтения – изменения, производимые в коллекции после привязки, в списковом элементе управления не отображаются. Чтобы включить отслеживание изменений, нужно использовать коллекцию с интерфейсом INotifyCollectionChanged. WPF включает единственную коллекцию, реализующую этот интерфейс, – это класс ObservableCollection<T>.
// изменённый класс MainWindow
public partial class MainWindow : Window
{
private readonly ObservableCollection<Task> tasks;
public MainWindow()
{
InitializeComponent();
tasks = new ObservableCollection<Task>(new Tasks());
lstTasks.ItemsSource = tasks;
}
}
Изменим внешний вид списка заданий при помощи шаблона данных. Шаблоны данных (data templates) – механизм для настройки отображения объектов определённого типа. В WPF шаблон данных – это объект класса System.Windows.DataTemplate. Основное свойство шаблона – VisualTree. Оно содержит визуальный элемент, определяющий внешний вид шаблона. Часто этим визуальным элементом является контейнер компоновки. В разметке XAML для задания VisualTree достаточно поместить в DataTemplate дочерний элемент. При формировании VisualTree обычно используется привязка данных для извлечения информации из объекта, для которого применяется шаблон. Сам шаблон данных, как правило, размещают в ресурсах окна или приложения.
С учётом вышесказанного определим шаблон данных в ресурсах окна и используем свойство списка ItemTemplate для применения шаблона к каждому элементу списка:
<!-- определяем шаблон в ресурсах окна -->
<Window.Resources>
<DataTemplate x:Key="taskTemplate">
<Border Name="border" BorderBrush="Aqua" BorderThickness="1"
CornerRadius="2" Padding="5" Margin="5">
<TextBlock FontSize="14" FontWeight="Bold"
Text="{Binding Name}" />
</Border>
</DataTemplate>
</Window.Resources>
<!-- изменённая настройка ListBox -->
<ListBox Name="lstTasks" HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource taskTemplate}" Width="250" />
Рис. 40. Список, к которому применён шаблон данных.
У класса DataTemplate имеется свойство DataType, определяющее тип данных, к которому будет применяться шаблон. Если задано это свойство, WPF будет использовать шаблон в любой ситуации, где до этого выводилась строка с результатом ToString():
<!-- не указываем ключ ресурса, если шаблон в ресурсах -->
<!-- подключено xmlns:TaskView="clr-namespace:TaskView" -->
<DataTemplate DataType="{x:Type TaskView:Task}">
<!-- описание шаблона не изменилось -->
</DataTemplate>
<!-- в ListBox не задаём свойство ItemTemplate -->
<ListBox Name="lstTasks" Width="250" HorizontalContentAlignment="Stretch"/>
В шаблон данных можно поместить триггеры данных. Следующий пример показывает использование триггера для того, чтобы изменить цвет окантовки задач из группы TaskType.Support:
<!-- эта разметка – часть шаблона DataTemplate -->
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Type}" Value="Support">
<Setter TargetName="border" Property="BorderBrush"
Value="Brown" />
</DataTrigger>
</DataTemplate.Triggers>
Обратите внимание – элемент <Setter> содержит установку TargetName. Как ясно из контекста, TargetName используется, чтобы обратиться к именованному дочернему элементу визуального представления шаблона данных. Дочерний элемент должен быть описан до триггера.
В WPF списковые элементы управления поддерживают возможность выбора для объекта одного из нескольких шаблонов данных. Предположим, что в примере со списком заданий требуется особым образом отображать задания с приоритетом, равным 1. Определим в ресурсах окна ещё один шаблон данных:
<DataTemplate x:Key="importantTask">
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2"
Padding="5" Margin="5">
<TextBlock FontSize="18" Foreground="Red" Text="{Binding Name}"/>
</Border>
</DataTemplate>
Выбор шаблона выполняется программно, с помощью создания подкласса для DataTemplateSelector и переопределения метода SelectTemplate():
public class TaskTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
if (item != null && item is Task)
{
var task = item as Task;
Window window = Application.Current.MainWindow;
return task.Priority == 1 ?
window.FindResource("importantTask") as DataTemplate :
window.FindResource(new DataTemplateKey(typeof(Task)))
as DataTemplate;
}
return null;
}
}
Затем можно объявить объект TaskTemplateSelector в качестве ресурса и назначить этот ресурс свойству ListBox.ItemTemplateSelector. Объект ListBox вызывает метод SelectTemplate() для каждого элемента в базовой коллекции.
<ListBox Name="lstTasks" Width="250"
HorizontalContentAlignment="Stretch"
ItemTemplateSelector="{StaticResource selector}"/>
Рис. 41. Селектор шаблонов в действии.
При работе с иерархическими элементами управления (например, TreeView) вместо шаблона данных на основе DataTemplate следует использовать HiererchicalDataTemplate. У такого шаблона имеется свойство ItemsSource, которое нужно связать с дочерней коллекцией, и свойство ItemTemplate – дочерний шаблон данных (DataTemplate или HiererchicalDataTemplate).