TreeViewItem Header won't stretch?
This problem occurs because WPF's default template for TreeViewItem
is set up as a 3-column by 2-row Grid
. The first row is for the "header" (actually a Border
), and the second row is for the ItemsPresenter
. The two rows are made visible or hidden as necessary, in order to accomplish the tree expansion when you click the little triangle--which occupies column zero of the Grid
.
Both rows really only need one additional column. For example, in the second row, we must have nothing at col-0, row-1, because that blank part should be indented when IsExpanded
is true. But the mystery begins when we note that the ItemsPresenter
, based in col-1, row-1, specifies Grid.ColumnSpan=2
.
Unfortunately in the top row, the Border
which holds the header is set to Grid.Column=1
... but no ColumnSpan. Since the col-2 of the Grid
has Width=*
this means the header/border won't stretch horizontally.
In other words, I seems like the 3-column grid design has no purpose except to specifically to prevent the header from stretching. As far as I can tell, a simple 2x2 arrangement would be more flexible [edit: see footnote #2], and support either full-stretching or the 'jagged' header non-stretching, via the regular WPF
alignment mechanisms.
Ideally, we'd change the Grid
to have only 2 columns instead of 3. Since that's not so easy, instead we'll make the header span 2 columns, just like the ItemsPresenter
does.
Ok, here is a small, complete, self-contained (XAML-only) working program which demonstrates--and fixes--the problem:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/netfx/2007/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:System.Reflection;assembly=mscorlib"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Width="800" SizeToContent="Manual">
<TreeView ItemsSource="{Binding Source={StaticResource data}}"
VirtualizingStackPanel.VirtualizationMode="Recycling"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingPanel.ScrollUnit="Item">
<TreeView.Resources>
<ObjectDataProvider x:Key="data" ObjectInstance="{x:Static sys:AppDomain.CurrentDomain}" MethodName="GetAssemblies" />
<HierarchicalDataTemplate DataType="{x:Type r:Assembly}" ItemsSource="{Binding Path=DefinedTypes}" >
<TextBlock Text="{Binding Path=Location}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type sys:Type}" ItemsSource="{Binding Path=CustomAttributes}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type r:CustomAttributeData}" ItemsSource="{Binding Path=ConstructorArguments}">
<TextBlock Text="{Binding Path=AttributeType.Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- == == BEGIN HERE == == -->
<Style.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="Grid.ColumnSpan" Value="2" />
</Style>
</Style.Resources>
<!-- == == == END == == == -->
<Setter Property="Background" Value="LightBlue" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Window>
If you run this program as shown you will see something like this. This is the altered (modified) behavior, which allows you to regain full control over the stretching behavior of the TreeViewItem
header:
Notice the BEGIN/END part with the dotted lines in the XAML source. Basically, I just set Grid.ColumnSpan=2
on the offending Border
, so that it will fill the stretched width of the Grid
. That element is emitted by the TreeViewItem
template, so I found that an effective way to alter its properties is via a targeting Style
in the resource dictionary of the TreeViewItem
's Style
. Yes, confusing. That Style
is accessed via TreeViewItem.ItemContainerStyle
.
To see the (existing) broken behavior, you can comment out the part between the dotted lines:
You can also set these styles in some resource dictionary, rather than using the ItemContainerStyle
property as I did here. I did it this way because it minimizes the scope of the fix, so that unrelated Border
controls won't be affected. If you do need a more discriminative way to target just this control, you might be able to take advantage of the fact that it has Name='Bd'
.
[edit:] This solution does not use reflection! Don't be scared by the meaningless demo data--it has nothing to do with this issue; it was just the easiest way to grab some hierarchical data for demonstration purposes, while keeping the entire program tiny.
[edit #2:] I just realized that what the designers were trying to avoid with the 3x2 grid arrangement was the following unsightly effect (exaggerated here by a zoomed-out screenshot). So if you adopt one of the solutions from this page, be forewarned that you might not want this: