none
Show TreeView nodes connected with dotted lines

    Question

  • Has anybody done any work with the TreeView control to replicate the ShowLines/ShowRootLines properties from the Windows Forms Treeview and draw dotted lines to connect nodes.

    I can see that it may not be too difficult to draw the horizontal line of dots but the vertical line looks more challenging.

    Sunday, January 07, 2007 2:18 AM

Answers

  • I've modified the example TreeViewItem template to add connected lines.  The difficult part is to not draw the line on the last item in the list.  To do this, I added a data trigger to change the line appearance and used a converter that returned true if the TreeViewItem was the last item in the list.  Here's my style:

    <Window x:Class="WindowsApplication3.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WindowsApplication3" Height="300" Width="300"
        xmlns:local="clr-namespace:WindowsApplication3"
        >
      <Window.Resources>
        <local:TreeViewLineConverter x:Key="LineConverter"/>

        <SolidColorBrush x:Key="GlyphBrush" Color="#444" />

        <!--=================================================================
          TreeViewItem
      ==================================================================-->
        <Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
          <Setter Property="Focusable" Value="False"/>
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="ToggleButton">
                <Grid
                  Width="15"
                  Height="13"
                  Background="White">
                  <Path x:Name="ExpandPath"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    Margin="1,1,1,1"
                    Fill="{StaticResource GlyphBrush}"
                    Data="M 4 0 L 8 4 L 4 8 Z"/>
                </Grid>
                <ControlTemplate.Triggers>
                  <Trigger Property="IsChecked"
                       Value="True">
                    <Setter Property="Data"
                        TargetName="ExpandPath"
                        Value="M 0 4 L 8 4 L 4 8 Z"/>
                  </Trigger>
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
        <Style x:Key="TreeViewItemFocusVisual">
          <Setter Property="Control.Template">
            <Setter.Value>
              <ControlTemplate>
                <Border>
                  <Rectangle Margin="0,0,0,0"
                         StrokeThickness="5"
                         Stroke="Black"
                         StrokeDashArray="1 2"
                         Opacity="0"/>
                </Border>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
        <Style x:Key="{x:Type TreeViewItem}"
             TargetType="{x:Type TreeViewItem}">
          <Setter Property="Background"
              Value="Transparent"/>
          <Setter Property="HorizontalContentAlignment"
              Value="{Binding Path=HorizontalContentAlignment,
                  RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
          <Setter Property="VerticalContentAlignment"
              Value="{Binding Path=VerticalContentAlignment,
                  RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
          <Setter Property="Padding"
              Value="1,0,0,0"/>
          <Setter Property="Foreground"
              Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
          <Setter Property="FocusVisualStyle"
              Value="{StaticResource TreeViewItemFocusVisual}"/>
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="{x:Type TreeViewItem}">
                <Grid>
                  <Grid.ColumnDefinitions>
                    <ColumnDefinition MinWidth="19"
                              Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                  </Grid.ColumnDefinitions>
                  <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition/>
                  </Grid.RowDefinitions>

                  <!-- Connecting Lines -->
                  <Rectangle x:Name="HorLn" Height="1" Stroke="#8888"  Margin="10,0,0,0" SnapsToDevicePixels="true"/>
                  <Rectangle x:Name="VerLn" Width="1" Stroke="#8888" Grid.RowSpan="2" SnapsToDevicePixels="true"/>
                  <ToggleButton x:Name="Expander"
                          Style="{StaticResource ExpandCollapseToggleStyle}"
                          IsChecked="{Binding Path=IsExpanded,
                                  RelativeSource={RelativeSource TemplatedParent}}"
                          ClickMode="Press"/>
                  <Border Name="Bd"
                      Grid.Column="1"
                      Background="{TemplateBinding Background}"
                      BorderBrush="{TemplateBinding BorderBrush}"
                      BorderThickness="{TemplateBinding BorderThickness}"
                      Padding="{TemplateBinding Padding}">
                    <ContentPresenter x:Name="PART_Header"
                              ContentSource="Header"
                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                  </Border>
                  <ItemsPresenter x:Name="ItemsHost"
                          Grid.Row="1"
                          Grid.Column="1"
                          Grid.ColumnSpan="2"/>
                </Grid>
                <ControlTemplate.Triggers>

                  <!-- This trigger changes the connecting lines if the item is the last in the list -->
                  <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineConverter}}" Value="true">
                    <Setter TargetName="VerLn"
                        Property="Height"
                        Value="6"/>
                    <Setter TargetName="VerLn"
                        Property="VerticalAlignment"
                        Value="Top"/>
                  </DataTrigger>
                  <Trigger Property="IsExpanded"
                       Value="false">
                    <Setter TargetName="ItemsHost"
                        Property="Visibility"
                        Value="Collapsed"/>
                  </Trigger>
                  <Trigger Property="HasItems"
                       Value="false">
                    <Setter TargetName="Expander"
                        Property="Visibility"
                        Value="Hidden"/>
                   </Trigger>
                  <MultiTrigger>
                    <MultiTrigger.Conditions>
                      <Condition Property="HasHeader"
                             Value="false"/>
                      <Condition Property="Width"
                             Value="Auto"/>
                    </MultiTrigger.Conditions>
                    <Setter TargetName="PART_Header"
                        Property="MinWidth"
                        Value="75"/>
                  </MultiTrigger>
                  <MultiTrigger>
                    <MultiTrigger.Conditions>
                      <Condition Property="HasHeader"
                             Value="false"/>
                      <Condition Property="Height"
                             Value="Auto"/>
                    </MultiTrigger.Conditions>
                    <Setter TargetName="PART_Header"
                        Property="MinHeight"
                        Value="19"/>
                  </MultiTrigger>
                  <Trigger Property="IsSelected"
                       Value="true">
                    <Setter TargetName="Bd"
                        Property="Background"
                        Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                    <Setter Property="Foreground"
                        Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                  </Trigger>
                  <MultiTrigger>
                    <MultiTrigger.Conditions>
                      <Condition Property="IsSelected"
                             Value="true"/>
                      <Condition Property="IsSelectionActive"
                             Value="false"/>
                    </MultiTrigger.Conditions>
                    <Setter TargetName="Bd"
                        Property="Background"
                        Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                    <Setter Property="Foreground"
                        Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                  </MultiTrigger>
                  <Trigger Property="IsEnabled"
                       Value="false">
                    <Setter Property="Foreground"
                        Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                  </Trigger>
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>

      </Window.Resources>
      <DockPanel>
        <TreeView>
          <TreeViewItem Header="Employee1" IsExpanded="true">
            <TreeViewItem Header="Jesper"/>
            <TreeViewItem Header="Aaberg"/>
            <TreeViewItem Header="12345"/>
          </TreeViewItem>
          <TreeViewItem Header="Employee2">
            <TreeViewItem Header="Dominik"/>
            <TreeViewItem Header="Paiha"/>
            <TreeViewItem Header="98765"/>
          </TreeViewItem>
        </TreeView>


      </DockPanel>
    </Window>

     

    And the converter class.

        class TreeViewLineConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                TreeViewItem item = (TreeViewItem)value;
                ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
                return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
            }

            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

    Tuesday, January 30, 2007 10:49 PM

