WPF Styles and Default Behavior

After my recent presentation at the VBUG conference, somebody sent me this question: “I have code to style a button and several triggers to change the button’s background color. For some reason, the trigger for the IsPressed property doesn’t seem to work. Why?” Here’s the style code that was being used (this actually includes some changes I made to the original, but never mind that):

<Style TargetType="{x:Type Button}">
  <Setter Property="Background" Value="Red"/>
    <Trigger Property="IsMouseOver" Value="True">
      <Setter Property="Background" Value="Green"/>
    <Trigger Property="IsPressed" Value="True">
      <Setter Property="FontWeight" Value="Bold"/>
      <Setter Property="Width" Value="200" />
      <Setter Property="Background" Value="Lime" />

With a few buttons on a form and this style code installed in App.xaml, you can easily see that he’s right – the IsMouseOver property change triggers a change of the background color to green, but IsPressed seems to do its job only partly, since it misses setting the Background property to Lime. In the original example, the change to the background color in that trigger was actually the only thing that was supposed to happen, so it looked like nothing was happening at all. With my changes, it seems obvious that the trigger is observed correctly, but the Background property change doesn’t seem to be executed.

The reason for this behavior is simple: there are standard templates in place that format the buttons in the way we’re normally used to seeing a button formatted. On Vista that means setting the background color to a certain gradient, displaying a rounded rectangle, and having certain animations in place that are executed when the button is focused, the mouse moves over it or it is clicked. This standard behavior is also implemented as control templates using triggers, and so interests collide - the triggers in the sample code actually do set the background color to Green or Lime, only the standard trigger quickly starts doing something else. As a result, the IsMouseOver trigger sets the color to Green only for a short while before the standard animation sets in, and in the IsPressed case the color is actually never seen.

To solve this problem I used Blend to get hold of the standard control template for a button. I opened my test solution in Blend, selected one of my buttons and used the context menu entry Edit Control Parts (Template) | Edit a Copy… to have a block of code for the base template inserted into my XAML file. Note that this block is inserted into the Window class XAML file by default. In many cases this is actually the sensible solution, but in the sample application I had my styles defined in App.xaml, so I had to move the block across to that file. If you do this, be aware that there’s also an XML namespace Microsoft_Windows_Themes inserted by Blend, which you have to move together with the code in order to make things work.

Now, if you have a close look at this code that has been created by Blend, you will see that there are three “events” called RenderDefaulted, RenderMouseOver and RenderPressed configured to call various elements of the ButtonBase class, which are pulled in using the TemplateBinding syntax extension. In addition, there are some triggers that are also involved with changing the button’s appearance in response to various events. For this example, I chose to remove all those elements to break things down to an extremely basic button that doesn’t react visually anymore (apart from my own triggers) to mouse moves or clicks. In reality this might not always be the right thing to do – this article is just meant as a starting point for readers to figure out what they need to do in their own use cases. Anyway, after my modifications, this is what I was left with:

<ControlTemplate x:Key="buttonTemplate" TargetType="{x:Type ButtonBase}">
  <Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome"
     Background="{TemplateBinding Background}"
     BorderBrush="{TemplateBinding BorderBrush}">
       HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
       Margin="{TemplateBinding Padding}"
       VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
       SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
       Content="{TemplateBinding Content}"
       ContentTemplate="{TemplateBinding ContentTemplate}"

Blend had automatically inserted a Template attribute for this control template into XAML, to make the particular button, which I had called the context menu on, work with this template. So I just had to run the application and there it was – two buttons still working in the same way as ever, and a third one without any of the animations and perfect green and lime colors depending on mouse state. Great!

You can download the source code for my test application here: WPFControlTemplating.zip

Sorry, this blog does not support comments.

I used various blog hosting services since this blog was established in 2005, but unfortunately they turned out to be unreliable in the long term and comment threads were lost in unavoidable transitions. At this time I don't want to enable third-party services for comments since it has become obvious in recent years that these providers invariably monetize information about their visitors and users.

Please use the links in the page footer to get in touch with me. I'm available for conversations on Keybase, Matrix, Mastodon or Twitter, as well as via email.