3
votes

First of all, this is not a duplicate of Setting the Inactive Highlight Colour of a WPF ListBox to the Active Highlight Colour. An explanation for that is given below.

Setting:

I have a WPF ListBox in a UserControl that will later be put into an application that uses heavy theming. From the perspective of the UserControl, I don't know in advance what the theming will be like.

Desired behavior:

If the ListBox does not have focus at some point, I still want the selected ListBoxItems to have the same appearance as if the ListBox does have focus.

Additional information:

Note that just setting the colors to some system defaults will not work. Doing so would override the containing application's theming. (That's the reason why this question is not a duplicate of the linked question above.)

Is there a way to realize this, e.g. using XAML?

EDIT: After a bit of research, I think I want to create a copy of the "default" ListBoxItem style ("default" at least in terms of being the default at the level of the UserControl), where all Triggers with Property="Button.IsFocused" Value="False" will not be triggered and all Triggers with Property="Button.IsFocused" Value="True" will always be triggered.

Unfortunately I have no clue where to even start to perform research in how to accomplish this. So any hints towards places where I can start researching would be much appreciated as well.

1
Is the theme you're trying to do this with your own theme (that you control) or a 3rd party theme?zastrowm
Sorry for not getting back to you sooner. In retrospective I shouldn't have started a bounty directly before my vacation. ;) The idea behind this User Control is that it should be possible to put it into any kind of application. In practice however it'll most likely be used only in a single app - in which I could modify the theme(s). Obviously I'm more interested in an answer that would work theme-independently... but as there does not seem to be an easy way to realize something like that (going by your answer), I guess a more specialized approach (like yours) would be a solution as well.Hauke P.

1 Answers

3
votes

Summary

It seems like you want to achieve setting the focused style equal to the non-focused style, without editing a theme and doing it in a theme independent way. As far as I know, this isn't possible, primarily because each theme can technically implement ListBoxItem focus behavior in different ways. In fact, I've seen a theme where your desired behavior was the behavior of the ListBoxItem!

How to Modify the Theme

Now if you're open to modifying each theme to suite your needs, read ahead.

If you're modifying the theme globally, you can edit the style for the ListBoxItem directly (after finding out where it exists). If you want the changes applied more specifically, then you'll end up copying the current ListBoxItem style (from whatever theme you're editing) and making changes to it.

A copy of the default ListBoxItem theme is as follows (I used Visual Studio to make the copy). The things you need to change are going to be slightly different for each theme, but the general idea is the same.

<Style x:Key="FocusVisual">
  <Setter Property="Control.Template">
    <Setter.Value>
      <ControlTemplate>
        <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
<SolidColorBrush x:Key="Item.MouseOver.Background" Color="#1F26A0DA"/>
<SolidColorBrush x:Key="Item.MouseOver.Border" Color="#a826A0Da"/>
<SolidColorBrush x:Key="Item.SelectedInactive.Background" Color="#3DDADADA"/>
<SolidColorBrush x:Key="Item.SelectedInactive.Border" Color="#FFDADADA"/>
<SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
<SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>
<Style TargetType="{x:Type ListBoxItem}">
  <Setter Property="SnapsToDevicePixels" Value="True"/>
  <Setter Property="Padding" Value="4,1"/>
  <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
  <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
  <Setter Property="Background" Value="Transparent"/>
  <Setter Property="BorderBrush" Value="Transparent"/>
  <Setter Property="BorderThickness" Value="1"/>
  <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type ListBoxItem}">
        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
          <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
        </Border>
        <ControlTemplate.Triggers>
          <MultiTrigger>
            <MultiTrigger.Conditions>
              <Condition Property="IsMouseOver" Value="True"/>
            </MultiTrigger.Conditions>
            <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.MouseOver.Background}"/>
            <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.MouseOver.Border}"/>
          </MultiTrigger>
          <MultiTrigger>
            <MultiTrigger.Conditions>
              <Condition Property="Selector.IsSelectionActive" Value="False"/>
              <Condition Property="IsSelected" Value="True"/>
            </MultiTrigger.Conditions>
            <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
            <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
          </MultiTrigger>
          <MultiTrigger>
            <MultiTrigger.Conditions>
              <Condition Property="Selector.IsSelectionActive" Value="True"/>
              <Condition Property="IsSelected" Value="True"/>
            </MultiTrigger.Conditions>
            <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
            <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
          </MultiTrigger>
          <Trigger Property="IsEnabled" Value="False">
            <Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
          </Trigger>
        </ControlTemplate.Triggers>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

The key part is in the middle:

<MultiTrigger>
  <MultiTrigger.Conditions>
    <Condition Property="Selector.IsSelectionActive" Value="False"/>
    <Condition Property="IsSelected" Value="True"/>
  </MultiTrigger.Conditions>
  <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Background}"/>
  <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedInactive.Border}"/>
</MultiTrigger>
<MultiTrigger>
  <MultiTrigger.Conditions>
    <Condition Property="Selector.IsSelectionActive" Value="True"/>
    <Condition Property="IsSelected" Value="True"/>
  </MultiTrigger.Conditions>
  <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
  <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
</MultiTrigger>

This is setting up two different styles for the selected item while focused and while unfocused.

To get your desired behavior, you have one of two options; you can either simply turn it into a simple trigger just on IsSelected, replacing the above chunk with:

<Trigger Property="IsSelected" Value="True">
  <Setter Property="Background" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Background}"/>
  <Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource Item.SelectedActive.Border}"/>
</Trigger>

or you can change the Item.SelectedInactive.Background and Item.SelectedInactive.Border properties to match the active colors (this was above the ListBox style):

<SolidColorBrush x:Key="Item.SelectedInactive.Background" Color="#3D26A0DA"/>
<SolidColorBrush x:Key="Item.SelectedInactive.Border" Color="#FF26A0DA"/>
<SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
<SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>

Generally the first approach is preferred, as it's more clear what's going on.

Additional Constraints

Now, the above copy of the default theme's ListBoxItem will change it for all ListBoxItems. If you want to only change some, then you need to add a key to your "copied style", like so:

<Style x:Key="InactiveLikeActive" TargetType="{x:Type ListBoxItem}">

And then at some level above where you want the style applied (perhaps even just a single ListBox itself), add the following style definition:

<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource InactiveLikeActive}" />

For example:

<ListBox>
  <ListBox.Resources>
    <Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource InactiveLikeActive}" />
  </ListBox.Resources>
  <ListBoxItem>One</ListBoxItem>
  <ListBoxItem>Two</ListBoxItem>
</ListBox>

Closing Thoughts

While WPF makes it possible to override almost all default appearances, it doesn't necessarily make it easy, or simple to do.