Monday, October 12, 2009

[2009.10.12] WPF: Change the color of the selected item in a ListBox control

A question that may come up at some point in the life of a WPF developer is: "How to change the color of the selected item in a ListBox control?". Going with just the word "selected" you would imagine that there is some property with that name that lives in ListBox. Close, but no dice.

There is, however, an IsSelected dependency property registered to the ListBoxItem control. Recall that ListBox implicitly wraps each of its items in a ListBoxItem, which is a ContentControl.

There are three ways that I know of that you can accomplish what you want - the first two requires knowledge of the template that makes up the ListBoxItem, whereas the last one, not so much. Let's look at the first two. Since the template is necessary, let's start by taking a look at the template for a ListBoxItem in Listing 1. I have removed the parts that are not relevant to the problem at hand so you are just seeing a slimmed down version of the real thing.
Note: I am using a tool called Show Me The Template! Feel free to use whatever works.

  1: <ControlTemplate TargetType="ListBoxItem" 
  2:                      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  3:                      xmlns:s="clr-namespace:System;assembly=mscorlib" 
  4:                      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  5:     <Border BorderThickness="{TemplateBinding Border.BorderThickness}" 
  6:                 Padding="{TemplateBinding Control.Padding}" 
  7:                 BorderBrush="{TemplateBinding Border.BorderBrush}" 
  8:                 Background="{TemplateBinding Panel.Background}" 
  9:                 Name="Bd" SnapsToDevicePixels="True">
 10:         <ContentPresenter Content="{TemplateBinding ContentControl.Content}" 
 11:                               ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" 
 12:                               ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" 
 13:                               HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" 
 14:                               VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" 
 15:                               SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
 16:     </Border>
 17:     <ControlTemplate.Triggers>
 18:         <Trigger Property="Selector.IsSelected">
 19:             <Setter TargetName="Bd" 
 20:                     Property="Panel.Background" 
 21:                     Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
 22:         </Trigger>
 23:     </ControlTemplate.Triggers>
 24: </ControlTemplate>

Listing 1. Slimmed down control template for a ListBoxItem

We are interested in Lines 18-21.

What we have in Line 18 is that when the IsSelected dependency property is triggered, it sets the Value of the Background property of the Border (with Name="Bd") to some predefined system level resource called HighLightBrushKey. Now we know that this refers to the ugly blue color we see when we select something from a ListBox.

At this stage you have a handle as to the structure of the template, and what property you need to modify to change the color. Now let's look at the ways in which we can go about accomplishing our little task.

[1] Override the HighLightBrushKey resource

This is by far the most painless method as it can be done in the Resources section of your Window or Page. Listing 2 shows how this is done using a LinearGradientBrush and the result is in Fig. 1. Pay special attention to the highlighted portion of the code in Line 7.


  1: <Window x:Class="WpfUnleashed01.Window1"
  2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4:         xmlns:gundam="clr-namespace:WpfUnleashed01" 
  5:         Title="Changing ListBox color selection" Height="100" Width="300">
  6:     <Window.Resources>
  7:         <LinearGradientBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
  8:                              StartPoint="0,0" EndPoint="0,1">
  9:             <GradientStop Offset="0" Color="LightPink" />
 10:             <GradientStop Offset="0.3" Color="Red" />
 11:             <GradientStop Offset="0.7" Color="Red" />
 12:             <GradientStop Offset="0.9" Color="LightPink" />
 13:         </LinearGradientBrush>
 14:     </Window.Resources>
 15:     <StackPanel>
 16:         <ListBox>
 17:             <TextBlock Text="Using a LinearGradientBrush" />
 18:             <TextBlock Text="to change the color of" />
 19:             <TextBlock Text="the ListBox selected item" />
 20:         </ListBox>
 21:     </StackPanel>
 22: </Window>

Listing 2. Overriding a predefined resource


[2009.10.12].01.ListBox.01
Fig. 1. Using the override method

Basically that is all there is to it - a super quick, easy way to change the selection color!

[2] Modifying the ControlTemplate of ListBoxItem

This method goes a bit deeper as it actually tweaks the default template that is used to render the ListBoxItem. The code is pretty simple to follow as shown in Listing 3. The result is shown in Fig. 3.

  1: <Window x:Class="WpfUnleashed01.Window1"
  2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4:         xmlns:gundam="clr-namespace:WpfUnleashed01" 
  5:         Title="Template Editing" Height="100" Width="300">
  6:     <Window.Resources>
  7:         <!--style will automatically apply to ListBoxItems-->
  8:         <Style TargetType="{x:Type ListBoxItem}">
  9:             <!--modify the Template property-->
 10:             <Setter Property="Template">
 11:                 <Setter.Value>
 12:                     <ControlTemplate TargetType="{x:Type ListBoxItem}">
 13:                         <Border Name="Bd" Padding="2" SnapsToDevicePixels="True">
 14:                             <ContentPresenter />
 15:                         </Border> 
 16:                         <ControlTemplate.Triggers>
 17:                             <Trigger Property="IsSelected" Value="True">
 18:                                 <Setter TargetName="Bd" Property="Background" Value="Yellow" />
 19:                             </Trigger> 
 20:                         </ControlTemplate.Triggers>
 21:                     </ControlTemplate>
 22:                 </Setter.Value>
 23:             </Setter>
 24:         </Style>
 25:     </Window.Resources>
 26: 
 27:     <StackPanel>
 28:         <ListBox>
 29:             <TextBlock Text="Using a LinearGradientBrush" />
 30:             <TextBlock Text="to change the color of" />
 31:             <TextBlock Text="the ListBox selected item" />
 32:         </ListBox>
 33:     </StackPanel>
 34: </Window>

