Класс ItemsControl обладает набором свойств, позволяющих гибко настроить внешний вид любого списка:
Template – шаблон списка как элемента управления. Чтобы отображать данные, помещённые в список, шаблон должен содержать ItemsPresenter или контейнер компоновки, у которого свойство IsItemsHost равно true.
ItemsPanel – контейнер компоновки для элементов списка.
ItemContainerStyle – стиль контейнера, обрамляющего элемент списка.
ItemContainerStyleSelector – объект класса-наследника StyleSelector с методом выбора стиля для ItemContainerStyle.
ItemTemplate – шаблон данных, используемый для отображения отдельного элемента списка.
ItemTemplateSelector – объект класса-наследника DataTemplateSelector с методом выбора шаблона для ItemTemplate.
Рис 48. Структура списка.
Если у списка установлено свойство ItemsContainerStyle, то он передаст этот стиль каждому своему элементу при его создании (в случае ListBox каждый элемент – это объект ListBoxItem). Ниже показана разметка и эффект применения ItemsContainerStyle для списка. Обратите внимание на триггер, срабатывающий при выделении элемента.
<!-- помещаем стиль в ресурсы окна -->
<Window.Resources>
<!-- этот ресурс нужен для изменения цвета выбранного элемента -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="DarkRed" />
<Style x:Key="normal" TargetType="ListBoxItem">
<Setter Property="Background" Value="LightSteelBlue" />
<Setter Property="Margin" Value="3" />
<Setter Property="Padding" Value="3" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontSize" Value="14" />
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- изменения в описании ListBox -->
<ListBox Name="lst" DisplayMemberPath="Name" Width="200"
ItemContainerStyle="{StaticResource normal}" />
Рис. 49. Список с установленным свойством ItemContainerStyle.
При помощи селекторов стилей реализуется возможность менять стиль элемента списка на основе элемента данных. Селектор стилей – класс, унаследованный от StyleSelector, с перекрытым методом SelectStyle(). Для рассматриваемого примера создадим селектор стилей, выделяющий «агентов».
public class AgentStyleSelector: StyleSelector
{
public Style NormalStyle { get; set; }
public Style AgentStyle { get; set; }
public override Style SelectStyle(object item,
DependencyObject container)
{
var person = (Person) item;
return person.Name.StartsWith("Agent")? AgentStyle:
NormalStyle;
}
}
Чтобы селектор стилей работал, нужно построить два стиля, а также создать и инициализировать экземпляр AgentStyleSelector.
<Window.Resources>
<!-- стили простые, без триггеров -->
<Style x:Key="normal" TargetType="ListBoxItem">
<Setter Property="Background" Value="LightSteelBlue" />
<Setter Property="Margin" Value="3" />
<Setter Property="Padding" Value="3" />
</Style>
<Style x:Key="agent" TargetType="ListBoxItem">
<Setter Property="Background" Value="Aqua" />
<Setter Property="Margin" Value="3" />
<Setter Property="Padding" Value="3" />
</Style>
</Window.Resources>
<!-- настройка ListBox -->
<ListBox Name="lst" DisplayMemberPath="Name" Width="200">
<ListBox.ItemContainerStyleSelector>
<WpfLists:AgentStyleSelector
NormalStyle="{StaticResource normal}"
AgentStyle="{StaticResource agent}" />
</ListBox.ItemContainerStyleSelector>
</ListBox>
Рис. 50. Селектор стилей в действии.
Процесс выбора стилей выполняется, когда список привязывается в первый раз. Это становится проблемой, когда в результате редактирования какой-то элемент данных перемещается из одной категории стиля в другую. В такой ситуации нужно заново применить стили. При этом проверяется и обновляется каждый элемент в списке – процесс, не занимающий много времени в списках малых и средних размеров. Простой подход состоит в удалении селектора стиля установкой свойства ItemContainerStyleSelector в null, с последующим его переназначением:
StyleSelector selector = lst.ItemContainerStyleSelector;
lst.ItemContainerStyleSelector = null;
lst.ItemContainerStyleSelector = selector;
Можно организовать запуск этого кода автоматически в ответ на определённые изменения, обрабатывая такие события, как PropertyChanged (объявлено в INotifyPropertyChanged) или Binding.SourceUpdated.
Изменить состав отображаемой в элементе списка информации помогают шаблоны данных. Для установки шаблона данных можно использовать свойство списка ItemTemplate или свойство шаблона DataTemplate (глобальный шаблон). У списка также имеется свойство ItemTemplateSelector для задания селектора шаблонов (работает так же, как и селектор стилей).
// селектор шаблона данных
public class AgentTemplateSelector: DataTemplateSelector
{
public DataTemplate NormalTemplate { get; set; }
public DataTemplate AgentTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
var person = item as Person;
return person == null? null:
(person.Name.StartsWith("Agent")? AgentTemplate:
NormalTemplate);
}
}
<!-- описание шаблонов данных в ресурсах -->
<Window.Resources>
<DataTemplate x:Key="normal">
<Border Width="170" BorderBrush="Blue" BorderThickness="1"
CornerRadius="2" Padding="3" Margin="3">
<StackPanel>
<TextBlock FontSize="12" FontWeight="Bold"
Text="{Binding Name}" />
<TextBlock Text="{Binding Age}" />
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="agent">
<Border Width="170" BorderBrush="Red" BorderThickness="3"
CornerRadius="2" Padding="3" Margin="3">
<TextBlock FontSize="16" FontWeight="Bold"
Text="{Binding Name}" />
</Border>
</DataTemplate>
</Window.Resources>
<!-- настройка ListBox для использования шаблонов данных -->
<ListBox Name="lst" Width="200">
<ListBox.ItemTemplateSelector>
<WpfLists:AgentTemplateSelector
NormalTemplate="{StaticResource normal}"
AgentTemplate="{StaticResource agent}" />
</ListBox.ItemTemplateSelector>
</ListBox>
Рис. 51. Список с шаблонами данных.
Стили элемента, шаблоны данных и селекторы дают достаточный контроль над всеми аспектами представления элемента. Однако они не позволяют изменить организацию элементов относительно друг друга. Независимо от того, какие шаблоны и стили применяются, ListBox поместит каждый элемент в отдельную горизонтальную строку и сложит строки друг на друга в стопку, формируя список.
Эту компоновку можно изменить, заменив контейнер, который используется списком для организации своих дочерних элементов. Для этого необходимо установить свойство ItemsPanel. В следующем фрагменте разметки применяется WrapPanel в качестве оболочки вокруг доступной ширины элемента управления ListBox:
<!-- показана только настройка свойства ItemsPanel -->
<ListBox Name="lst" Width="400"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Рис. 52. Изменение контейнера компоновки элементов.
Большинство списочных элементов управления используют в качестве контейнера для компоновки VirtualizingStackPanel. Этот контейнер гарантирует эффективную обработку больших списков привязанных данных. Он создаёт только те элементы, которые необходимы для отображения набора текущих видимых элементов.