0 Comments

In this post I will demonstrate how to create a custom template for a WPF button using XAML. In particular we will look at being able to have complete control over all the visual states, including disabled, mouse over, mouse down and even the appearance of the focus rectangle.

Stage 1: Creating a style

The first thing to do is to create a Style which sets the properties of the Button that we want to customize. We will put this in the Page.Resources section of our XAML file.

<Style x:Key="OrangeButton" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="FontFamily" Value="Verdana"/>
<Setter Property="FontSize" Value="11px"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource MyFocusVisual}" />
<Setter Property="Background" >
   <Setter.Value>
       <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" >
           <GradientStop Color="#FFFFD190" Offset="0.2"/>
           <GradientStop Color="Orange" Offset="0.85"/>
           <GradientStop Color="#FFFFD190" Offset="1"/>
       </LinearGradientBrush>
   </Setter.Value>
</Setter>

This is all fairly straightforward stuff - I am just setting the font and background gradient. The only complicated bit is that I am overriding the focus rectangle drawing, as I want a smaller rectangle than the one that gets drawn by default. So I need another style in my Page.Resources section:

<Style x:Key="MyFocusVisual">
     <Setter Property="Control.Template">
       <Setter.Value>
         <ControlTemplate TargetType="{x:Type Control}">
             <Grid Margin="3 2">
               <Rectangle Name="r1" StrokeThickness="1" Stroke="Black" StrokeDashArray="2 2"/>
               <Border Name="border" Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding ActualHeight}"  CornerRadius="2" BorderThickness="1" />
             </Grid>
         </ControlTemplate>
       </Setter.Value>
     </Setter>
   </Style>

Stage 2: Creating a template

We would already be finished if I just wanted the normal appearance of my button to be changed. But I want complete control - including the appearance in mouse over, mouse down and disabled situations. So a template allows us to completely control what visual elements constitute our control.

Again this goes in Page.Resources. The first part is shown here:

<Setter Property="Template">
   <Setter.Value>
       <ControlTemplate TargetType="Button">
           <Border Name="border"
               BorderThickness="1"
               Padding="4,2"
               BorderBrush="DarkGray"
               CornerRadius="3"
               Background="{TemplateBinding Background}">
               <Grid >
               <ContentPresenter HorizontalAlignment="Center"
                              VerticalAlignment="Center" Name="contentShadow"
                   Style="{StaticResource ShadowStyle}">
                   <ContentPresenter.RenderTransform>
                       <TranslateTransform X="1.0" Y="1.0" />
                   </ContentPresenter.RenderTransform>
               </ContentPresenter>
               <ContentPresenter HorizontalAlignment="Center"
                           VerticalAlignment="Center" Name="content"/>
               </Grid>
           </Border>

Here you can see I have set up a simple border which gives my button rounded corners, a single pixel gray border, and uses the fill from the control's Background property. This means that by default it will use the orange gradient specified in my style, but that users can override this for their own Background.

To draw the content (usually text), we use a ContentPresenter control. I am trying to do something clever here, which is create a bevelled effect on the text by drawing it again in gray. This works fine on text, but for some reason doesn't do anything when the Content is a Shape. I'm not sure why that is. ShadowStyle is defined as follows:

<Style x:Key="ShadowStyle">
   <Setter Property="Control.Foreground" Value="LightGray" />
</Style>

Stage 3: Applying some triggers

Now we need to add some triggers to our Button template so that we can change its appearance on various events.

First is mouse over. When IsMouseOver becomes true, we change the colour of the border and the text colour to blue. Unfortunately, setting the Foreground property does nothing if the content is a shape.

<ControlTemplate.Triggers>
  <Trigger Property="IsMouseOver" Value="True">
     <Setter TargetName="border" Property="BorderBrush" Value="#FF4788c8" />
     <Setter Property="Foreground" Value="#FF4788c8" />
  </Trigger>

Next is mouse down. When IsPressed becomes true, we modify the background gradient so it looks like the button has gone 'down'. I also move the text down one pixel.

