dotNET
dotNET

Reputation: 35450

TreeView: Place selection indicator along the item at the left edge of the control

So the requirement is simple, but the solution doesn't seem to be (or at least I haven't succeeded yet). I need to display a vertical bar at the left side of the currently selected item of the TreeView control. Something like this:

enter image description here

Problem I'm facing is that with child items, this indicator also moves towards right, as it is part of the ItemTemplate, like this:

enter image description here

This is undesirable. I need the red indicator to stick to the left edge of the control, like this:

enter image description here

I can see why this happens. The ItemsPresenter in TreeViewItem template introduces a left margin of 16 units, which causes the all child items to move right-wards as well. I can't figure out how to avoid it.

Note: The red bar is a Border with StrokeThickness set to 4,0,0,0. It encompasses the Image and TextBlock elements inside it, though this doesn't directly have anything to do with the problem.

Upvotes: -2

Views: 191

Answers (1)

emoacht
emoacht

Reputation: 3601

As you are aware, since the left vacant space is outside of ItemsPresenter which hosts the content of TreeViewItem, you cannot accomplish it by ordinary Style.

Instead, a workaround would be to change the bar to an element such as Rentangle and move it to the edge of TreeView. For example, it can be done by an attached property which is to be attached to the element and move it to the edge of TreeView with a specified left margin.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

public static class TreeViewHelper
{
    public static double? GetLeftMargin(DependencyObject obj)
    {
        return (double?)obj.GetValue(LeftMarginProperty);
    }
    public static void SetLeftMargin(DependencyObject obj, double value)
    {
        obj.SetValue(LeftMarginProperty, value);
    }
    public static readonly DependencyProperty LeftMarginProperty =
        DependencyProperty.RegisterAttached("LeftMargin", typeof(double?), typeof(TreeViewHelper), new PropertyMetadata(null, OnValueChanged));

    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((d is FrameworkElement element) && (e.NewValue is double leftMargin))
        {
            element.Loaded += (_, _) =>
            {
                TreeView? tv = GetTreeView(element);
                if (tv is null)
                    return;

                Point relativePosition = element.TransformToAncestor(tv).Transform(new Point(0, 0));
                element.RenderTransform = new TranslateTransform(leftMargin - relativePosition.X, 0);
            };
        }
    }

    private static TreeView? GetTreeView(FrameworkElement element)
    {
        DependencyObject test = element;
        while (test is not null)
        {
            test = VisualTreeHelper.GetParent(test);
            if (test is TreeView tv)
                return tv;
        }
        return null;
    }
}

Edit:

This workaround does not depend on how to show/hide the bar upon selection of the ListViewItem. Although the question does not provide the actual code for this, if you implement a mechanism to change BorderBrush upon selelection, you can modify it to change Fill of the bar (in the case of Rectangle).

Upvotes: 0

Related Questions