views:

3643

answers:

2

How would one go about styling a listbox such that the selection has a different text color than the default view? I've looked at this in several ways and because the ContentPresenter lacks a Foreground property.

The default control template of the listbox provides several rectangles which one can use to adjust the highlight color. For example, with the default style a rectangle called BGColor3 has its opacity adjusted to get a highlight effect.

Here is the bulk of my control template:

<Grid>
    <Rectangle x:Name="BGColor2" Fill="{StaticResource HoverBrush}" Stroke="Black" StrokeThickness="1" Opacity="0"/>
    <Rectangle x:Name="BGColor3" Fill="{StaticResource ListboxHighlightBrush}" StrokeThickness="0" Opacity="0"/>
    <Rectangle x:Name="BGColor" Stroke="Black" StrokeThickness="0" Opacity="0.2" Fill="{TemplateBinding Background}"/>
    <Border Height="20">
     <ContentPresenter HorizontalAlignment="Left" Margin="{TemplateBinding Padding}" x:Name="contentPresenter" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
    </Border>
    <Rectangle Fill="Blue" Opacity="0.4" x:Name="FocusVisual" Stroke="#BF313131" Margin="1" StrokeThickness="1" StrokeDashArray="1 2" StrokeDashCap="Square" Visibility="Collapsed" />
    <Rectangle x:Name="BorderRect" Stroke="Black" StrokeThickness="1" Opacity="0.3" />
</Grid>

In the selection Visual State, here is the gist:

<VisualStateGroup x:Name="SelectionStates">
    <VisualState x:Name="Unselected"/>
    <VisualState x:Name="Selected">
     <Storyboard>
      <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="BGColor2" Storyboard.TargetProperty="(UIElement.Opacity)">
       <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.8"/>
      </DoubleAnimationUsingKeyFrames>
     </Storyboard>
    </VisualState>
</VisualStateGroup>

What is evident is that BGColor2 the rectangle is being modified (opacity) so that the selected item has a background. Fair enough. Within this portion of the Storyboard, is there anyway to access either the ContentPresenter or something else similar and toggling the text foreground color?

Footnote: wouldn't it be much cleaner to simply have different templates versus visual state transitions?

---- added after first answer given ----

Using a TextBlock almost does the trick but what's interesting is that the transition for SelectedUnfocused does not appear to be enforced which is to say that implementing your solution works perfectly until a person mouses over a previously selected item then the text once again goes to black.

Here is my template:

<!-- ItemStyle brushes -->
<SolidColorBrush x:Key="BaseColorBrush" Color="White"/>
<SolidColorBrush x:Key="BaseColorBrushFaint" Color="#265B0000"/>
<SolidColorBrush x:Key="ForegroundColorBrush" Color="#FFFFFFFF"/>
<SolidColorBrush x:Key="HoverBrush2" Color="#FF808000"/>
<LinearGradientBrush x:Key="HoverBrush"  EndPoint="0.5,1" StartPoint="0.5,0">
 <GradientStop Color="#FF406DC7" Offset="1"/>
 <GradientStop Color="#FF002C83"/>
</LinearGradientBrush>

<LinearGradientBrush x:Key="ListboxHighlightBrush" EndPoint="0.5,1" StartPoint="0.5,0">
 <GradientStop Color="#FF7CA8FF" Offset="1"/>
 <GradientStop Color="#FF376FDC"/>
</LinearGradientBrush>

<SolidColorBrush x:Key="HyperlinkBrush" Color="#FFC8A1A1"/>

