Search code examples
wpfxamldatatrigger

How can I pass a parameter to a DataTrigger?


I have a list of things that I'm displaying via ItemsControl where each Item is basically a card that can be clicked. Is there a way I can pass a parameter to a DataTrigger to show whether or not a card has been clicked and if it is clicked set the Background to another color?


Solution

  • [...] show whether or not a card has been clicked and if it is clicked [...]

    You created a view of cards and want them to be selectable. An ItemsControl is does not support selection, but there is a control called Selector that derives from ItemsControl, which is the abstract base type for items controls that need selection. Its derivatives include ListBox and ListView, which come with selection and highlighting out-of-the-box. In other words, do not re-invent the wheel, there are already more suitable controls that meet your requirements.

    Types derived from Selector contain dependency properties for SelectedIndex, SelectedItem or SelectedValue, which makes it easy for you to bind them and create triggers. There is also an attached property IsSelected for item containers, which is exactly what you need to change the Background or any other property depending on the clicked or selected item.

    In the following I will show you how to customize the appearance of ListBox items. You can do the same with a ListView. You can extract the default style and template for a ListBoxItem using Blend or Visual Studio.

    As you can see below there are a few brushes, a focus visual and the style with control template and triggers. Adapt this style to meet your desired design. Look for the triggers that bind the IsSelected property.

    <SolidColorBrush x:Key="Item.MouseOver.Background" Color="#1F26A0DA"/>
    <SolidColorBrush x:Key="Item.MouseOver.Border" Color="#a826A0Da"/>
    <SolidColorBrush x:Key="Item.SelectedActive.Background" Color="#3D26A0DA"/>
    <SolidColorBrush x:Key="Item.SelectedActive.Border" Color="#FF26A0DA"/>
    <SolidColorBrush x:Key="Item.SelectedInactive.Background" Color="#3DDADADA"/>
    <SolidColorBrush x:Key="Item.SelectedInactive.Border" Color="#FFDADADA"/>
    <Style x:Key="FocusVisual">
       <Setter Property="Control.Template">
          <Setter.Value>
             <ControlTemplate>
                <Rectangle Margin="2" StrokeDashArray="1 2" SnapsToDevicePixels="true" StrokeThickness="1" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
             </ControlTemplate>
          </Setter.Value>
       </Setter>
    </Style>
    <Style x:Key="ListBoxItemContainerStyle" 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" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" 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>
    

    Move these resources to a resource dictionary that is in scope of the controls where you want to use them, or simply copy them to the application resources to make them globally available. In order to apply the style, you have two options.

    1. Use the x:Key and reference them as ItemContainerStyle in each ListBox.

      <ListBox ItemContainerStyle="{DynamicResource ListBoxItemContainerStyle}" .../>
      
    2. Make the style implicit by removing the x:Key. Then it will be applied to all ListBoxItem in scope of the resource dictionary that contains it.

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