WPF TreeView: как оформить выбранные элементы закругленными углами, как в Проводнике

Выбранный элемент в WPF TreeView имеет темно-синий фон с "острыми" углами. Это выглядит немного устаревшим сегодня:

WPF выбрал TreeViewItem с или без фокуса

Я хотел бы изменить фон, чтобы он выглядел как в Проводнике Windows 7 (с / без фокуса):

Проводник TreeViewItem выбранПроводник TreeViewItem выбран без фокуса

То, что я пробовал до сих пор, не удаляет исходный темно-синий фон, но закрашивает округлую границу сверху, так что вы видите темно-синий цвет по краям, а с левой стороны - безобразно.

Интересно, что когда моя версия не имеет фокуса, она выглядит довольно хорошо:

Я хотел бы воздержаться от переопределения шаблона элемента управления, как показано здесь или здесь. Я хочу установить минимально необходимые свойства, чтобы выбранный элемент выглядел как в Проводнике.

Альтернатива: я также был бы рад, если бы выделенный элемент был похож на мой, теперь, когда на нем нет фокуса. При потере фокуса цвет должен измениться с синего на серый.

Вот мой код:

<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>

Решение

Благодаря превосходным ответам Шеридана и Мелика мой 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>

3 ответа

Решение

Добавление к ответу @Sheridan
Это не на 100% точно, но должно быть достаточно близко (это использует цвета из GridView что довольно близко к Windows Explorer)

<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>

Добавьте это в свой 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 события для переключения между Styles.

EDIT >>>

Если вы хотите быть flash, вы можете использовать анимацию для переключения между цветами фона, добавив триггеры в ваш 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 собственность прямо вместо

Стиль Windows 10 TreeView (и ListView)

Первоначально я искал способ применения цветовой схемы Windows 10 к TreeViewItem, в том числе

  • IsMouseOver только для текущего элемента
  • Windows 10 цветов, которые WPF уже применяет их к ListBox (не Windows Explorer)

Если кто-то из вас ищет именно это, пожалуйста, не стесняйтесь взять код ниже. Я использовал решение Хельге Кляйна для решения проблемы IsMouseOver и применил цвета Windows 10 к XAML. Поэтому я предлагаю это как дополнение к принятому ответу.

Также см. Ниже слово для ListView а также ComboBox также.


Скриншот

пример

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 ( Источник, опубликованный Хельге Кляйн, незначительные изменения / упрощение)

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>
Другие вопросы по тегам