Get TreeViewItem's parent in HierarchicalDataTemplate in WPF
Asked Answered
C

1

5

I am merging two examples found on the internet. One about stretched selection styles and one about multi-selection in a treeview.

I have everything working, except for the indentations of the treeview. I could give all my code, but that wouldn't solve it.

My problem lies in the following method:

public static class TreeViewItemExtensions
{
    public static int GetDepth(this TreeViewItem item)
    {
        FrameworkElement elem = item;
        while (elem.Parent != null)
        {
            var tvi = elem.Parent as TreeViewItem;
            if (tvi != null)
                return tvi.GetDepth() + 1;
            elem = elem.Parent as FrameworkElement;
        }
        return 0;
    }
}

This method tries to retrieve the depth of a treeviewItem in the tree. The problem is: elem.Parent is always null. Which results in depths of 0.

I think this is happening, because I am using an HierarchicalDataTemplate. A part of my xaml looks like this.

<TreeView Name="folderTree"
          ItemsSource="{Binding Folders}"
          SelectedItemChanged="folderTree_SelectedItemChanged"
          HorizontalContentAlignment="Stretch">

    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Folders}"
                                  DataType="{x:Type Model:IFolder}">

            <StackPanel Orientation="Horizontal">
                <StackPanel.Style>
                    <Style TargetType="{x:Type StackPanel}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding IsSelected}"
                                         Value="True">
                                <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </StackPanel.Style>

                <Image Source="{Binding Converter={StaticResource iconConverter}}" Height="{Binding ElementName=theFile,Path=ActualHeight}"/>
                <TextBlock Text="{Binding FileName}" Name="theFile"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

The XAML for my style of the treeview looks like this:

<Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">

    <!-- leaving some stuff out here -->

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TreeViewItem}">

                <ControlTemplate.Resources>
                    <local:LeftMarginMultiplierConverter Length="19" x:Key="lengthConverter" />
                </ControlTemplate.Resources>

                <StackPanel>
                    <!-- The upper part of the TreeViewItem -->
                    <Border Name="Bd" 
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Padding="{TemplateBinding Padding}">

                        <!-- The margin is what we try to measure, how can we get the parents from the templatedParents? -->
                        <Grid Margin="{Binding Converter={StaticResource lengthConverter}, 
                                               RelativeSource={RelativeSource TemplatedParent}, 
                                               Path=.}">

                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="19" />
                                <ColumnDefinition />
                            </Grid.ColumnDefinitions>

                            <ToggleButton x:Name="Expander" 
                                          Style="{StaticResource ExpandCollapseToggleStyle}"
                                          IsChecked="{Binding Path=IsExpanded, 
                                                      RelativeSource={RelativeSource TemplatedParent}}"
                                          ClickMode="Press"/>

                            <ContentPresenter x:Name="PART_Header"
                                              Grid.Column="1"
                                              ContentSource="Header"
                                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                        </Grid>
                    </Border>

                    <!-- the children of the TreeViewItem -->
                    <ItemsPresenter x:Name="ItemsHost" />
                </StackPanel>

                <!-- leaving some stuff out here with triggers -->

            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

How can I make the HierarchicalDataTemplate fill the Parent property?

Circuitous answered 25/7, 2011 at 14:58 Comment(0)
G
7

I'd scan the visual tree instead.

Here is a simple (even not-so-elegant) solution:

public static class TreeViewItemExtensions
{
    public static int GetDepth(this TreeViewItem item)
    {
        DependencyObject target = item;
        var depth = 0;
        while (target != null)
        {
            if (target is TreeView)
                return depth;
            if (target is TreeViewItem)
                depth++;

            target = VisualTreeHelper.GetParent(target);
        }
        return 0;
    }
}

Cheers.

Guendolen answered 25/7, 2011 at 15:39 Comment(8)
I've seen this one. But how come, when I normally add TreeViewItems to the tree, there is a parent/child relation?Circuitous
Here is another post answering about the Parent property within a DataTemplate: #3255865 However, even the scan of the visual tree is NOT the best way. The most reliable way is to get the depth from the model, just because the tree is a visual representation of a hierarchical model. The visual tree couldn't be reliable. For example, think to a filtered view of your hierarchy: the visual changes, but the depth keeps the same.Guendolen
Yes it seems easier to put it in the model, but how can I specify that in my Style? My first tutorial I reference to overrides the default style of a treeview. How could one use the model inside a style?Circuitous
Sorry, but I misunderstand your target. The logical depth should be calculated in the model, while the visual depth can rely only to the visual layer. Doesn't work the function above with the converter of the referred sample?Guendolen
Yes it does work, but I'm just curious on how you would do it in the MVVM model. I'll post my XAML for the style of a treeviewitem and where the depth needs to be calculated.Circuitous
There's no a big difference: in the model (or view-model) you keep updated the actual depth, as an integer meaning how many levels an item is nested. That would be a generic info, not involving the view at any time. In the view-model or the xaml, you may calculate the margin width. The point is that the depth level is NOT calculated upon the visual tree (that could be even missing), but on the real data.Guendolen
@MarioVernari let us continue this discussion in chatCircuitous
Ah yes of course, I just need to use {Binding} to access the datacontext, even in a Style. I will keep it with the visualtreehelper, because you are right about the view calculating it. But I needed this option for other purposes as well.Circuitous

© 2022 - 2024 — McMap. All rights reserved.