<Trigger Property="IsPressed" Value="True">                   
   <Setter Property="Background" >
   <Setter.Value>
       <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" >
           <GradientStop Color="#FFFFD190" Offset="0.35"/>
           <GradientStop Color="Orange" Offset="0.95"/>
           <GradientStop Color="#FFFFD190" Offset="1"/>
       </LinearGradientBrush>
   </Setter.Value>
   </Setter>
   <Setter TargetName="content" Property="RenderTransform" >
   <Setter.Value>
       <TranslateTransform Y="1.0" />
   </Setter.Value>
   </Setter>
</Trigger>

Third, we draw a dark gray border when a button is focussed or if it is the default button (the button that will be clicked when you press enter).

<Trigger Property="IsDefaulted" Value="True">
   <Setter TargetName="border" Property="BorderBrush" Value="#FF282828" />
</Trigger>
<Trigger Property="IsFocused" Value="True">
   <Setter TargetName="border" Property="BorderBrush" Value="#FF282828" />
</Trigger>

Finally, the when the button is disabled, we gray out the text and wash out the background a little by reducing its opacity.

<Trigger Property="IsEnabled" Value="False">
       <Setter TargetName="border" Property="Opacity" Value="0.7" />
       <Setter Property="Foreground" Value="Gray" />
   </Trigger>
</ControlTemplate.Triggers>

Stage 4: Using the control

Now we are ready to use our control. All we have to do is set the Style property of our button. We can override any of the settings such as font size if we like. The button will automatically resize itself to fit the content.

<StackPanel HorizontalAlignment="Center">
<Button Style="{StaticResource OrangeButton}">Hello</Button>
<Button Style="{StaticResource OrangeButton}">World</Button>
<Button Style="{StaticResource OrangeButton}" FontSize="20">Big Button</Button>
<Button Style="{StaticResource OrangeButton}" IsDefault="True">Default</Button>
<Button Style="{StaticResource OrangeButton}" IsEnabled="False">Disabled</Button>
<Button Style="{StaticResource OrangeButton}" Width="70" Height="30">70 x 30</Button>
<TextBox />
<Button Style="{StaticResource OrangeButton}" Width="30" Height="30">
<Path Fill="Black" Data="M 3,3 l 9,9 l -9,9 Z" />
</Button>
</StackPanel>

Here's what it looks like:

image

Download an example XAML file here.

Vote on HN

Comments

Comment by F Quednau

Nice work...only nag is that I have trouble seeing your code (this is firefox 2.0.0.6): It gets clipped by its container at the point where the right-hand side starts.

Comment by Peregrinati

Me too - I've noticed this code clipping on a lot of sites (including code project!), it's quite aggrevating.

Good code otherwise!

Comment by Mark H

sorry about the clipping. hopefully you can get at the code by viewing the page source. I'm hoping to migrate to another blog platform sometime this year

Comment by Linda Rawson

It looks like great work. Is there anyway to get the entire code piece? Like exactly what I would put in the resource section?

Comment by Anonymous

very well explained. this was the best explaination after reading three books on WPF.

thanks!!!!

Anonymous
Comment by Steve

why don't you just type gibberish on the internet. Oh wait, you have! Complete code snippet please.

Comment by Anonymous

To all the people who'se panties are in a knot over the clipping issue (yes, you Steve) appreciate the fact that you have a code snippet that works. It's obviously not the authors fault. Go click view source you fool.

Anonymous
Comment by ¬°El Perro!

Where is the download link to get a visual studio project with this example????

Comment by Mark H

hi el perro,
I'm afraid there isn't a download link. I did this in XAML Pad.

Comment by Evan

This is wonderful, useful, and better still, I now know how to do it myself. Thanks!

For kicks, I added a trigger for when the button is disabled.

(To the complainers - the code is usable as-is. All you need to do is </end> a few tags, bung it into a resource dictionary, and tell App.xaml about the dictionary.)

Evan
Comment by Anonymous

It is not at all working. It was just a time weast from some fool.

Anonymous
Comment by ghkj

This comment has been removed by a blog administrator.

Comment by wducklow

Hey, great example.

The one thing that is giving me problems is the mnemonics.

