WPF: Деревья и колонки

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

clip_image002

Библиотека WPF, на первый взгляд, не содержит такого элемента. ;-)

Но существует несколько сторонних разработок, позволяющих добиться такого эффекта.

  1. 1. Telerik RadTreeListView. Элемент управления. созданный компанией Telerik, не имеющий ничего общего с System.Windows.Controls.TreeView и напрямую наследующий System.Windows.Controls.ItemsControl. Прямо из коробки этот элемент не содержит колонок. Но после небольшой доработки, предложенной автором(http://www.telerik.com/community/forums/wpf/treeview/multicolumn-treeview.aspx) этот элемент превращается в довольно симпатичное дерево с колонками. Колонки задаются довольно просто.
  2. CodeProject: TreeListView (http://www.codeproject.com/KB/WPF/TreeListView.aspx) Это основанный на System.Windows.Controls.TreeView элемент управления который содержит несколько десятков строк кода на С#, вместе с замысловатым в котором вычисляют насколько необходимо сдвинуть строку от левого края в зависимости от уровня элемента дерева. Через этот конвертор затем выполняют привязку ширины фиктивного элемента осуществляющего сдвиг. Кроме того элемент переопределяет почти все шаблоны из TreeView. Неплохой вариант, поддерживает даже изменения порядка колонок, но слишком уж сложно все организовано.
  3. Mindscape MulticolumnTreeView (http://www.mindscape.co.nz/) Показывает колонки прямо из коробки, никаких доработок не требует. Но опять-таки стоит денег.

Тут я предлагаю вспомнить о том, что элементы управления в WPF не имеют жестко заданного внешнего вид, а имеют только поведение и дерево это нечто, что содержит иерархию элементов, а как выглядеть этим элементам решать разработчику. Для того, чтобы добавить колонки в обычное дерево мы переопределим его ItemTemplate:

<TreeView.ItemTemplate>

  <HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">

    <Grid>

      <Grid.Resources>

        <Style TargetType="TextBlock">

          <Setter Property="Margin" Value="1"/>

        </Style>

      </Grid.Resources>

      <Grid.ColumnDefinitions>

        <ColumnDefinition Width="*"/>

        <ColumnDefinition Width="Auto"/>

        <ColumnDefinition Width="Auto"/>

        <ColumnDefinition Width="Auto"/>

      </Grid.ColumnDefinitions>

        <TextBlock Text="{Binding Path=Title}" Grid.Column="0" />

        <TextBlock Text="{Binding Path=DateTime}" Grid.Column="1"

              Width="{Binding ElementName=DateTimeHeader, Path=ActualWidth}"/>

        <TextBlock Text="{Binding Path=Raiting}" Grid.Column="2"

              Width="{Binding ElementName=RaitingHeader, Path=ActualWidth}"/>

        <Rectangle Width="{Binding ElementName=EmptyHeader, Path=ActualWidth}" Grid.Column="3" />

    </Grid>

  </HierarchicalDataTemplate>

</TreeView.ItemTemplate>

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

<Control.Template>

  <ControlTemplate>

    <StackPanel>

      <Grid x:Name="HeaderGrid">

        <Grid.Resources>

          <Style TargetType="GridViewColumnHeader">

            <Setter Property="Margin" Value="1"/>

          </Style>

        </Grid.Resources>

        <Grid.ColumnDefinitions>

          <ColumnDefinition Width="Auto"/>

          <ColumnDefinition Width="Auto"/>

          <ColumnDefinition Width="Auto"/>

          <ColumnDefinition Width="*" />

        </Grid.ColumnDefinitions>

        <GridViewColumnHeader Content="Title"   Grid.Column="0" x:Name="TitleHeader" Width="220" />

        <GridViewColumnHeader Content="Date/Time" Grid.Column="1" x:Name="DateTimeHeader" />

        <GridViewColumnHeader Content="Raiting" Grid.Column="2" x:Name="RaitingHeader" />

        <Rectangle Grid.Column="3" x:Name="EmptyHeader" />

      </Grid>

      <ItemsPresenter Width="{Binding ElementName=HeaderGrid, Path=ActualWidth}" />

    </StackPanel>

  </ControlTemplate>

</Control.Template>

В этом коде на XAML есть одна небольшая хитрость: в заголовке последняя колонка элемента Grid заполняет все оставшееся место, а в шаблоне элемента дерева первая (ведь у каждого элемента она разная, т.к. элементы разных уровней по-разному отодвинуты от левого края). Кроме того добавлен прямоугольник заполняющий свободную область справа и выполнены соответствующие привязки ширины элементов и данных в элементах TextBlock. На этом казалось бы можно остановится, но шаблон элемента TreeViewItem из которого строится дерево не растягивает свое содержимое на все доступное пространство. Поэтому его придется переопределить. Возьмем его стандартный шаблон и изменим его всего в двух местах.

В единственном элементе Grid вместо

<Grid.ColumnDefinitions>

  <ColumnDefinition Width="Auto" />

  <ColumnDefinition Width="Auto" />

  <ColumnDefinition Width="*" />

</Grid.ColumnDefinitions>

оставляем только две колонки

<Grid.ColumnDefinitions>

  <ColumnDefinition Width="Auto" />

  <ColumnDefinition Width="*" />

</Grid.ColumnDefinitions>

И в элементе ContentPresenter устанавливаем HorizontalAlignment="Stretch".

Добавим мелкие изменения, связанные с удалением одной колонки. Не забываем также вставить привязку

IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource Mode=TemplatedParent, AncestorType={x:Null}}}"

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

Вот и все наш элемент готов и без единой строчки Code-Behind.

clip_image004

Полный код примера здесь: DetailedTree или здесь: http://cid-c22b7611bb9d0310.skydrive.live.com/browse.aspx/Samples

Опубліковані 24-11-2009 12:04 від Dmitry Peleshenko
Зареєстрований як

Коментарі

 

Vladimir Lisnic said:

Спасибо за пример, очень интересно.

Нет возможности скачать код примера, ссылка http://cut.ms/GSu не работает.

Следующие строки выдают ошибку:

<GridViewColumnHeader Content="Title"   Grid.Column="0" x:Name="TitleHeader" Width="220" />

       <GridViewColumnHeader Content="Date/Time" Grid.Column="1" x:Name="DateTimeHeader" />

       <GridViewColumnHeader Content="Raiting" Grid.Column="2" x:Name="RaitingHeader" />

November 26, 2009 3:25 AM
 

Dmitry Peleshenko said:

Что-то сломалось в скайдрайв или я что-то напутал. Вот линк на папку в моем скайдрайв http://cid-c22b7611bb9d0310.skydrive.live.com/browse.aspx/Samples

November 26, 2009 3:38 AM
 

Vladimir Lisnic said:

скачал, запустил, работает ) круто.

