开发者

WPF TreeView: How to style selected items with rounded corners like in Explorer

The selected item in a WPF TreeView has a dark blue background with "sharp" corners. That looks a bit dated today:

WPF TreeView: How to style selected items with rounded corners like in Explorer

I would like to change the background to look like in Explorer of Windows 7 (with/without focus):

WPF TreeView: How to style selected items with rounded corners like in Explorer

WPF TreeView: How to style selected items with rounded corners like in Explorer

What I tried so far does not remove the original dark blue background but paints a rounded border on top of it so that you see the dark blue color at the edges and at the left side - ugly.

WPF TreeView: How to style selected items with rounded corners like in Explorer

Interestingly, when my version does not have the focus, it looks pretty OK:

WPF TreeView: How to style selected items with rounded corners like in Explorer

I would like to refrain from redefining the control template as shown here or here. I want to set the minimum required properties to make the selected item look like in Explorer.

Alternative: I would also be happy to have the focused selected item look like mine does now when it does not have the focus. When losing the focus, the c开发者_如何学运维olor should change from blue to grey.

Here is my code:

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

Solution

With the excellent answers of Sheridan and Meleak my TreeView now looks like this in code (a result I am very happy with and which is pretty near Explorer's style):

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


Adding to @Sheridan's answer
This isn't a 100% accurate but should get you pretty close (it's using the colors from GridView which is pretty close to Windows Explorer)

WPF TreeView: How to style selected items with rounded corners like in 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>


Add this into your TreeView.ContainerStyle to remove the default blue background.

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

You can replace the Black with whatever colour you want your item text and selected item text to be.

To have a grey background when not focused, you could set up a 'non focused' Style with a grey backgorund and use EventTriggers on the TreeViewItem.GotFocus and LostFocus events to switch between the Styles.

EDIT>>>

If you want to be flash, you can use animations to change between the background colours by adding triggers to your ItemBorder Border directly in your HierarchicalDataTemplate like so:

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

Note that this will only work if the ColorAnimation has a From colour. As this code stands, the runtime will look for a SolidColorBrush set on the Border.Background property, so you must set one. You could set the ColorAnimation.From property directly instead.


Windows 10 TreeView (and ListView) Style

I was originally looking for a way to apply the Windows 10 color scheme to a TreeViewItem, including

  • IsMouseOver on current item only
  • Windows 10 colors which WPF already applies them to ListBox (not Windows Explorer)

If any of you are looking for exactly this, please feel free to take the code below. I used Helge Klein's solution for the IsMouseOver issue and applied the Windows 10 colors to the XAML. Therefore I propose this as an addition to the accepted answer.

Also, see below for a word on ListView and ComboBox as well.


Screenshot

WPF TreeView: How to style selected items with rounded corners like in Explorer

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 (by Helge Klein, minor changes / simplification)

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 and ComboBox: In Windows 7 (and 8?), this will cause the design from TreeView to ListBox/ListView and ComboBox to differ. Therefore, if you want to apply this color scheme to these control types as well, too, use this:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