1

I am trying to provide a separation (margin) between the top hierarchical items in a WPF TreeView. The problem is that I cannot figure out how to write the Style for it to apply only to the top items and not to every item.

The code for my TreeView looks like this:

<TreeView ItemContainerStyle="{StaticResource treeViewItemStyle}"
          ItemsSource="{Binding Container.RootRules}"
          KeyUp="treeView_KeyUp"
          SelectedItemChanged="TreeView_SelectedItemChanged">
  <TreeView.Resources>
    <HierarchicalDataTemplate DataType="{x:Type me:HybridForecastRulesViewModel}"
                              ItemsSource="{Binding Children}">
      <Border Name="bd"
        ...
      </Border>
    </HierarchicalDataTemplate>

    <HierarchicalDataTemplate DataType="{x:Type me:RootRulesViewModel}"
                              ItemsSource="{Binding Rules}">
      <Grid>
        ...
      </Grid>
    </HierarchicalDataTemplate>
  </TreeView.Resources>
</TreeView>

I have a style for the treeViewItems like this:

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

  <Setter Property="Margin" Value="0,10,0,0" />

  <Style.Triggers>
    <DataTrigger Binding="{Binding IsVisible}" Value="False">
      <Setter Property="Visibility" Value="Collapsed" />
    </DataTrigger>
  </Style.Triggers>

</Style>

But this style applies to items of both type (RootRulesViewModel and HybridForecastRulesViewModel), when I would like for it to only apply to items of type RootRulesViewModel. How can this be done?

And the icing on the cake would be for all RootRulesViewModel items to have a top Margin of 10, except or the first one.

yu_ominae
  • 2,975
  • 6
  • 39
  • 76
  • 1
    Can't you just set an appropriate Margin on the Grid in the HierarchicalDataTemplate for RootRulesViewModel? – Clemens Sep 02 '16 at 10:14
  • I tried that, but it only adds the margin to the item itself, so the item gets moved down, but the arrow to the left remains where it was. It really doesn't look good that way. – yu_ominae Sep 02 '16 at 10:58
  • You could use a Margin of `0,5` (i.e. a bottom and top value of 5) instead of `0,10,0,0`. – Clemens Sep 02 '16 at 11:03
  • Well, that solves the problem with the alignment between the header and the marker, but it also adds a gap between the top node and the following children, which is not quite what I want. How can something seemingly so simple be so difficult to achieve? – yu_ominae Sep 02 '16 at 13:51

2 Answers2

1

TreeViewItem has no Level property to use.

So you have 2 options:

1) this. look at the TreeLevelConverter, it's interesting. They bind the control itself and use the converter to retrieve the Level. In your case, you can extend the converter so after it retrieves the Level, converts it in a Thickness instance to use as Margin.

2) you can create a Level property on your ViewModels (possibly in their base class in order to avoid code duplication). Then, every time that you add a child to a ViewModel node, you set the Level on that child. In the xaml, you bind the Margin property to the Level on the ViewModel, using a converter that returns different Thickness depending if the Level is 1 or not.

EDIT:

This is how you set a common Style for all the TreeViewItem:

<TreeView>
  <TreeView.ItemContainerStyle>
    <Style TargetType="{x:Type TreeViewItem}">
      <Setter 1 .../>
      <Setter 2 .../>
      <Setter Property="Margin"
              Value="{Binding Level, Converter={StaticResource LevelToMarginConverter}}"/>
    </Style>
  </TreeView.ItemContainerStyle>
  <TreeView.Resources>
    <!-- here your hierarchical DataTemplate... -->
    <HierarchicalDataTemplate ... />
  </TreeView.Resources>
</TreeView>
Community
  • 1
  • 1
Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
  • Thanks for your answer. The problem here for me is: how do I access that property on my `TreeViewItem` Style? – yu_ominae Sep 02 '16 at 14:18
  • Thanks, that's clear now. It looks like it's slightly more work than the solution I have already implemented based on header types, but it would be very useful when there is only one hierarchical template. I'll mark this as the answer as it does the trick. Thanks for your help. – yu_ominae Sep 02 '16 at 14:31
0

I've actually ended up with a solution for this. I'm not sure how good it is, so would be open to suggestions to improve.

I've written an object converter from this answer, to get the type of object in the Header of the TreeViewItem. The code looks like this:

            <Style.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Header, Converter={StaticResource ObjectTypeConverter}}"
                             Value="RootRulesViewModel">
                    <Setter Property="Margin"
                            Value="0,0,0,10" />
                </DataTrigger>
                <DataTrigger Binding="{Binding IsVisible}"
                             Value="False">
                    <Setter Property="Visibility"
                            Value="Collapsed" />
                </DataTrigger>
            </Style.Triggers>
        </Style>

Where ObjectTypeConverter is my custom converter which converts the object type to a string value.

Community
  • 1
  • 1
yu_ominae
  • 2,975
  • 6
  • 39
  • 76