Search code examples
wpfxamlcontroltemplatedatatrigger

Changing button background in ControlTemplate DataTrigger


I'd like to define a WPF ControlTemplate for a "color swatch" button. Essentially, I want to be able to use the template like this:

<Button Template="{StaticResource SwatchButtonTemplate}" Background="{Binding ActiveColor}">

Where ActiveColor is a Color property in the data context. I've been able to achieve that with this template:

<ControlTemplate x:Key="SwatchButtonTemplate" TargetType="{x:Type Button}">
    <Border Name="OuterBorder" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{StaticResource BorderBrush}" Background="{TemplateBinding Background}">
        <Border Name="InnerBorder" BorderThickness="0" BorderBrush="White" Background="{TemplateBinding Background}">
            <ContentPresenter/>
        </Border>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="OuterBorder" Property="BorderThickness" Value="1"/>
            <Setter TargetName="InnerBorder" Property="BorderThickness" Value="1"/>
            <Setter TargetName="OuterBorder" Property="BorderBrush" Value="{StaticResource MouseOverAccentBrush}"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

But now what I'd like to do is use a special brush for the button background if the color is transparent (e.g. display a red "X" in the background if the color is transparent).

I can't seem to figure out how to set up a trigger in the template that checks the background color, and if it is transparent, use a different brush for the Background property. I've tried adding a DataTrigger to the template:

    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            ...
        </Trigger>
        <DataTrigger Binding="{TemplateBinding Background}" Value="#00000000">
            <Setter TargetName="InnerBorder" Property="Background" Value="{StaticResource transparent_swatch_brush}"/>
        </DataTrigger>
    </ControlTemplate.Triggers>

But I get a XamlParseException when using this.

Edit: The full usage of this template is in a list of swatch buttons, and the source of the list is bound to an ObservableCollection<Brush> called ColorList populated with SolidColorBrushes (one of which is transparent)

<ItemsControl ItemsSource="{Binding ColorList}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button x:Name="button" Background="{Binding}"
                    Template="{StaticResource SwatchButtonTemplate}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Height="20" Rows="1" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

And the full template definition:

<ControlTemplate x:Key="SwatchButtonTemplate" TargetType="{x:Type Button}">
    <Border Name="OuterBorder" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{StaticResource BorderBrush}" Background="{TemplateBinding Background}">
        <Border Name="InnerBorder" BorderThickness="0" BorderBrush="White" Background="{TemplateBinding Background}">
            <ContentPresenter/>
        </Border>
    </Border>
    <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter TargetName="OuterBorder" Property="BorderThickness" Value="1"/>
            <Setter TargetName="InnerBorder" Property="BorderThickness" Value="1"/>
            <Setter TargetName="OuterBorder" Property="BorderBrush" Value="{StaticResource MouseOverAccentBrush}"/>
        </Trigger>
        <DataTrigger Binding="{Binding Background, RelativeSource={RelativeSource Mode=TemplatedParent}}" Value="#00ffffff">
            <Setter TargetName="InnerBorder" Property="Background" Value="{StaticResource transparent_swatch_brush}"/>
        </DataTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Solution

  • Thanks to @EdPlunkett for the suggestion to avoid "{TemplateBinding ...}" in the data trigger. The solution (XAML only) is to use a "Self" relative source:

    <ControlTemplate x:Key="SwatchButtonTemplate" TargetType="{x:Type Button}">
        <Border Name="OuterBorder" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{StaticResource BorderBrush}" Background="{TemplateBinding Background}">
            <Border Name="InnerBorder" BorderThickness="0" BorderBrush="White" Background="{TemplateBinding Background}">
                <ContentPresenter/>
            </Border>
        </Border>
        <ControlTemplate.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter TargetName="InnerBorder" Property="BorderThickness" Value="1" />
            </Trigger>
            <DataTrigger Binding="{Binding Path=Background.Color, RelativeSource={RelativeSource Self}}" Value="#00ffffff">
                <Setter TargetName="InnerBorder" Property="Background" Value="{StaticResource transparent_swatch_brush}" />
            </DataTrigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    

    (also note that Colors.Transparent is #00ffffff, not #00000000 as I thought).