1
votes

I am looking for a way to echo the parent node name at the end of the list of elements using a TreeView (WPF, c#).

The list of information I'm working with can be long, and the I would like to show the user what "category" they are in without having to scroll back up to the top of the list. I have a HierarchicalDataTemplate that does some simple font formatting, but I cannot see where to put the "closing" node, or how to get the text for it.

This is a simplified example of what I am trying to do:

enter image description here

2

2 Answers

2
votes

You can do it at least two ways. First way is to redefine whole treeview style. You can find some examples how to do it here (The example doesn't solve your problem, but you can get the general idea). Downside of this solution is that code is pretty big and it will look the same on every system, like Windows XP of Windows 10, because in normal situation every system uses its own style.

I prefer to solve such a problems another way. I make changes from C# code. For example, in your case, you can see the structure of treeview in Visual Studio Xaml visualiser:enter image description here

Every treeview item contains a ToggleButton - to expand and collapse on the first row of the grid. Next goes a border which contains TextBlock with the content of the item (In your case it can be another control from your datatemplate, I took textblock for the example). It placed on the same first row. In the second row of the grid there is ItemsPresenter containing child elements.

So, the goal is to add one more row two the grid and put a textblock there.

First, add ItemContainerStyle to the treeview and set event handler there:

<TreeView.ItemContainerStyle>
  <Style TargetType="{x:Type TreeViewItem}">
    <EventSetter Event="Loaded" Handler="Item_Loaded"/>
  </Style>
</TreeView.ItemContainerStyle>

And then in the code behind:

void Item_Loaded(object sender, RoutedEventArgs e) {
    // Find the main grid of this TreeView item.
    Grid grid = FindVisualChild<Grid>((DependencyObject) sender);

    // Add new row, because it has only 2 and we need 3
    grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

    // Get the content to put into the textblock (or another control you are using in datatemplate)
    string text = ((Node) grid.DataContext).Name;

    // I'm using TextBlock to show example, you can use your own control
    TextBlock tb = new TextBlock {
        Text = text,
        Foreground = new SolidColorBrush(Colors.Gray),
    };

    grid.Children.Add(tb);

    // Visibility of our modification depends on IsExpanded and amount of child elements. If itemcontainer is collapsed or doesn't have children, we don't show this modification.
    bool flag = false;
    grid.SizeChanged += (sender1, e1) => {
        if (flag) {
            return;
        }
        flag = true;
        tb.Visibility = grid.RowDefinitions[1].ActualHeight > 0
        ? Visibility.Visible
        : Visibility.Collapsed;
        flag = false;
    };

    // Set the position of the added part
    tb.SetValue(Grid.RowProperty, 2);
    tb.SetValue(Grid.ColumnProperty, 1);
}

It is not the ideal solution, Xaml can be different on different systems, so this function can be slightly different. I checked it only on windows 10.

The FindVisualChild function is like this:

static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject {
    for (int i = 0 ; i < VisualTreeHelper.GetChildrenCount(obj) ; i++) {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child is T) {
            return (T) child;
        }
        T childOfChild = FindVisualChild<T>(child);
        if (childOfChild != null) {
            return childOfChild;
        }
    }
    return null;
}
0
votes

I'd actually go a different approach. A few actually.

The first is simply to re-template the TreeViewItem for the parent nodes. Nice and easy considering the 'meat' of it is just a grid with a disclosure toggle, a ContentPresenter for the header (this is the 'item' in the tree) and an ItemsControl (with its ItemsSource bound to the node's children) which just nests more of these containers. Simply add a third row to the grid in the template, insert another content presenter and set its binding too to the header, and you're done. You can do that with a simple style right there. No code-behind needed at all.

The second thing you can do is instead of mucking with the tree itself (which I'm actually not a fan of in the first place!) is to add a completely separate 'crumbtrail' control to show the selected branch in the associated tree. They're really easy to do. On item selection in the tree, walk up the hierarchy, adding the items to a List, then binding that to a ListBox with a horizontal stack panel for its ItemsPanel. Of course you'd want to template it a little to add separators and such, but it also gives you a 'clickable' trail.

Anyway, those are just two thoughts. Perhaps they can even inspire a third.