<!-- Search Listbox ItemStyle -->
<Style x:Key="ItemStyle" TargetType="ListBoxItem">
 <Setter Property="Padding" Value="4"/>
 <Setter Property="HorizontalContentAlignment" Value="Left"/>
 <Setter Property="VerticalContentAlignment" Value="Top"/>
 <Setter Property="Background" Value="{StaticResource BaseColorBrush}"/>
 <Setter Property="BorderBrush" Value="{StaticResource HoverBrush}"/>
 <Setter Property="Foreground" Value="#FF333333"/>
 <Setter Property="BorderThickness" Value="1"/>
 <Setter Property="TabNavigation" Value="Local"/>
 <Setter Property="Template">
  <Setter.Value>
   <ControlTemplate TargetType="ListBoxItem">
    <Grid>
     <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="CommonStates">
       <VisualState x:Name="Normal"/>
       <VisualState x:Name="MouseOver">
        <Storyboard>
         <DoubleAnimation Storyboard.TargetName="BGColor3" Storyboard.TargetProperty="Opacity" Duration="0" To="1"/>
         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="BGColor" Storyboard.TargetProperty="(UIElement.Opacity)">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.3"/>
         </DoubleAnimationUsingKeyFrames>
         <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)">
          <EasingColorKeyFrame KeyTime="00:00:00" Value="White"/>
         </ColorAnimationUsingKeyFrames>
        </Storyboard>
       </VisualState>
       <VisualState x:Name="Disabled">
        <Storyboard>
         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(UIElement.Opacity)">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.55"/>
         </DoubleAnimationUsingKeyFrames>
        </Storyboard>
       </VisualState>
      </VisualStateGroup>
      <VisualStateGroup x:Name="SelectionStates">
       <VisualState x:Name="Unselected"/>
       <VisualState x:Name="Selected">
        <Storyboard>
         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="BGColor2" Storyboard.TargetProperty="(UIElement.Opacity)">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.9"/>
         </DoubleAnimationUsingKeyFrames>
         <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)">
          <EasingColorKeyFrame KeyTime="00:00:00" Value="White"/>
         </ColorAnimationUsingKeyFrames>
        </Storyboard>
       </VisualState>
       <VisualState x:Name="SelectedUnfocused">
        <Storyboard>
         <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="BGColor2" Storyboard.TargetProperty="(UIElement.Opacity)">
          <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.9"/>
         </DoubleAnimationUsingKeyFrames>
         <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)">
          <EasingColorKeyFrame KeyTime="00:00:00" Value="#FFFFFFFF"/>
         </ColorAnimationUsingKeyFrames>
        </Storyboard>
       </VisualState>
      </VisualStateGroup>
      <VisualStateGroup x:Name="FocusStates">
       <VisualState x:Name="Focused">
        <Storyboard>
         <ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual" Storyboard.TargetProperty="Visibility" Duration="0">
          <DiscreteObjectKeyFrame KeyTime="0">
           <DiscreteObjectKeyFrame.Value>
            <Visibility>Visible</Visibility>
           </DiscreteObjectKeyFrame.Value>
          </DiscreteObjectKeyFrame>
         </ObjectAnimationUsingKeyFrames>
         <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)">
          <EasingColorKeyFrame KeyTime="00:00:00" Value="White"/>
         </ColorAnimationUsingKeyFrames>
        </Storyboard>
       </VisualState>
       <VisualState x:Name="Unfocused">
        <Storyboard/>
       </VisualState>
      </VisualStateGroup>
     </VisualStateManager.VisualStateGroups>
     <Rectangle x:Name="BGColor2" Fill="{StaticResource HoverBrush}" Stroke="Black" StrokeThickness="0" Opacity="0"/>
     <Rectangle x:Name="BGColor3" Fill="{StaticResource ListboxHighlightBrush}" StrokeThickness="0" Opacity="0"/>
     <Rectangle x:Name="BGColor" Stroke="Black" StrokeThickness="0" Opacity="0.3" Fill="{TemplateBinding Background}"/>
     <Border Height="20">
      <TextBlock Canvas.ZIndex="22" x:Name="contentPresenter" Foreground="{TemplateBinding Foreground}"
       Text="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding 
       HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" />
     </Border>
     <Rectangle  Opacity="0.4" x:Name="FocusVisual" Stroke="#BF313131" Margin="1" StrokeThickness="1" StrokeDashArray="1 2" StrokeDashCap="Square" Visibility="Collapsed" />
     <Rectangle x:Name="BorderRect" Stroke="Black" StrokeThickness="0" Opacity="0.3" />
    </Grid>
   </ControlTemplate>
  </Setter.Value>
 </Setter>
</Style>

And my consumption of the template:

 <ListBox ItemContainerStyle="{StaticResource ItemStyle}" x:Name="testListBox" />

And finally some code to populate the listbox:

   List<string> data = new List<string>();
   for (int i = 0; i < 30; i++) { 
    data.Add(DateTime.Now.AddDays(i).ToString("MMMM dd yyyy"));
   }   
   testListBox.ItemsSource = data;
+2  A: 

Hi, I tried your scenario. By default the content of the default listitem is a string which is black. A listbox item has a foreground property you can set but I'm not sure either where it is in the template. They did something special for strings to use the foreground but didn't expose it in the template. A hack you might want to try is, if you know you will always have string content, to replace the contentpresenter of the controltemplate with a textblock, which does have the forground. (To edit the ListItem's template right click on it in Blend3 and go to Edit Template, Edit a Copy, and modify the new Style}

<ContentPresenter x:Name="contentPresenter" 
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />

to

<TextBlock x:Name="contentPresenter" Foreground="{TemplateBinding Foreground}"
Text="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding 
HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" />

I added the TemplateBinding for Text and Foreground. You should be able to manipulate the foreground in the visual states to get the right states look disabled, selected, etc. I hope this helps.

Paully
I posted a response - I implemented your suggested tactic and it works 99% of the time. Perhaps at this point it's more a framework issue - in the second example of code I pasted in everything works until a person does a mouse out on the selected item in the listbox.
David in Dakota
+3  A: 

Change the ContentPresenter in you template to a ContentControl which has a Foreground property you can TemplateBind.

Shawn Wildermuth
Thanks Shawn, so even when I used the TextBlock I was able to hook into the Foreground but the issue I was running into had to do with the VisualState changes such that when you mouse out of the selected item it remains white instead of going back to the default. I tried it by replacing ContentPresenter for TextBlock just to see if that made a difference and still hit the snag.
David in Dakota
Ah, good suggestion! That's a better idea than a TextBlock. I will use it next time.
Paully

related questions