I'm trying to implement to implement a treeview winforms-like with the dotted lines connecting the nodes, but I'm having to problems. The first is that when I add a node with that parent node expanded the vertical line is not drawn, only if it's colapsed. The second one is that when I remove the first child of a level, the "new first child" is not with the vertical line drawn. I want to update the tree dinamically.
<!-- A style modifying WPF original style to allow TreeViewItem to be stretched --><Style x:Key="StretchedTreeViewItemControlTemplate" TargetType="{x:Type TreeViewItem}"><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><ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"><ToggleButton.Style><Style TargetType="{x:Type ToggleButton}"><Setter Property="Focusable" Value="False"/><Setter Property="Width" Value="16"/><Setter Property="Height" Value="16"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type ToggleButton}"><Border Background="Transparent" Height="16" Padding="5" Width="16"><Path x:Name="ExpandPath" Data="M0,0 L0,6 L6,0 z" Fill="Transparent" Stroke="#FF989898"><Path.RenderTransform><RotateTransform Angle="135" CenterY="3" CenterX="3"/></Path.RenderTransform></Path></Border><ControlTemplate.Triggers><Trigger Property="IsChecked" Value="True"><Setter Property="RenderTransform" TargetName="ExpandPath"><Setter.Value><RotateTransform Angle="180" CenterY="3" CenterX="3"/></Setter.Value></Setter><Setter Property="Fill" TargetName="ExpandPath" Value="#FF595959"/><Setter Property="Stroke" TargetName="ExpandPath" Value="#FF262626"/></Trigger><Trigger Property="IsMouseOver" Value="True"><Setter Property="Stroke" TargetName="ExpandPath" Value="#FF1BBBFA"/><Setter Property="Fill" TargetName="ExpandPath" Value="Transparent"/></Trigger><MultiTrigger><MultiTrigger.Conditions><Condition Property="IsMouseOver" Value="True"/><Condition Property="IsChecked" Value="True"/></MultiTrigger.Conditions><Setter Property="Stroke" TargetName="ExpandPath" Value="#FF262626"/><Setter Property="Fill" TargetName="ExpandPath" Value="#FF595959"/></MultiTrigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style></ToggleButton.Style></ToggleButton><Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Grid.ColumnSpan="2" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True"><ContentPresenter x:Name="PART_Header" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/></Border><ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/></Grid><ControlTemplate.Triggers><Trigger Property="IsExpanded" Value="False"><Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/></Trigger><Trigger Property="HasItems" Value="False"><Setter Property="Visibility" TargetName="Expander" Value="Hidden"/></Trigger><Trigger Property="IsSelected" Value="True"><Setter Property="Background" TargetName="Bd" 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 Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/><Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/></MultiTrigger><Trigger Property="IsEnabled" Value="False"><Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style>
<!-- A style reproducing the old Winform style for the WPF TreeViewItem --><Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type TreeViewItem}"><ControlTemplate.Resources><mwc:LineVisibilityConverter x:Key="LineVisibilityConverter" /><mwc:FirstLeafLineMarginConverter x:Key="FirstLeafLineMarginConverter" /><mwc:TreeViewItemToLineTranslateTransformFactor x:Key="TreeViewItemToLineTranslateTransformFactor" /></ControlTemplate.Resources><Grid><Grid.ColumnDefinitions><ColumnDefinition MinWidth="19" Width="Auto"/><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition/></Grid.RowDefinitions><Line X1="0" X2="10" Fill="Transparent" RenderOptions.EdgeMode="Aliased" Stroke="#808080" VerticalAlignment="Center" HorizontalAlignment="Right" StrokeDashArray="1 1"><Line.RenderTransform><TranslateTransform Y="2" /></Line.RenderTransform></Line><Line Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource LineVisibilityConverter}, ConverterParameter=Line}" Stretch="Fill" Y1="0" Y2="1" RenderOptions.EdgeMode="Aliased" Grid.RowSpan="2" Stroke="#808080" StrokeDashArray="1 1" HorizontalAlignment="Center"><Line.RenderTransform><TranslateTransform Y="{Binding ActualHeight, ElementName=PART_Header, Converter={StaticResource TreeViewItemToLineTranslateTransformFactor}}" /></Line.RenderTransform></Line><Line Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource LineVisibilityConverter}, ConverterParameter=FirstLeafLine}" Y1="0" Y2="1" Stretch="Fill" RenderOptions.EdgeMode="Aliased" Stroke="#808080" Margin="{Binding ActualHeight, ElementName=PART_Header, Converter={StaticResource FirstLeafLineMarginConverter}}" StrokeDashArray="1 1" VerticalAlignment="Stretch" HorizontalAlignment="Center"></Line><ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"><ToggleButton.Style><Style TargetType="{x:Type ToggleButton}"><Setter Property="Focusable" Value="False"/><Setter Property="Width" Value="16"/><Setter Property="Height" Value="16"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type ToggleButton}"><Border Height="9" Width="9" BorderBrush="#808080" BorderThickness="1" CornerRadius="1" SnapsToDevicePixels="True"><Border.Background><LinearGradientBrush EndPoint="0,1" StartPoint="0,0"><GradientStop Color="#FCFCFC" Offset="0" /><GradientStop Color="#E3E3E3" Offset="1"/></LinearGradientBrush></Border.Background><Grid SnapsToDevicePixels="True"><Rectangle Height="1" Width="5" Stroke="#4B63A7" SnapsToDevicePixels="True" /><Rectangle x:Name="CrossHorizontalLine" Height="5" Width="1" Stroke="#4B63A7" SnapsToDevicePixels="True" /></Grid></Border><ControlTemplate.Triggers><Trigger Property="IsChecked" Value="True"><Setter Property="Visibility" TargetName="CrossHorizontalLine" Value="Collapsed" /></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style></ToggleButton.Style><ToggleButton.RenderTransform><TranslateTransform Y="1" /></ToggleButton.RenderTransform></ToggleButton><Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True"><ContentPresenter x:Name="PART_Header" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="True"/></Border><ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1" SnapsToDevicePixels="True" /></Grid><ControlTemplate.Triggers><Trigger Property="IsExpanded" Value="False"><Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/></Trigger><Trigger Property="HasItems" Value="False"><Setter Property="Visibility" TargetName="Expander" Value="Hidden"/></Trigger><Trigger Property="IsSelected" Value="True"><Setter Property="Background" TargetName="Bd" 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 Property="Background" TargetName="Bd" 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>
public class TreeViewItemToLineTranslateTransformFactor : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return Math.Ceiling((double)value / 2) + 3; } }
public class FirstLeafLineMarginConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return new Thickness(0, 1, 0, (double)value / 2 - 3); } }
public class LineVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var result = Visibility.Collapsed; var treeViewItem = value as TreeViewItem; if (treeViewItem == null) { throw new ArgumentException("value"); } if (((string)parameter) == "Line") { var owningPanel = treeViewItem.SafeFindAncestor<Panel>(); if (owningPanel != null) { var treeViewItemIndex = owningPanel.Children.IndexOf(treeViewItem); if (treeViewItemIndex < owningPanel.Children.Count - 1) { result = Visibility.Visible; } } } else if (((string)parameter) == "FirstLeafLine") { var owningPanel = treeViewItem.SafeFindAncestor<Panel>(); if (owningPanel != null) { var treeViewItemIndex = owningPanel.Children.IndexOf(treeViewItem); if (treeViewItemIndex == 0) { // try to find a TreeViewItem ancestor var parentTreeViewitem = owningPanel.SafeFindAncestor<TreeViewItem>(); if (parentTreeViewitem != null) { result = Visibility.Visible; } } } } return result; } }