WPF TreeView: как стилизовать выбранные элементы с закругленными углами, как в Explorer
Выбранный элемент в WPF TreeView имеет темно-синий фон с "острыми" углами. Сегодня это выглядит немного устаревшим:
![WPF selected TreeViewItem with or without focus]()
Я хотел бы изменить фон, чтобы он выглядел как в проводнике Windows 7 (с/без фокуса):
![Explorer TreeViewItem selected]()
![Explorer TreeViewItem selected without focus]()
То, что я пробовал до сих пор, не удаляет исходный темно-синий фон, а рисует закругленную рамку сверху, так что вы видите темно-синий цвет по краям и с левой стороны - уродливый.
![enter image description here]()
Интересно, что когда моя версия не имеет фокуса, она выглядит довольно хорошо:
![enter image description here]()
Я хотел бы воздержаться от пересмотра шаблона управления как здесь или здесь. Я хочу установить минимальные требуемые свойства, чтобы выбранный элемент выглядел как в Проводнике.
Альтернатива: Я также был бы рад, если бы сфокусированный выбранный элемент выглядел так, как будто мой сейчас делает, когда у него нет фокуса. При потере фокуса цвет должен меняться от синего до серого.
Вот мой код:
<TreeView
x:Name="TreeView"
ItemsSource="{Binding TopLevelNodes}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderBrush" Value="#FF7DA2CE" />
<Setter Property="Background" Value="#FFCCE2FC" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewmodels:ObjectBaseViewModel}" ItemsSource="{Binding Children}">
<Border Name="ItemBorder" CornerRadius="2" Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TreeViewItem}}"
BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=TreeViewItem}}" BorderThickness="1">
<StackPanel Orientation="Horizontal" Margin="2">
<Image Name="icon" Source="/ExplorerTreeView/Images/folder.png"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</Border>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Решение
С превосходными ответами Sheridan и Meleak мой TreeView теперь выглядит так в коде (в результате я очень доволен и довольно близок к стилю Explorer):
<TreeView
...
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- Style for the selected item -->
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<!-- Selected and has focus -->
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderBrush" Value="#7DA2CE"/>
</Trigger>
<!-- Mouse over -->
<Trigger Property="helpers:TreeView_IsMouseDirectlyOverItem.IsMouseDirectlyOverItem" Value="True">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFFAFBFD" Offset="0"/>
<GradientStop Color="#FFEBF3FD" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush" Value="#B8D6FB"/>
</Trigger>
<!-- Selected but does not have the focus -->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
<Condition Property="IsSelectionActive" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="BorderBrush" Value="#D9D9D9"/>
</MultiTrigger>
</Style.Triggers>
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="2"/>
</Style>
</Style.Resources>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type viewmodels:ObjectBaseViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Margin="2,1,5,2">
<Grid Margin="0,0,3,0">
<Image Name="icon" Source="/ExplorerTreeView/Images/folder.png"/>
</Grid>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<!-- Brushes for the selected item -->
<LinearGradientBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFDCEBFC" Offset="0"/>
<GradientStop Color="#FFC1DBFC" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="{x:Static SystemColors.ControlBrushKey}" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF8F8F8" Offset="0"/>
<GradientStop Color="#FFE5E5E5" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
</TreeView.Resources>
</TreeView>
Ответы
Ответ 1
Добавление к ответу @Sheridan
Это не на 100% точнее, но должно быть довольно близко (оно использует цвета от GridView
, которые довольно близки к Проводнику Windows)
![enter image description here]()
<TreeView ...>
<TreeView.Resources>
<LinearGradientBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFD9F4FF" Offset="0"/>
<GradientStop Color="#FF9BDDFB" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="{x:Static SystemColors.ControlBrushKey}" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFEEEDED" Offset="0"/>
<GradientStop Color="#FFDDDDDD" Offset="1"/>
</LinearGradientBrush>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="BorderThickness" Value="1.5"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderBrush" Value="#adc6e5"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
<Condition Property="IsSelectionActive" Value="False"/>
</MultiTrigger.Conditions>
<Setter Property="BorderBrush" Value="LightGray"/>
</MultiTrigger>
</Style.Triggers>
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="2"/>
</Style>
</Style.Resources>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Ответ 2
Добавьте это в свой TreeView.ContainerStyle
, чтобы удалить фон blue
по умолчанию.
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
</Style.Resources>
Вы можете заменить Black
на любой цвет, который вы хотите, чтобы текст вашего предмета и текст выбранного элемента были.
Чтобы иметь серый фон, если он не сфокусирован, вы можете настроить "не сфокусированный" Style
с серым backgorund и использовать EventTrigger
для событий TreeViewItem.GotFocus
и LostFocus
для переключения между Style
с.
ИЗМЕНИТЬ → >
Если вы хотите быть мигающим, вы можете использовать анимацию для изменения цвета фона, добавив триггеры к вашему ItemBorder Border
прямо в ваш HierarchicalDataTemplate
, например:
<Border.Triggers>
<EventTrigger RoutedEvent="Border.GotFocus">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="YourColour" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="Border.LostFocus">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="LightGray" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Border.Triggers>
Обратите внимание, что это будет работать, только если ColorAnimation
имеет цвет From
. Поскольку этот код стоит, среда выполнения будет искать SolidColorBrush
, установленную в свойстве Border.Background
, поэтому вы должны установить ее. Вы можете напрямую установить свойство ColorAnimation.From
.
Ответ 3
Стиль WindowsView TreeView (и ListView)
Первоначально я искал способ применения цветовой схемы Windows 10 к TreeViewItem, включая
- IsMouseOver только для текущего элемента
- Windows 10 цветов, которые WPF уже применяет к ListBox (не Windows Explorer)
Если кто-то из вас ищет именно это, не стесняйтесь принимать код ниже. Я использовал решение Helge Klein для проблемы IsMouseOver и применил цвета Windows 10 к XAML. Поэтому я предлагаю это как дополнение к принятому ответу.
Также см. ниже слово на ListView
и ComboBox
.
Скриншот
![example]()
App.xaml
<Style TargetType="{x:Type TreeView}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#CBE8F6" />
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#F6F6F6" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" />
</Style.Resources>
</Style>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderBrush" Value="#26A0DA" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="local:TreeViewItemHelper.IsMouseDirectlyOverItem" Value="True" />
<Condition Property="IsSelected" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#E5F3FB" />
<Setter Property="BorderBrush" Value="#70C0E7" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="IsSelectionActive" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="BorderBrush" Value="#DADADA" />
</MultiTrigger>
</Style.Triggers>
</Style>
TreeViewItemHelper (Источник как опубликовано Helge Klein, незначительные изменения/упрощение)
public static class TreeViewItemHelper
{
private static TreeViewItem CurrentItem;
private static readonly RoutedEvent UpdateOverItemEvent = EventManager.RegisterRoutedEvent("UpdateOverItem", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TreeViewItemHelper));
private static readonly DependencyPropertyKey IsMouseDirectlyOverItemKey = DependencyProperty.RegisterAttachedReadOnly("IsMouseDirectlyOverItem", typeof(bool), typeof(TreeViewItemHelper), new FrameworkPropertyMetadata(null, new CoerceValueCallback(CalculateIsMouseDirectlyOverItem)));
public static readonly DependencyProperty IsMouseDirectlyOverItemProperty = IsMouseDirectlyOverItemKey.DependencyProperty;
static TreeViewItemHelper()
{
EventManager.RegisterClassHandler(typeof(TreeViewItem), UIElement.MouseEnterEvent, new MouseEventHandler(OnMouseTransition), true);
EventManager.RegisterClassHandler(typeof(TreeViewItem), UIElement.MouseLeaveEvent, new MouseEventHandler(OnMouseTransition), true);
EventManager.RegisterClassHandler(typeof(TreeViewItem), UpdateOverItemEvent, new RoutedEventHandler(OnUpdateOverItem));
}
public static bool GetIsMouseDirectlyOverItem(DependencyObject obj)
{
return (bool)obj.GetValue(IsMouseDirectlyOverItemProperty);
}
private static object CalculateIsMouseDirectlyOverItem(DependencyObject item, object value)
{
return item == CurrentItem;
}
private static void OnUpdateOverItem(object sender, RoutedEventArgs e)
{
CurrentItem = sender as TreeViewItem;
CurrentItem.InvalidateProperty(IsMouseDirectlyOverItemProperty);
e.Handled = true;
}
private static void OnMouseTransition(object sender, MouseEventArgs e)
{
lock (IsMouseDirectlyOverItemProperty)
{
if (CurrentItem != null)
{
DependencyObject oldItem = CurrentItem;
CurrentItem = null;
oldItem.InvalidateProperty(IsMouseDirectlyOverItemProperty);
}
Mouse.DirectlyOver?.RaiseEvent(new RoutedEventArgs(UpdateOverItemEvent));
}
}
}
ListBox/ListView и ComboBox: В Windows 7 (и 8?) это приведет к тому, что дизайн из TreeView в ListBox/ListView и ComboBox будет отличаться. Поэтому, если вы хотите применить эту цветовую схему к этим типам управления, также используйте это:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="Border" BorderThickness="1" Background="Transparent">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#E5F3FB" />
<Setter TargetName="Border" Property="BorderBrush" Value="#70C0E7" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#CBE8F6" />
<Setter TargetName="Border" Property="BorderBrush" Value="#26A0DA" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True" />
<Condition Property="Selector.IsSelectionActive" Value="False" />
</MultiTrigger.Conditions>
<Setter TargetName="Border" Property="Background" Value="#F6F6F6" />
<Setter TargetName="Border" Property="BorderBrush" Value="#DADADA" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}" />
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
<Border Name="Border" BorderThickness="1" Padding="1" Background="Transparent">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#E5F3FB" />
<Setter TargetName="Border" Property="BorderBrush" Value="#70C0E7" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#CBE8F6" />
<Setter TargetName="Border" Property="BorderBrush" Value="#26A0DA" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>