Listing 3. Just changing the Value property in Line 18 produces the desired results

[2009.10.12].02.ListBox.02
Fig. 2. Editing the ControlTemplate of the ListBoxItem

While this method was a bit more involved, it nonetheless did what you wanted to do, plus since you were editing the structure of the control's template, you also have the freedome to do a lot more.

[3] Using Expression Blend

The last method I will look at is using Expression Blend to make the necessary changes to the template to get the desired result. This may appeal to those who live in Blend most of their time and aren't too keen on coding in XAML.

After starting your new Blend project, select the ListBox control from the Asset Panel and put it where ever you want in your project. This is shown in Fig. 3. Once you are done making the tweaks you can delete the item. In addition, I have added two TextBlocks to the ListBox for testing purposes.

[2009.10.12].03.ListBox.in.blend.01
Fig. 3. Getting a ListBox from the Asset panel

With our ListBox in place, the next step is to edit the template. However, ListBox has several templates. If you right click on the ListBox either in the Objects and Timeline panel or on the object in the scene, you will see two menus giving you access to the templates you can modify as in Fig. 4.

[2009.10.12].04.ListBox.in.blend.02
Fig. 4. ListBox template options

The first choice is Edit Template, but we don't want this as this is the template for the ListBox. Recall from above that the property we are seeking lives in the ListBoxItem control, which ListBox uses to wrap items added to its collection. The second submenu, Edit Additional Templates has what we are looking for, namely the Edit Generated Item Container (ItemContainerStyle). Refer to Fig. 5.

[2009.10.12].05.ListBox.in.blend.03
Fig. 5. Editing the ListBoxItem's ControlTemplate via a Style

Actually, we are doing Step 2 in Blend! We are using a style to modify the underlying template to suit our needs. Either way, choose Edit a Copy..., name it and choose whether to place it in the Resource collection of the Window, Application or added to a ResourceDictionary. Feel free to choose any option, I will leave mine with the default settings as in Fig. 6.

[2009.10.12].06.ListBox.in.blend.04
Fig. 6. Adding a resource to your project in Blend

Now Blend switches to template editing mode as in Fig. 7.

[2009.10.12].07.ListBox.in.blend.05
Fig. 7. Template mode

What we are interested in is the Trigger menu and the IsSelected = true property setting. If you click on that (the red box in Fig. 7), you will notice two things:

  • The main work area has a red border with the heading:
    IsSelected = True trigger recording is on
  • The IsSelected = true now had a red "recording light" next to itself which basically says that whatever changes we make to this property will wipe out its old value and "record" our new choice (you still have to Save your project of course!)

Fig. 8 illustrates this.

[2009.10.12].08.ListBox.in.blend.06
Fig. 8. Changing the property trigger

The Properties when active heading is our focus, namely the second option:

Bd.Background = {x:Static SystemColors.HighlighBrushKey}. If you click this option while recording is on, the Properties tab shows the brush that is used to set the Background property of an element called Bd (it's actually a Border). Refer to Fig. 9 to see what I am talking about.

[2009.10.12].09.ListBox.in.blend.07
Fig. 9. Default brush used

Now you can simply turn off that brush, as in Fig. 10 and select your own.

[2009.10.12].10.ListBox.in.blend.08
Fig. 10. Turn off default brush

You are free to now add whatever you want. For example, I will add a simple RadialGradientBrush as my new Background property. See Fig. 11.

[2009.10.12].11.ListBox.in.blend.09
Fig. 11. Changing the default brush to a radial gradient

At this point you can click the "record light" button either in the main work area or under the Triggers tab to turn off recording. In addition, you can jump out of Template Editing Mode as shown in Fig. 12.

[2009.10.12].12.ListBox.in.blend.10
Fig. 12. Back to normal mode

Build and run the application to see the results in Fig. 13.

[2009.10.12].13.ListBox.in.blend.11
Fig. 13. Running to application - new selected color!

Now if you add another ListBox to your window and you want to add that style for the ListBoxItem, right click on the ListBox and follow the highlights indicated in Fig. 14.

[2009.10.12].14.ListBox.in.blend.12
Fig. 14. Adding the style to another ListBox

Have fun, happy coding (or not).

No comments: