WPF - A Stretching TreeView

What is a stretching treeView?

Recently, I found the need to have a TreeView in my WPF application that was only a few levels deep. I didn't want a horizontal scrollbar to appear and I wanted the long text nodes to wrap. So, I invented the StretchingTreeView control.

Why couldn't I do this with a normal WPF TreeView?

Well, you can, but you have to replace the ControlTemplate for all the TreeViewItems. That's either a lot of XAML or a lot of code.

So, what exactly is the problem?

If you look at the default ControlTemplate for a TreeViewItem, you will see that it contains a grid with 3 columns. In the first column is the expander, in the second column is the Header of the TreeViewItem. And in the last column is nothing. The second column is set to a Width of Auto which means that it will size to its contents. The third column is set to a Width of Star which means it will grow and shrink with the width of the TreeView. No matter what you put in the Header of the TreeViewItem, it will never stretch to the right edge of the TreeView. And Since the Header column is set to Auto, it will never cause a TextBlock or anything else to wrap. Instead, if you turn off the horizontal scrollbar, your long text items simply get clipped at the right edge of the TreeView.

So, what's the code to fix it?

Well it was actually very simple. Once I found out that you can control what kind of Controls a TreeView creates for its items. By subclassing TreeView and overriding the methods GetContainerForItemOverride and IsItemItsOwnContainerOverride, you can control what types are created for your tree. This is especially important if you are databinding your tree to some hierarchy of objects and can't just create TreeViewItems directly. In any case here is the code...

     class StretchingTreeView : TreeView
    {
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new StretchingTreeViewItem();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is StretchingTreeViewItem;
        }
    }

    class StretchingTreeViewItem : TreeViewItem
    {
        public StretchingTreeViewItem()
        {
            this.Loaded += new RoutedEventHandler(StretchingTreeViewItem_Loaded);
        }

        private void StretchingTreeViewItem_Loaded(object sender, RoutedEventArgs e)
        {
            // The purpose of this code is to stretch the Header Content all the way accross the TreeView. 
            if (this.VisualChildrenCount > 0)
            {
                Grid grid = this.GetVisualChild(0) as Grid;
                if (grid != null && grid.ColumnDefinitions.Count == 3)
                {
                    // Remove the middle column which is set to Auto and let it get replaced with the 
                    // last column that is set to Star.
                    grid.ColumnDefinitions.RemoveAt(1);
                }
            }
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new StretchingTreeViewItem();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is StretchingTreeViewItem;
        }
    }

Basically, all I do is create my own TreeViewItems that wait until they are loaded and then fix the Grid by removing the middle column. I don't like the fix, because it seems like a hack, but it's hardly any code. And that I like.

I hope this helps somebody out there!