All replies

  • Have you had any luck with this? I can't seem to find an easy way either.
    Monday, January 29, 2007 7:00 AM
  • No I haven't and obviously nobody else has either given the lack of responses.

    When I get some spare time I'll take another look at it.

    It's worrying how difficult it is to style WPF controls to look like their Windows Forms counterparts. I've been trying to change Menus and MenuItems to look like the ToolStrip equivalents and it's not easy.

    Steve

     

     

     

    Monday, January 29, 2007 9:16 PM
  • I've modified the example TreeViewItem template to add connected lines.  The difficult part is to not draw the line on the last item in the list.  To do this, I added a data trigger to change the line appearance and used a converter that returned true if the TreeViewItem was the last item in the list.  Here's my style:

    <Window x:Class="WindowsApplication3.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WindowsApplication3" Height="300" Width="300"
        xmlns:local="clr-namespace:WindowsApplication3"
        >
      <Window.Resources>
        <local:TreeViewLineConverter x:Key="LineConverter"/>

        <SolidColorBrush x:Key="GlyphBrush" Color="#444" />

        <!--=================================================================
          TreeViewItem
      ==================================================================-->
        <Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
          <Setter Property="Focusable" Value="False"/>
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="ToggleButton">
                <Grid
                  Width="15"
                  Height="13"
                  Background="White">
                  <Path x:Name="ExpandPath"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Center"
                    Margin="1,1,1,1"
                    Fill="{StaticResource GlyphBrush}"
                    Data="M 4 0 L 8 4 L 4 8 Z"/>
                </Grid>
                <ControlTemplate.Triggers>
                  <Trigger Property="IsChecked"
                       Value="True">
                    <Setter Property="Data"
                        TargetName="ExpandPath"
                        Value="M 0 4 L 8 4 L 4 8 Z"/>
                  </Trigger>
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
        <Style x:Key="TreeViewItemFocusVisual">
          <Setter Property="Control.Template">
            <Setter.Value>
              <ControlTemplate>
                <Border>
                  <Rectangle Margin="0,0,0,0"
                         StrokeThickness="5"
                         Stroke="Black"
                         StrokeDashArray="1 2"
                         Opacity="0"/>
                </Border>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>
        <Style x:Key="{x:Type TreeViewItem}"
             TargetType="{x:Type TreeViewItem}">
          <Setter Property="Background"
              Value="Transparent"/>
          <Setter Property="HorizontalContentAlignment"
              Value="{Binding Path=HorizontalContentAlignment,
                  RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
          <Setter Property="VerticalContentAlignment"
              Value="{Binding Path=VerticalContentAlignment,
                  RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
          <Setter Property="Padding"
              Value="1,0,0,0"/>
          <Setter Property="Foreground"
              Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
          <Setter Property="FocusVisualStyle"
              Value="{StaticResource TreeViewItemFocusVisual}"/>
          <Setter Property="Template">
            <Setter.Value>
              <ControlTemplate TargetType="{x:Type TreeViewItem}">
                <Grid>
                  <Grid.ColumnDefinitions>
                    <ColumnDefinition MinWidth="19"
                              Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                  </Grid.ColumnDefinitions>
                  <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition/>
                  </Grid.RowDefinitions>

                  <!-- Connecting Lines -->
                  <Rectangle x:Name="HorLn" Height="1" Stroke="#8888"  Margin="10,0,0,0" SnapsToDevicePixels="true"/>
                  <Rectangle x:Name="VerLn" Width="1" Stroke="#8888" Grid.RowSpan="2" SnapsToDevicePixels="true"/>
                  <ToggleButton x:Name="Expander"
                          Style="{StaticResource ExpandCollapseToggleStyle}"
                          IsChecked="{Binding Path=IsExpanded,
                                  RelativeSource={RelativeSource TemplatedParent}}"
                          ClickMode="Press"/>
                  <Border Name="Bd"
                      Grid.Column="1"
                      Background="{TemplateBinding Background}"
                      BorderBrush="{TemplateBinding BorderBrush}"
                      BorderThickness="{TemplateBinding BorderThickness}"
                      Padding="{TemplateBinding Padding}">
                    <ContentPresenter x:Name="PART_Header"
                              ContentSource="Header"
                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                  </Border>
                  <ItemsPresenter x:Name="ItemsHost"
                          Grid.Row="1"
                          Grid.Column="1"
                          Grid.ColumnSpan="2"/>
                </Grid>
                <ControlTemplate.Triggers>

                  <!-- This trigger changes the connecting lines if the item is the last in the list -->
                  <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineConverter}}" Value="true">
                    <Setter TargetName="VerLn"
                        Property="Height"
                        Value="6"/>
                    <Setter TargetName="VerLn"
                        Property="VerticalAlignment"
                        Value="Top"/>
                  </DataTrigger>
                  <Trigger Property="IsExpanded"
                       Value="false">
                    <Setter TargetName="ItemsHost"
                        Property="Visibility"
                        Value="Collapsed"/>
                  </Trigger>
                  <Trigger Property="HasItems"
                       Value="false">
                    <Setter TargetName="Expander"
                        Property="Visibility"
                        Value="Hidden"/>
                   </Trigger>
                  <MultiTrigger>
                    <MultiTrigger.Conditions>
                      <Condition Property="HasHeader"
                             Value="false"/>
                      <Condition Property="Width"
                             Value="Auto"/>
                    </MultiTrigger.Conditions>
                    <Setter TargetName="PART_Header"
                        Property="MinWidth"
                        Value="75"/>
                  </MultiTrigger>
                  <MultiTrigger>
                    <MultiTrigger.Conditions>
                      <Condition Property="HasHeader"
                             Value="false"/>
                      <Condition Property="Height"
                             Value="Auto"/>
                    </MultiTrigger.Conditions>
                    <Setter TargetName="PART_Header"
                        Property="MinHeight"
                        Value="19"/>
                  </MultiTrigger>
                  <Trigger Property="IsSelected"
                       Value="true">
                    <Setter TargetName="Bd"
                        Property="Background"
                        Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                    <Setter Property="Foreground"
                        Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                  </Trigger>
                  <MultiTrigger>
                    <MultiTrigger.Conditions>
                      <Condition Property="IsSelected"
                             Value="true"/>
                      <Condition Property="IsSelectionActive"
                             Value="false"/>
                    </MultiTrigger.Conditions>
                    <Setter TargetName="Bd"
                        Property="Background"
                        Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                    <Setter Property="Foreground"
                        Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                  </MultiTrigger>
                  <Trigger Property="IsEnabled"
                       Value="false">
                    <Setter Property="Foreground"
                        Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                  </Trigger>
                </ControlTemplate.Triggers>
              </ControlTemplate>
            </Setter.Value>
          </Setter>
        </Style>

      </Window.Resources>
      <DockPanel>
        <TreeView>
          <TreeViewItem Header="Employee1" IsExpanded="true">
            <TreeViewItem Header="Jesper"/>
            <TreeViewItem Header="Aaberg"/>
            <TreeViewItem Header="12345"/>
          </TreeViewItem>
          <TreeViewItem Header="Employee2">
            <TreeViewItem Header="Dominik"/>
            <TreeViewItem Header="Paiha"/>
            <TreeViewItem Header="98765"/>
          </TreeViewItem>
        </TreeView>


      </DockPanel>
    </Window>

     

    And the converter class.

        class TreeViewLineConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                TreeViewItem item = (TreeViewItem)value;
                ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
                return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
            }

            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

    Tuesday, January 30, 2007 10:49 PM
  • For the ToolStrip menu, if you place menu inside a toolbar, you will get a different appearance that is pretty close to how winforms looks.  You'll also want to restyle the toolbar so it can't be moved:

    <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <DockPanel>
        <ToolBarTray DockPanel.Dock="Top">
          <ToolBar>
            <ToolBar.Template>
              <ControlTemplate><ItemsPresenter/></ControlTemplate>
            </ToolBar.Template>
            <Menu Background="Transparent">
              <MenuItem Header="File">
                <MenuItem Command="Save"/>
                <MenuItem Command="Open"/>
                <MenuItem Command="Close"/>
              </MenuItem>
              <MenuItem Header="Edit">
                <MenuItem Command="Cut"/>
                <MenuItem Command="Copy"/>
                <MenuItem Command="Paste"/>
              </MenuItem>
            </Menu>
          </ToolBar>
        </ToolBarTray>
        <TextBox AcceptsReturn="true"/>
      </DockPanel>
    </Page>

    Tuesday, January 30, 2007 10:52 PM
  • That's great Neil. Many thanks for your help.
    Thursday, February 01, 2007 9:06 PM
  • Hi,

     

    The last node doesn't have line visible. But when I new node is added after that the previous one should have the connected line. Is there anyway to do that,

     

    Thanks

    Anusha

     

    Friday, May 25, 2007 1:53 PM
  • Can anybody here help me to put dotted lines in this sample organization chart created by Miguel Saez.. Here's the link

    http://staff.southworks.net/blogs/msaez/archive/2007/03/05/Organization-Chart-using-WPF.aspx


    Thanks for help!
    Tuesday, June 19, 2007 6:05 AM
  • Hi Neill,

    I tried to apply your dotted lines solution to my tree list view. This combines a tree view with a grid view. The problem is, that the grid for the lines affects the gid of the columns, too. Do you have a hint how to solve this problem? Here is my code, the alignment of the lines is not done yet. My example ist just a little try with the dotted lines.

    Thanks
    Stefanie

     

    <Style TargetType="{x:Type l:TreeListViewItem}">  
            <Setter Property="KeyboardNavigation.TabNavigation" Value="Continue" /> 
            <Setter Property="Background" Value="Transparent"/>  
            <Setter Property="HorizontalContentAlignment" Value="{Binding Path=HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>  
            <Setter Property="VerticalContentAlignment" Value="{Binding Path=VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>  
            <Setter Property="Padding" Value="1,0,0,0"/>  
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>  
            <Setter Property="Template">  
                <Setter.Value> 
                    <ControlTemplate TargetType="{x:Type l:TreeListViewItem}">  
                        <Grid> 
                            <Grid.ColumnDefinitions> 
                                <ColumnDefinition MinWidth="19" Width="Auto"/>  
                                <ColumnDefinition Width="Auto"/>  
                                <ColumnDefinition Width="Auto"/>  
                                <ColumnDefinition Width="*"/>  
                            </Grid.ColumnDefinitions> 
                            <Grid.RowDefinitions> 
                                <RowDefinition Height="Auto"/>  
                                <RowDefinition/> 
                            </Grid.RowDefinitions> 
     
                            <Rectangle x:Name="HorLn" Height="1" Stroke="#8888" Width="19" 
                                Grid.Column="0" Grid.Row="0" Grid.RowSpan="2"   
                                HorizontalAlignment="Right" SnapsToDevicePixels="true"/>  
                            <Rectangle x:Name="VerLn" Width="1" Stroke="#8888" HorizontalAlignment="Center" 
                                Grid.Row="0" Grid.Column="1" SnapsToDevicePixels="true"/>                          
                            <ToggleButton x:Name="Expander"   
                                Grid.Column="2" Grid.Row="0" 
                                Style="{StaticResource ProjectViewToggleButton}" 
                                Margin="{Binding Level,Converter={StaticResource LevelToIndentConverter},RelativeSource={RelativeSource AncestorType={x:Type l:TreeListViewItem}}}" 
                                IsChecked="{Binding Path=IsExpanded,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListViewItem}}}" 
                                ClickMode="Press"/>  
                            <Border Name="Bd" Background="{TemplateBinding Background}" 
                                Grid.Column="3" Grid.Row="0" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                Padding="{TemplateBinding Padding}">  
                                    <GridViewRowPresenter x:Name="PART_Header" 
                                        Content="{TemplateBinding Header}" 
                                        Columns="{Binding Path=Columns,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListView}}}" /> 
                            </Border> 
                            <ItemsPresenter   
                                Grid.Column="2" Grid.Row="1" Grid.ColumnSpan="2" 
                                x:Name="ItemsHost" /> 
                        </Grid> 
                          
                        <!--<StackPanel> 
                            <Border Name="Bd" Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                Padding="{TemplateBinding Padding}">  
                                <GridViewRowPresenter x:Name="PART_Header" 
                                        Content="{TemplateBinding Header}" 
                                        Columns="{Binding Path=Columns,RelativeSource={RelativeSource AncestorType={x:Type l:TreeListView}}}" /> 
                            </Border> 
                            <ItemsPresenter x:Name="ItemsHost" /> 
                        </StackPanel>--> 
                        <ControlTemplate.Triggers> 
                            <Trigger Property="IsExpanded" Value="false">  
                                <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>  
                            </Trigger> 
                            <MultiTrigger> 
                                <MultiTrigger.Conditions> 
                                    <Condition Property="HasHeader" Value="false"/>  
                                    <Condition Property="Width" Value="Auto"/>  
                                </MultiTrigger.Conditions> 
                                <Setter TargetName="PART_Header" Property="MinWidth" Value="75"/>  
                            </MultiTrigger> 
                            <MultiTrigger> 
                                <MultiTrigger.Conditions> 
                                    <Condition Property="HasHeader" Value="false"/>  
                                    <Condition Property="Height" Value="Auto"/>  
                                </MultiTrigger.Conditions> 
                                <Setter TargetName="PART_Header" Property="MinHeight" Value="19"/>  
                            </MultiTrigger> 
                            <Trigger Property="IsSelected" Value="true">  
                                <Setter TargetName="Bd" Property="Background" 
                                    Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>  
                                <Setter Property="Foreground" 
                                    Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>  
                            </Trigger> 
                            <MultiTrigger> 
                                <MultiTrigger.Conditions> 
                                    <Condition Property="IsSelected" Value="true"/>  
                                    <Condition Property="IsSelectionActive" Value="false"/>  
                                </MultiTrigger.Conditions> 
                                <Setter TargetName="Bd" Property="Background" 
                                    Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>  
                                <Setter Property="Foreground" 
                                    Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>  
                            </MultiTrigger> 
                            <Trigger Property="IsEnabled" Value="false">  
                                <Setter Property="Foreground" 
                                    Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>  
                            </Trigger> 
                        </ControlTemplate.Triggers> 
                    </ControlTemplate> 
                </Setter.Value> 
            </Setter> 
        </Style> 

     

    Wednesday, September 03, 2008 10:36 AM
  • Hi,
    I got the similar requirement and the code published here worked for me perfectly. But my application have some classes extended from the TreeViewItem Calss and i populate the dynamically on the treeView. The code here doesn't work for those dynamically populated extended treeview item classes. Can some onle help me regarding this please?

    Monday, September 22, 2008 2:28 PM
  • This example code is great.
    To make it more robust, I tried to bind the height of the vertical line on the last item in the list to the half of the height of the last item.
    The height of the vertical line in the example is hard coded in the DataTrigger. Which makes the line not corrected if the height of the TreeviewItem becomes larger when the font size is changed.

                                    <Setter TargetName="VerLn"
                                        Property="Height"
                                        Value="8"/>

    I can bind the height to the height of TreeViewItem. And I have to use a value converter to half the height.
    Is there anyway that I can do it in XAML, I don't want to create a code-behind file  to hold the value converter.
                               <Setter TargetName="VerLn"
                                        Property="Height"
                                        Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActualHeight, Converter={StaticResource LineHeightConverter}}"/>
    • Edited by Jetsun Wednesday, November 12, 2008 6:14 AM
    Wednesday, November 12, 2008 6:06 AM
  • This code works fine until you try to add nodes, as AnushaD said.

    The problem is that when the node is added there are no changes in existing nodes that can be used by the trigger in XAML.

    I have used attached properties trick described by Dan Crevier to overcome the problem.

    The idea is that by attaching the property to an item I also attach another property to it, that monitors changes to the item's containing collection. When collection changes the monitor checks if the item is the last one in there and sets another attached property - IsLastOne accordingly. The original XAML is modified to set the line on the item according to that property.

    Here is the code for the ItemExtender class that holds attached properties and monitors container changes:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    
    namespace WindowsApplication3
    {
      public class TVIExtender
      {
        private TreeViewItem _item;
    
        public static DependencyProperty UseExtenderProperty =
          DependencyProperty.RegisterAttached("UseExtender", typeof(bool), typeof(TVIExtender),
                                              new PropertyMetadata(false, new PropertyChangedCallback(OnChangedUseExtender)));
    
        public static bool GetUseExtender(DependencyObject sender)
        {
          return (bool)sender.GetValue(UseExtenderProperty);
        }
        public static void SetUseExtender(DependencyObject sender, bool useExtender)
        {
          sender.SetValue(UseExtenderProperty, useExtender);
        }
    
        private static void OnChangedUseExtender(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
          TreeViewItem item = sender as TreeViewItem;
          if (null != item)
          {
            if ((bool)e.NewValue)
            {
              if (item.ReadLocalValue(ItemExtenderProperty) == DependencyProperty.UnsetValue)
              {
                TVIExtender extender = new TVIExtender(item);
                item.SetValue(ItemExtenderProperty, extender);
              }
            }
            else
            {
              if (item.ReadLocalValue(ItemExtenderProperty) != DependencyProperty.UnsetValue)
              {
                TVIExtender extender = (TVIExtender)item.ReadLocalValue(ItemExtenderProperty);
                extender.Detach();
                item.SetValue(ItemExtenderProperty, DependencyProperty.UnsetValue);
              }
            }
          }
        }
    
        public static DependencyProperty ItemExtenderProperty =
          DependencyProperty.RegisterAttached("ItemExtender", typeof(TVIExtender), typeof(TVIExtender));
    
        public static DependencyProperty IsLastOneProperty =
          DependencyProperty.RegisterAttached("IsLastOne", typeof(bool), typeof(TVIExtender));
    
        public static bool GetIsLastOne(DependencyObject sender)
        {
          return (bool)sender.GetValue(IsLastOneProperty);
        }
        public static void SetIsLastOne(DependencyObject sender, bool isLastOne)
        {
          sender.SetValue(IsLastOneProperty, isLastOne);
        }
    
        public TVIExtender(TreeViewItem item)
        {
          _item = item;
    
          ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(_item);
          ic.ItemContainerGenerator.ItemsChanged += OnItemsChangedItemContainerGenerator;
          
          _item.SetValue(IsLastOneProperty,
                   ic.ItemContainerGenerator.IndexFromContainer(_item) == ic.Items.Count - 1);
        }
    
        void OnItemsChangedItemContainerGenerator(object sender, ItemsChangedEventArgs e)
        {
          ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(_item);
    
          if (null != ic)
            _item.SetValue(IsLastOneProperty,
                           ic.ItemContainerGenerator.IndexFromContainer(_item) == ic.Items.Count - 1);
        }
    
        private void Detach()
        {
          if (_item != null)
          {
            ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(_item);
            ic.ItemContainerGenerator.ItemsChanged -= OnItemsChangedItemContainerGenerator;
    
            _item = null;
          }
        }
      }
    }
    

    In the XAML add this setter to TreeViewItem style:
    <Setter Property="local:TVIExtender.UseExtender" Value="True"/>
    and change the following tregger:
    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineConverter}}" Value="true">
        <Setter TargetName="VerLn" Property="Height" Value="6"/>
        <Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
    </DataTrigger>
    
    to:
    <Trigger Property="local:TVIExtender.IsLastOne" Value="True">
        <Setter TargetName="VerLn" Property="Height" Value="6"/>
        <Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
    </Trigger>
    

    Not the most elegant solution, but works just fine :)

    Alex.
    • Edited by Alex.P Friday, June 05, 2009 4:53 PM
    Friday, June 05, 2009 4:27 PM
  • If height any item is more 20 vertical line is  short.

    <Trigger Property="local:TreeViewExtender.IsLastOne" Value="True">
                                    <Setter TargetName="VerLn" Property="Height" 
                                            Value="{Binding RelativeSource={RelativeSource FindAncestor ,  AncestorType=TreeViewItem},Converter={StaticResource LineHConverter}}"/>
                                    <Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
                                </Trigger>
    The code:
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                TreeViewItem item = (TreeViewItem)value;
                return item.ActualHeight/2;
            }



    Tuesday, December 29, 2009 4:38 AM
  • @Alex.P

    Thanks, you're a life saver!
    Thursday, January 07, 2010 10:51 AM
  • Tuesday, May 24, 2011 12:06 PM
  • WoW, thanks Neil...
    Thursday, November 10, 2011 10:01 AM