When I add "_Hello" to the content of the button the alt-H functionality is neither drawn or works. Any ideas why?

Comment by Nick

Thanks for the post. I am changing few colors now and put it to work in real life. Good job.

Nick
Comment by Amar Syafiq

Hi mark,
instead of creating the custom button in a page can i create it in window as well? or use it in window?

Comment by Mark H

Hi Amar, of course, you can use it wherever you like

Comment by RRave

Dear Sir,

I hope you are doing well. I got this email address from one of your contribution web site. I have launched a web site www.codegain.com and it is basically aimed C#,JAVA,VB.NET,ASP.NET,AJAX,Sql Server,Oracle,WPF,WCF and etc resources, programming help, articles, code snippet, video demonstrations and problems solving support. I would like to invite you as an author and a supporter.
Looking forward to hearing from you and hope you will join with us soon.

Thank you
RRaveen
Founder CodeGain.com

Comment by Bernd

Thanks for sharing this. Regarding the style for the FocusVisual I was wondering why you put a Rectangle and a Border inside the Grid... removing the Border does not seem to change anything.
Cheers,
Bernd

Comment by Mark H

Hi Bernd,
I can't quite remember why I put it in. It was a long time ago! I think it was to do with making sure the focus rectangle was an appropriate size, but from what you are saying, perhaps it is no longer needed.

Comment by Anonymous

Hi,
what do i have to do if i want the button to be round? I tried to change the rectangle to ellipse in the "MyFocusVisual"-Style but that doesnt work. any ideas?

Anonymous
Comment by Sheshu Panga

Hi Mark,
It's indeed a nice article, though very old but still applicable. I live in Bournemouth, UK and in my spare time I try to write some applications for my every day use. This article has helped me a lot - Thanks a lot. How do I follow your blog? I don't see any option!
Regards,
Sheshu -

Comment by Mark H

hi Sheshu,
I've added a subscribe link to the right, although your feed reader should be able to determine the RSS URL if you just give it the main site feed.

Comment by Anonymous

In your last button example you use
Path Fill="Black"

How can I change color from black to white on IsMouseOver trigger?

Thank you.

Anonymous
Comment by Mark H

hi anonymous,
I haven't worked out a way to make that happen. If I ever find out I'll update this post.
Mark

Comment by pawels blog

Hi! Thank your for this example! It's very nice.
I adopted your xaml-code to my
Application.Recources.
If I add the Buttons, as you described, in my MainWindow.xaml, i can used ist -> no problem.
My Problem is that I want to add the Button dynamically in my C# code.
E.G.: Button myButton = new Button();
But how can I add the Style of your Button to myButton?
Can you, or anyone else, give me a hint? :)

Thx!

Comment by celil aydin

Hi
Thank you for this very helpfull tutoroial. I have a questions regarding to templates in XAML.Let's assume that I created one button templated in Templates.xaml file and I would like to use that button in MyButtons.xaml file in the same project. How it should be referred in the second XAML file? In other words, is it possible to use templates in another XAML files?
Best regards.

Comment by Brad

Nice work. Do you know how to make a accelerator character work with this? I loose this feature when specify the button style.


Thanks,
Brad

Comment by victor cassel

Thank you! I used this to create a button with both image and text. It works great!

victor cassel
Comment by Clark Kent

This comment has been removed by a blog administrator.

Clark Kent
Comment by Anonymous

How do you use a hotkey with these buttons, like Alt+H for the Hello button?
Thanks.

Anonymous
Comment by LePanther

This is a great code! Thanks for sharing! :-)

LePanther
Comment by Mel

If you're interested in enabling the AccessKey functionality in this template, meaning a Button with "_Cancel" as its content will display as "Cancel" and accept Alt+C as an activation event, then you simply need to add RecognizeAccessKey="True" to the ContentPresenter with Name="content".

Comment by Mark H

thanks Mel, that's a useful tip

Comment by Wolfgang U.

Thx - I like the example, modify the code for my uses and I am happy to figure it out, how to create a button with a template. Well - this is my first day in WPF ;)

Wolfgang U.
comments powered by Disqus