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"/>
  <Style.Triggers>
    <Trigger Property="IsMouseOver" Value="True">
      <Setter Property="Background" Value="Green"/>
    </Trigger>
    <Trigger Property="IsPressed" Value="True">
      <Setter Property="FontWeight" Value="Bold"/>
      <Setter Property="Width" Value="200" />
      <Setter Property="Background" Value="Lime" />
    </Trigger>
  </Style.Triggers>
</Style>

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"
     SnapsToDevicePixels="True"
     Background="{TemplateBinding Background}"
     BorderBrush="{TemplateBinding BorderBrush}">
    <ContentPresenter
       HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
       Margin="{TemplateBinding Padding}"
       VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
       SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
       Content="{TemplateBinding Content}"
       ContentTemplate="{TemplateBinding ContentTemplate}"
       RecognizesAccessKey="True"/>
  </Microsoft_Windows_Themes:ButtonChrome>
</ControlTemplate>

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 (9016 bytes)

1 Comment on WPF styles and default behavior

  1. At <a href="http://www.xamltemplates.net&quot; title="www.xamltempaltes.net">www.xamltemplates.net</a> you can buy themes for all the wpf controls and there is a free sample that you can download.

    Like

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s