одно НО!:

Когда вы пытаетесь задать Grid.Column в GridViewColumnHeader то получаете ошибку при открытом фале в Visual Studio или Expression Blend. Строки ниже иллюстрируют код, который выдает ошибку:

<GridViewColumnHeader Content="Title"   Grid.Column="0" x:Name="TitleHeader" Width="220" />

Ошибка: Property 'Column' of type 'GridViewColumn' cannot be specified as a string. *\MainWindow.xaml

Это ошибка не мешает коду скомпилироваться, если вы закроете XAML файл. Проект после компиляции запуститься и все будет работать корректно.

Как workaround могу посоветовать:

<GridViewColumnHeader Content="Title" x:Name="TitleHeader"  Width="220">

   <GridViewColumnHeader.Style>

       <Style TargetType="{x:Type GridViewColumnHeader}">

           <Setter Property="Grid.Column" Value="0"/>

       </Style>

   </GridViewColumnHeader.Style>

</GridViewColumnHeader>

не очень изящное решение, но зато работает WYSIWYG editor.

Когда вы задаете Grid.Column="0" в GridViewColumnHeader VS пытаеться распарсить это как значение свойства GridViewColumnHeader.Column, ну оно конечно же не парситься.

Если найду решение, то отпишу...

November 27, 2009 6:07 AM
 

Dmitry Peleshenko said:

Получилось воспроизвести ошибку, да похоже VS действительно не равнодушна к GridViewColumnHeader :) Если заменить все такие элементы на кнопки, то все работает (только размер колонко менять нельзя). Как вариант, можно завернуть все GridViewColumnHeader в StackPanel или в другой контейнер, или вообще заменить другим элементом который может менять размер.

<StackPanel Grid.Column="0" >

  <GridViewColumnHeader Content="Title" x:Name="TitleHeader" Width="220" />

</StackPanel>

Почему Grid.Column и GridViewColumnHeader.Column студия считает одним и тем же свойством остается загадкой. Но дизайнер для WPF это уже совсем другая история...

November 28, 2009 5:49 AM
 

Dmitry Peleshenko said:

В VS 2010 проблема исправлена, дизайнер не путает Grid.Column и GridViewColumnHeader.Column.

November 28, 2009 6:41 AM
Анонімні коментарі деактивовані. Увійдіть або Зареєструйтесь щоб мати доступ до ресурсів Спільноти.

Календар повідомлень

<November 2009>
SMTWTFS
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

Пошук

Go

Синдикація

SkinName:iroha_Blog2