WPF par défaut TreeView est très bon, je veux toujours des lignes reliant chacun de ses éléments enfants, comme Windows Forms TreeView. J'ai cherché sur Internet et ai quelques exemples, mais il n'a pas assez bien conçu.
Comment puis-je y parvenir avec WPF?
Laissez-moi répondre à ma propre question.
Code
Tout ce que vous devez faire est un fichier XAML et un code derrière:
Vous devez d’abord dessiner Bouton bascule: du bouton Triangle au bouton Plus-Moins: tracez un rectangle avec une bordure sombre, puis tracez deux lignes, une ligne verticale et une ligne horizontale. Lorsque TreeViewItem est développé, la ligne verticale masque:
<!-- Toggle Button -->
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="15" Height="13" SnapsToDevicePixels="True">
<!-- Rectangle 9x9 pixels -->
<Rectangle Width="9" Height="9" Stroke="#919191" SnapsToDevicePixels="true">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,2" StartPoint="0.5,0">
<GradientStop Color="White" Offset="0"/>
<GradientStop Color="Silver" Offset="0.5"/>
<GradientStop Color="LightGray" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- Vertical line inside rectangle -->
<Rectangle x:Name="ExpandPath" Width="1" Height="5" Stroke="Black" SnapsToDevicePixels="true"/>
<!-- Horizontal line inside rectangle -->
<Rectangle Width="5" Height="1" Stroke="Black" SnapsToDevicePixels="true"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" TargetName="ExpandPath" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Dans le code ci-dessus, vous pouvez voir un déclencheur, il fera en sorte que la ligne verticale à l'intérieur du bouton bascule se cache si l'élément est développé ou indique si ses enfants sont réduits.
Ensuite, vous devez dessiner des lignes de connexion verticales et horizontales entre les nœuds: Vous devez repenser le contrôle TreeViewItem. Ajoutez ces lignes de connexion:
<!-- Horizontal line -->
<Rectangle x:Name="HorLn" Margin="9,1,0,0" Height="1" Stroke="#DCDCDC" SnapsToDevicePixels="True"/>
<!-- Vertical line -->
<Rectangle x:Name="VerLn" Width="1" Stroke="#DCDCDC" Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true" Fill="White"/>
à votre modèle TreeViewItem comme ceci:
<!-- TreeViewItem -->
<Style x:Key="{x:Type TreeViewItem}" 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>
<!-- Connecting Lines -->
<!-- Horizontal line -->
<Rectangle x:Name="HorLn" Margin="9,1,0,0" Height="1" Stroke="#DCDCDC" SnapsToDevicePixels="True"/>
<!-- Vertical line -->
<Rectangle x:Name="VerLn" Width="1" Stroke="#DCDCDC" Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true" Fill="White"/>
<!-- Insert Toggle Button -->
<ToggleButton Margin="-1,0,0,0" 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}" SnapsToDevicePixels="True">
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" MinWidth="20"/>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Ensuite, vous devez placer la classe TreeViewLineConverter dans votre espace de noms. Cette classe modifiera les lignes de connexion si l'élément est le dernier de la liste:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace TreeViewEx
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
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)
{
return false;
}
}
}
Insérez votre espace de noms dans votre XAML, à savoir:
<Window x:Class="TreeViewEx.MainWindow"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewEx"/>
Ajoutez cette ligne à Window.Resources:
<local:TreeViewLineConverter x:Key="LineConverter"/>
Ajouter un déclencheur au modèle TreeViewItem, ce déclencheur modifie les lignes de connexion si l'élément est le dernier de la liste:
<!-- 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="9"/>
<Setter TargetName="VerLn" Property="VerticalAlignment" Value="Top"/>
</DataTrigger>
Le TreeView aura maintenant le style WinForms. Vous pouvez ajouter davantage de déclencheurs pour contrôler le comportement de TreeView si vous le souhaitez. Le déclencheur complet peut être trouvé sur le fichier joint.
ToDo
Dans WinForms TreeView, les lignes de connexion sont des lignes en pointillés. Pour mettre ces lignes en pointillé, changez:
<!-- Connecting Lines -->
<Rectangle x:Name="HorLn" Margin="9,1,0,0" Height="1" Stroke="#DCDCDC" SnapsToDevicePixels="True"/>
<Rectangle x:Name="VerLn" Width="1" Stroke="#DCDCDC" Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true" Fill="White"/>
À:
<!-- Connecting Lines -->
<Rectangle x:Name="HorLn" Margin="9,1,0,0" Height="1" Stroke="Blue" StrokeDashCap="Square" StrokeDashArray="0,2" StrokeDashOffset="1" SnapsToDevicePixels="True"/>
<Rectangle x:Name="VerLn" Width="1" Stroke="Blue" StrokeDashCap="Square" StrokeDashArray="0,2" Margin="0,0,1,0" Grid.RowSpan="2" SnapsToDevicePixels="true" Fill="White"/>
Mais ce n'est pas joli, comme vous voyez. Comme je suis un débutant dans WPF, je ne sais pas comment coiffer parfaitement cette ligne.
Problème!
Il existe un problème de ligne verticale lorsque vous ajoutez un TreeViewItem à TreeView:
Vous pouvez me suggérer de modifier la taille de la ligne verticale, mais si vous modifiez également la taille de la police, cela ne fonctionnera pas.
Code source
Vous pouvez télécharger mon code source ici:
https://tuyentk.googlecode.com/files/TreeViewEx.Zip (4.4 KB)
Référence
C’est le code que j’ai référé avant d’écrire le mien: Social MSDN: Afficher les nœuds TreeView connectés en pointillés
Bel exemple. Le problème dans votre solution avec les lignes en pointillé est que vous utilisez un rectangle en tant que ligne avec une largeur ou une hauteur définie sur 1. Si vous le faites, les bordures gauche et droite se trouvent sur le même pixel. Cela n’est pas grave si ces lignes sont continues, mais si elles sont en pointillé, elles ne doivent pas obligatoirement avoir des points aux mêmes endroits (c’est-à-dire que la bordure gauche commence par les points situés au pixel 0 et la bordure droite au pixel 1). pas beau.
La solution consiste à créer des lignes en pointillés avec quelque chose de différent des rectangles. Vous pouvez utiliser par exemple Border
. J'ai pris la solution de ici .
Changer les lignes de connexion en:
<!-- Connecting Lines -->
<Border x:Name="HorLn" Margin="9,0,0,0" HorizontalAlignment="Stretch" Height="1" BorderThickness="0,0,0,1">
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="2,0" SpreadMethod="Repeat" MappingMode="Absolute">
<GradientStop Color="Transparent" Offset="0" />
<GradientStop Color="Transparent" Offset="0.499" />
<GradientStop Color="#999" Offset="0.5" />
</LinearGradientBrush>
</Border.BorderBrush>
</Border>
<Border x:Name="VerLn" Margin="0,0,1,0" Grid.RowSpan="2" VerticalAlignment="Stretch" Width="1" BorderThickness="0,0,1,0">
<Border.BorderBrush>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,2" SpreadMethod="Repeat" MappingMode="Absolute">
<GradientStop Color="Transparent" Offset="0" />
<GradientStop Color="Transparent" Offset="0.499" />
<GradientStop Color="#999" Offset="0.5" />
</LinearGradientBrush>
</Border.BorderBrush>
</Border>
Réponse retravaillée un peu. La taille des lignes verticales est en corrélation dynamique avec la hauteur de l’élément et les rectangles sont remplacés par des bordures
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="15" Height="13" SnapsToDevicePixels="True">
<Rectangle Width="9" Height="9" Stroke="#919191" SnapsToDevicePixels="true" Fill="White"/>
<Rectangle x:Name="ExpandPath" Width="1" Height="5" Stroke="Black" SnapsToDevicePixels="true"/>
<Rectangle Width="5" Height="1" Stroke="Black" SnapsToDevicePixels="true"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" TargetName="ExpandPath" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeViewStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="0,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid Name="ItemRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Name="Lines" Grid.Column="0" Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Row="0" Grid.Column="1" BorderThickness="1 0 0 1" SnapsToDevicePixels="True" BorderBrush="{TemplateBinding BorderBrush}"/>
<Border Grid.Row="1" Grid.Column="1" BorderThickness="1 0 0 0" SnapsToDevicePixels="True" BorderBrush="{TemplateBinding BorderBrush}" Name="LineToNextItem"
Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}},
Converter={StaticResource LineConverter}}"/>
</Grid>
<ToggleButton x:Name="Expander" Grid.Column="0" Grid.Row="0"
Style="{StaticResource ExpandCollapseToggleStyle}"
IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<Border Name="Bd" Grid.Column="1" Grid.Row="0"
HorizontalAlignment="Left"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" MinWidth="20"/>
</Border>
<Grid Grid.Column="0" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Grid.Column="1" BorderThickness="1 0 0 0" SnapsToDevicePixels="True" BorderBrush="{TemplateBinding BorderBrush}"
Visibility="{Binding ElementName=LineToNextItem, Path=Visibility}"/>
</Grid>
<ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.Row="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
</Trigger>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
</Trigger>
<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>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<TreeView Name="TreeView" Margin="24">
<Border CornerRadius="20" BorderBrush="Red" BorderThickness="1 0 0 0">
<TreeViewItem Header="aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" IsExpanded="True">
<Border CornerRadius="20" BorderBrush="Red" BorderThickness="1 0 0 0">
<TreeViewItem Header="aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" IsExpanded="True">
<Border CornerRadius="20" BorderBrush="Red" BorderThickness="1 0 0 0">
<TreeViewItem Header="aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" IsExpanded="True">
<Border CornerRadius="20" BorderBrush="Red" BorderThickness="1 0 0 0">
<TreeViewItem Header="aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" IsExpanded="True">
</TreeViewItem>
</Border>
</TreeViewItem>
</Border>
<Border CornerRadius="20" BorderBrush="Red" BorderThickness="1 0 0 0">
<TreeViewItem Header="aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" IsExpanded="True">
</TreeViewItem>
</Border>
</TreeViewItem>
</Border>
<Border CornerRadius="20" BorderBrush="Red" BorderThickness="1 0 0 0">
<TreeViewItem Header="aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" IsExpanded="True">
</TreeViewItem>
</Border>
</TreeViewItem>
</Border>
</TreeView>