Настройка отображения данных с привязкой данных и WPF - Один элемент управления для отображения всей иерархии

ОГЛАВЛЕНИЕ

Один элемент управления для отображения всей иерархии

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

Заполнить представление TreeView в WPF элементами можно одним из двух способов. Первый способ – добавление элементов вручную в коде либо в коде XAML, а второй – создание их через привязку данных.

В приведеном ниже коде XAML показано, как можно добавлять элементы TreeViewItem к представлению TreeView в коде XAML:

<TreeView>
  <TreeViewItem Header="Item 1">
   <TreeViewItem Header="Sub-Item 1" />
   <TreeViewItem Header="Sub-Item 2" />
  </TreeViewItem>
  <TreeViewItem Header="Item 2" />
</TreeView>

Прием создания элементов в TreeView вручную имеет смысл в ситуациях, когда элемент управления всегда будет отображать небольшой и статичный набор элементов. При необходимости отображать большие объемы данных, которые могут меняться со временем, становится необходимым использование более динамического подхода. На этом этапе вариантов есть два. Можно написать код, который проходит по структуре данных, создает элементы TreeViewItem, основанные на найденных им объектах данных, и добавляет эти элементы к TreeView. В качестве альтернативы можно воспользоваться иерархическими шаблонами данных и возложить всю работу на WPF.

Использование иерархических шаблонов данных

То, как WPF следует визуализировать иерархические данные через иерархические шаблоны данных, можно выразить декларативно. Класс HierarchicalDataTemplate является средством, наводящим мост между сложной структурой данных и визуальным представлением этих данных. Он очень похож на нормальный DataTemplate, но также позволяет указать, откуда происходят дочерние элементы объекта данных. Также можно предоставить классу HierarchicalDataTemplate шаблон для визуализации этих дочерних элементов.

Предположим, что теперь требуется отобразить данные, представленные наРис. 7 внутри одного элемента управления TreeView. Получившийся TreeView может выглядеть примерно так, как показано наРис. 9. Реализация этого включает использование двух HierarchicalDataTemplate и одного DataTemplate.

 

Рис. 9. Изображение целой иерархии данных в TreeView

Два иерархических шаблона отображают объекты Customer и Order. Поскольку у объектов OrderDetail нет дочерних элементов, их можно визуализировать с помощью неиерархического DataTemplate. Свойство ItemTemplate элемента TreeView использует шаблон для объектов типа Customer, поскольку объекты типа Customer и объекты данных содержатся в корневом уровне TreeView. В коде XAML, приведенном наРис. 10, показано, как собираются все части этой головоломки.

Рис. 10. Код XAML в основе отображения TreeView

<Grid>
  <Grid.DataContext>
   <!-- 
   This sets the DataContext of the UI
   to a Customers returned by calling
   the static CreateCustomers method. 
   -->
   <ObjectDataProvider 
    xmlns:local="clr-namespace:VariousBindingExamples"
    ObjectType="{x:Type local:Customer}"
    MethodName="CreateCustomers"
    />
  </Grid.DataContext>

  <Grid.Resources>
   <!-- ORDER DETAIL TEMPLATE -->
   <DataTemplate x:Key="OrderDetailTemplate">
    <TextBlock>
     <Run>Product:</Run>
     <TextBlock Text="{Binding Path=Product}" />
     <Run>(</Run>
     <TextBlock Text="{Binding Path=Quantity}" />
     <Run>)</Run>
    </TextBlock>
   </DataTemplate>

   <!-- ORDER TEMPLATE -->
   <HierarchicalDataTemplate 
    x:Key="OrderTemplate"
    ItemsSource="{Binding Path=OrderDetails}"
    ItemTemplate="{StaticResource OrderDetailTemplate}"
    >
    <TextBlock Text="{Binding Path=Desc}" />
   </HierarchicalDataTemplate>

   <!-- CUSTOMER TEMPLATE -->
   <HierarchicalDataTemplate 
    x:Key="CustomerTemplate"
    ItemsSource="{Binding Path=Orders}"
    ItemTemplate="{StaticResource OrderTemplate}"
    >
    <TextBlock Text="{Binding Path=Name}" />
   </HierarchicalDataTemplate>
  </Grid.Resources>

  <TreeView
   ItemsSource="{Binding Path=.}"
   ItemTemplate="{StaticResource CustomerTemplate}"
   />

</Grid>

Я выделяю коллекцию объектов Customer для DataContext таблицы (Grid), которая содержит представление TreeView. В коде XAML это можно проделать, используя ObjectDataProvider, являющийся удобным способом вызова метода из XAML. Поскольку DataContext наследуется вниз по дереву элементов, DataContext представления TreeView дает ссылку на этот набор объектов Customer. Именно по этой причине мы можем дать его свойству ItemsSource привязку "{Binding Path=.}", которая является способом указать, что свойство ItemsSource привязано к DataContext элемента TreeView.

Если свойству ItemTemplate представления TreeView не было присвоено значение, то TreeView будет отображать только объекты Customer верхнего уровня. Поскольку WPF не известно, как визуализировать Customer, оно вызовет ToString на каждом Customer и отобразит этот текст для каждого элемента. У него не будет возможности выяснить, что у каждого Customer имеется список связанных с ним объектов Order, а у каждого объекта Order список объектов OrderDetail. Поскольку платформа WPF не может волшебным образом разобраться в существующей схеме данных, необходимо объяснить эту схему платформе WPF, чтобы она могла правильно визуализировать структуру данных.

Шаблоны HierarchicalDataTemplate вступают в дело именно тогда, когда необходимо объяснить платформе WPF структуру и внешний вид данных. Шаблоны, используемые в этой демонстрации, содержат очень простые деревья визуальных элементов, в основном просто поля TextBlock с небольшим объемом текста в них. В более замысловатых приложениях шаблоны могут иметь интерактивные вращающиеся трехмерные модели, изображения, рисунки векторной графики, сложные элементы управления UserControl или любое другое содержимое WPF, предназначенное для визуализации базового объекта данных.

Важно обратить внимание на порядок объявления шаблонов. Шаблон необходимо объявить, прежде чем на него можно ссылаться через выражение StaticResource. Это требование, навязываемое средством чтения XAML, и оно относится ко всем ресурсам, а не только к шаблонам.

Вместо этого сылаться на шаблоны можно при помощи выражения DynamicResource – в таком случае лексический порядок объявлений шаблонов не важен. Однако использование ссылок DynamicResource, в отличие от ссылок StaticResource, сопровождается некоторыми издержками при выполнении, поскольку они отслеживают изменения в системе ресурсов. Поскольку мы не замещаем шаблоны во время выполнения, эти издержки не нужны, так что лучше всего использовать ссылки StaticResource и расположить объявления шаблонов в нужном порядке.