Как вы, наверное, уже поняли, это не совсем тривиально. Проблема в том, что в отличие от ListBox, DataGrid и др. TreeView не имеет строковой структуры. Вместо этого он использует иерархическую структуру, которая выглядит примерно так:
Есть сетка, которая оборачивает весь элемент управления, но для любой данной "строки" TreeViewItem не расширяется полностью влево, где вы хотите разместить свои кнопки.
Таким образом, чтобы реализовать это, вам придется повторно шаблон TreeViewItem. Шаблон по умолчанию использует сетку для размещения своего содержимого, с содержимым строки в строке 0 и дочерними (если есть) в строке 1. Важно, что дочерние элементы также помещаются в столбец 1, как TreeView выполняет отступ. Таким образом, первым шагом является размещение дополнительных 3 столбцов слева, плюс еще 3 справа, чтобы разместить шесть кнопок, которые вы хотите добавить в каждую строку. Затем вы захотите изменить Grid.Column и Grid.ColumnSpan для ItemsPresenter на каждом уровне, чтобы он занимал ширину всего элемента управления.
Теперь проблема, конечно, заключается в том, что вы ' Вы потеряли отступ, поэтому вам нужно добавить еще один столбец в сетку, чтобы добавить его обратно. Чтобы правильно выполнить отступ для любого заданного уровня, вам нужно знать отступ для его родителя, который ранее был присущ сам макету. но теперь был раздет Есть несколько решений для этого, но самое простое IMO - использовать присоединенное свойство, которое я назову TreeViewItemHelper.Indent
. Для каждого ItemsPresenter в TreeView вы хотите рассчитать отступ, который будет использоваться для всех дочерних элементов ниже текущего уровня:
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="10" Grid.Column="0" Grid.Row="1"
local:TreeViewItemHelper.Indent="{Binding Path=(local:TreeViewItemHelper.Indent), Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}, Converter={StaticResource IndentConverter}}" />
Обратите внимание, что я на самом деле здесь не использую значение для чего-либо, просто вычисляю что это должно быть, привязав каждый TreeViewItemHelper.Indent
к одному на предыдущем уровне и запустив его через конвертер, который просто добавляет фиксированную величину (т. е. ширину кнопок ToggleButtons, используемых для расширения узлов дерева):
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new GridLength(((GridLength)value).Value + IndentSize);
}
Наконец, вам нужно где-нибудь применить отступ. Мы уже создали столбец в нашей сетке для него, поэтому мы можем просто добавить фиктивный элемент управления к этому столбцу и привязать его ширину к уровню отступа любого элемента ItemsPresenter, внутри которого он находится:
<Rectangle Grid.Column="3" Width="{Binding Path=(local:TreeViewItemHelper.Indent).Value, Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}}" Fill="Transparent"/>
Добавить все вместе, хорошо встряхните, и вот как выглядит XAML для повторного шаблона TreeViewItem и присвоения ему стиля:
<local:IndentConverter x:Key="IndentConverter" />
<ControlTemplate x:Key="TreeViewItemControlTemplate1" TargetType="{x:Type TreeViewItem}">
<Grid x:Name="tvGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<!--<ColumnDefinition Width="0"/>-->
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- Left buttons -->
<Button Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
<Button Grid.Column="1" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
<Button Grid.Column="2" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
<Rectangle Grid.Column="3" Width="{Binding Path=(local:TreeViewItemHelper.Indent).Value, Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}}" Fill="Transparent"/>
<ToggleButton x:Name="Expander" Grid.Column="4" 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="White" Stroke="#FF818181">
<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="#FF27C7F7"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="#FFCCEEFB"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Stroke" TargetName="ExpandPath" Value="#FF1CC4F7"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="#FF82DFFB"/>
</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="5" 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>
<!-- Right buttons -->
<Button Grid.Column="7" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
<Button Grid.Column="8" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
<Button Grid.Column="9" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="10" Grid.Column="0" Grid.Row="1"
local:TreeViewItemHelper.Indent="{Binding Path=(local:TreeViewItemHelper.Indent), Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}, Converter={StaticResource IndentConverter}}" />
</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>
<Style TargetType="TreeViewItem">
<Setter Property="Template" Value="{DynamicResource TreeViewItemControlTemplate1}" />
<Setter Property="IsExpanded" Value="True" />
</Style>
Вот класс для присоединенного свойства, которое он использует:
public static class TreeViewItemHelper
{
public static GridLength GetIndent(DependencyObject obj)
{
return (GridLength)obj.GetValue(IndentProperty);
}
public static void SetIndent(DependencyObject obj, GridLength value)
{
obj.SetValue(IndentProperty, value);
}
// Using a DependencyProperty as the backing store for Indent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IndentProperty =
DependencyProperty.RegisterAttached("Indent", typeof(GridLength), typeof(TreeViewItemHelper), new PropertyMetadata(new GridLength(0)));
}
И, наконец, преобразователь отступов:
public class IndentConverter : IValueConverter
{
private const int IndentSize = 16; // hard-coded into the XAML template
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new GridLength(((GridLength)value).Value + IndentSize);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
И вот результат: