Architect or Cobbler?
Good code starts with good design

Styling WPF Buttons

Saturday, November 11, 2006

I was at TechEd last week and I was asked several times how to customize buttons. The answer is pretty straightforward, you use styles. If you're an ASP.Net developer that won't scare you, but if you're a Windows Forms developer then you may need a quick refresher.

You can apply a style to any control, and give default values for properties, so for instance you could create a style template that always coloured your buttons red, something like this:

<Window.Resources> <Style x:Key="MyRedButtonStyle" TargetType="{x:Type Button}"> <Setter Property="Background" Value="Red" /> </Style> </Window.Resources>

To use this style in your button is fairly trivial, you simply reference the key you created as in the following code:

<Button Style="{StaticResource MyRedButtonStyle}"> Hello</Button>

Of course this simply allows you to change the basic properties, what if I want to change the entire look and feel of the Button control - well in that case you need to provide a control template. At first glance this appears more complex, but it isn't really that bad.


<Window.Resources> <Style x:Key="MyButtonStyle" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Grid> <Ellipse Name="EllipseButton"

Width="{TemplateBinding Width}"

Height="{TemplateBinding Height}" > <Ellipse.BitmapEffect> <BevelBitmapEffect BevelWidth="3"

Relief=".6" >

</BevelBitmapEffect> </Ellipse.BitmapEffect>

<Ellipse.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="Aqua" Offset="0" /> <GradientStop Color="Gray" Offset=".9" /> </LinearGradientBrush> </Ellipse.Fill>

</Ellipse> <ContentPresenter TextElement.FontSize="{TemplateBinding FontSize}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" /> </Grid>

</ControlTemplate> </Setter.Value> </Setter> </Style>

When you provide a control template you need to do all the redrawing, as it currently stands your button UI doesn't change when the button is clicked, so you need to provide a trigger to update the template as the user clicks on the button, as shown below:


<Window.Resources> <Style x:Key="MyButtonStyle" TargetType="{x:Type Button}">

. . .

</Grid> <ControlTemplate.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsMouseOver" Value="True" /> <Condition Property="IsPressed" Value="True" /> </MultiTrigger.Conditions> <Setter TargetName="EllipseButton" Property="Ellipse.Fill" > <Setter.Value> <LinearGradientBrush StartPoint="0,1" EndPoint="0,0"> <GradientStop Color="Aqua" Offset="0" /> <GradientStop Color="Gray" Offset=".9" /> </LinearGradientBrush> </Setter.Value> </Setter> <Setter TargetName="EllipseButton" Property="Ellipse.BitmapEffect" > <Setter.Value> <BevelBitmapEffect BevelWidth="1" Relief=".1" ></BevelBitmapEffect> </Setter.Value> </Setter>

</MultiTrigger> </ControlTemplate.Triggers>

</ControlTemplate> </Setter.Value> </Setter> </Style>

You can customise any control in this way and the flexibility is incredible. Of course you could also do all of this through source code if you are completely set against XAML.

You should notice the TemplateBinding directives, these simply specify that the values in the XAML will be passed through to the template, as the example below shows, the height and width attributes are passed to the underlying template:


<Button Name ="myButton" Style="{StaticResource MyButtonStyle}" Height="80" Width="80" Click="OnClick"> Button</Button>

Running this gives you the following button , and clicking it changes the GUI, so all in all a great 10 minutes work.


# posted by James @ 5:13 PM   2 comments Comments: It would probably be beneficial to template bind the Margin of the ContentPresenter to the Padding on the button.

You might also want to consider template binding the ContentTemplate and ContentTemplateSelector in the button, in case developer using the button wants to tweak the template.

You also don't need to bind the Width and Height of the Ellipse, as it will naturally scale to the size of the Grid (and thus the Button).
# posted by Anonymous Anonymous @ 3:08 PM   Nice article!
That's an interesting point "You also don't need to bind the Width and Height". I'm a newbie and it appeared these values were picked up automatically via inheritance. However if I set the size of the Grid the Button doesn't scale to that size instead it didn't seemed to be drawn properly!
# posted by Blogger martin @ 8:47 AM   Post a Comment

<< Main blog page
This page is powered by Blogger. Isn't yours?