TreeListView: Show Hierarchy Data with Details in Columns
This is the first session of this topic. I’ll introduce the minimum steps needed to build a TreeListView. The TreeListView is able to present hierarchy data with details in columns. See the following diagram:
The main Avalon elements we are going to cover in this session are TreeView, TreeViewItem, GridViewColumnCollection, GridViewRowPresenter, GridViewHeaderRowPresenter and Binding of course.
Step 1. Sub-class TreeView and TreeViewItem
The key in this step is to override proper methods to make sure correct container is generated.
public class TreeListView : TreeView
{
protected override DependencyObject GetContainerForItemOverride(object item)
{
return new TreeListViewItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TreeListViewItem;
}
public GridViewColumnCollection Columns
{
get
{
if (_columns == null)
{
_columns = new GridViewColumnCollection();
}
return _columns;
}
}
private GridViewColumnCollection _columns;
}
public class TreeListViewItem : TreeViewItem
{
// copy the above two overrides here, they are same.
}
Step 2. Add a Level property to TreeListViewItem
Make the item be aware of the level it belongs to.
public class TreeListViewItem : TreeViewItem
{
public int Level {
get {
if (_level == -1) {
TreeListViewItem parent = ItemsControl.ItemsControlFromItemContainer(this) as TreeListViewItem;
_level = (parent != null) ? parent.Level + 1 : 0;
}
return _level;
}
}
}
Step 3. Write a data template for the 1st column
The keys in this step are:
- Bind Margin to item’s Level to mimic the indent;
- Write a converter to convert Level to proper indent space;
- Use ‘ancestor’ binding to find the TreeListViewItem;
- When there is no child in the item, trigger the expander’s (+ sign) visibility to Hidden but not Collapsed, so that the space is kept and all the item texts align vertically.
<DataTemplate x:Key="CellTemplate_Name">
<DockPanel>
<ToggleButton x:Name="Expander" Style="…" ClickMode="Press"
Margin="{Binding Level,Converter={StaticResource LevelToIndentConverter},RelativeSource={RelativeSource AncestorType={x:Type l:TreeListViewItem}}}"
IsChecked="{Binding Path=IsExpanded,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListViewItem}}}"
/>
<TextBlock Text="{Binding Name}"/>
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=HasItems,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListViewItem}}}"
Value="False">
<Setter TargetName="Expander"
Property="Visibility"
Value="Hidden"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
public class LevelToIndentConverter : IValueConverter
{
public object Convert(object o, Type type, object parameter, CultureInfo culture)
{
return new Thickness((int)o * c_IndentSize, 0, 0, 0);
}
public object ConvertBack(…) {…}
…
}
Step 4. Write control template for TreeListViewItem
The keys in this step are:
- Instead of using ContentPresenter, we use GridViewRowPresenter to present the content in multiple columns
- Presenter finds content from item’s Header property
- Bind GridViewRowPresenter.Colums property to TreeListView.Columns property
<ControlTemplate TargetType="{x:Type l:TreeListViewItem}">
<StackPanel>
<Border …>
<GridViewRowPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}"
Columns="{Binding Path=Columns,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListView}}}"/>
</Border>
<ItemsPresenter x:Name="ItemsHost"/>
</StackPanel>
…
</ControlTemplate>
Step 5. Put it all together
The key in this step is: When setup the columns you want to show, the cell template for the 1st column is the one we just wrote that can do smart indent.
<Window …>
<Window.Resources>
<Style x:Key="ExpandCollapseToggleStyle">
…
<l:LevelToIndentConverter x:Key="LevelToIndentConverter"/>
<DataTemplate x:Key="CellTemplate_Name">
…
<Style TargetType="{x:Type l:TreeListViewItem}">
…
<Style TargetType="{x:Type l:TreeListView}">
…
</Window.Resources>
<l:TreeListView>
<l:TreeListView.Columns>
<GridViewColumn Header="Name" CellTemplate="{StaticResource CellTemplate_Name}"/>
<GridViewColumn Header="IsAbstract" DisplayMemberBinding="{Binding IsAbstract}"/>
<GridViewColumn Header="Namespace" DisplayMemberBinding="{Binding Namespace}"/>
</l:TreeListView.Columns>
<l:TreeListViewItem>
<l:TreeListViewItem.Header>
<x:Type TypeName="DependencyObject"/>
</l:TreeListViewItem.Header>
<l:TreeListViewItem>
<l:TreeListViewItem.Header>
<x:Type TypeName="Visual"/>
</l:TreeListViewItem.Header>
</l:TreeListViewItem>
…
</l:TreeListViewItem>
…
</l:TreeListView>
</Window>
We’re done!
I’ll introduce how to encapsulate the implementation details into a control in near future.
Comments
- Anonymous
March 02, 2006
As promised, the ATC team has posted another ListView sample. This sample is amazing because it doesn’t... - Anonymous
March 02, 2006
Dear all,
Your gridview which you provide are impressive.
Do you have a custom gridview supports edit data and show/hide columns.
Thanks
Steven - Anonymous
March 02, 2006
Good suggestion! We will post such a custom gridview in next two weeks. Could you provide more information about your scenario? - Anonymous
March 03, 2006
Could you please also include in your future post the example of how to change the background of every other row in the list provided that only part of the list is displayed (i.e. vertical scrollbar is enabled on the ListView/GridView).
Thank You - Anonymous
March 08, 2006
The comment has been removed - Anonymous
March 08, 2006
Hello, Alex_simkin
you will see the sample by the end of March. Thanks! - Anonymous
March 08, 2006
Hello, Steven
Your scenario is a typical DataGrid one, while the goal of ListView is to diplay items not to edit.
We will give a sample of editing. However, you can't suppose it works exactly as DataGrid. For instance, cell nevigation is not supported.
DataGrid is not provided so far, but it is in planing.
Thanks! - Anonymous
March 13, 2006
Hello, Team
Just want to clarify.
Before March of what year? :)
Alex - Anonymous
March 13, 2006
alex, it will be ready by the end of this March.
Thanks! - Anonymous
August 03, 2006
Hi Team,
Is there a possibility to bind the 'columns' of a grid view to a collection?
A typical example could be a grid that could be bound dynamically to any table data (fields->columns, values->rows)?
Thanks,
Laxman - Anonymous
September 24, 2006
Sample updated.
Add TreeListView.Columns property. So user can use multi TreeListView with different columns in the same app easily. This also avoid the problem of column sharing. Because in ListView (also TreeListView), GridViewColumn object shouldn’t be shared. - Anonymous
February 20, 2007
The comment has been removed - Anonymous
November 14, 2007
PingBack from http://www.mindscape.co.nz/blog/?p=61 - Anonymous
February 15, 2008
Hi Team !Great example, i was looking for a way to display hierarchical data by using a listview and i found this example, thx very much !!As im new to wpf, i have a newbie question...i created my own tree, but the headers are not visible....can you tell me what i've down wrong ? <l:TreeListView></l:TreeListView> <l:TreeListViewItem Header="My tree"> </l:TreeListViewItem>etc ... - Anonymous
February 15, 2008
Sorry i'v done wrong...and not down....and the code looks like <l:TreeListView> <l:TreeListViewItem Header="My tree"> </l:TreeListViewItem></l:TreeListView> - Anonymous
February 25, 2008
How can we show different images for each treelistnode? I tried a lot but no success.. - Anonymous
February 28, 2008
The comment has been removed