Friday, 28 March 2008

Gel Button Template in Silverlight

A while ago I posted about how to get a gel button effect in Silverlight. With the advent of Silverlight 2.0 beta, it is now possible to turn this into a button template for easy reuse. What's more, it is relatively straightforward to add animations for states such as MouseOver and MouseDown.

The way to go about this is to create a style in your App.xaml file (you can put the style in any XAML page, but putting it in App.Xaml makes it available everywhere in your application).

<LinearGradientBrush x:Key="GreenGradientBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientStop Offset="0" Color="#006700"/>
    <GradientStop Offset="1" Color="#00ef00"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="RedGradientBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientStop Offset="0" Color="#774040"/>
    <GradientStop Offset="1" Color="#ef4040"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="BlueGradientBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientStop Offset="0" Color="#404077"/>
    <GradientStop Offset="1" Color="#4040ef"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="CyanGradientBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientStop Offset="0" Color="#007777"/>
    <GradientStop Offset="1" Color="#00efef"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="YellowGradientBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientStop Offset="0" Color="#777740"/>
    <GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="MagentaGradientBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientStop Offset="0" Color="#770077"/>
    <GradientStop Offset="1" Color="#ef00ef"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="OrangeGradientBrush" StartPoint="0,0" EndPoint="0,1">
    <GradientStop Offset="0" Color="DarkOrange"/>
    <GradientStop Offset="1" Color="Orange"/>
</LinearGradientBrush>

<Style TargetType="Button" x:Key="GelButton">
    <Setter Property="Background" Value="{StaticResource GreenGradientBrush}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid x:Name="RootElement">
                    <Grid.Resources>
                        <Storyboard x:Key='MouseOver State'>
                            <DoubleAnimation Storyboard.TargetName='MainButtonScale' 
                                Storyboard.TargetProperty='ScaleX' To='1.03' 
                                Duration='0:0:0.05'/>
                            <DoubleAnimation Storyboard.TargetName='MainButtonScale' 
                                Storyboard.TargetProperty='ScaleY' To='1.03' 
                                Duration='0:0:0.05'/>
                        </Storyboard>
                        <Storyboard x:Key='Normal State'>
                        </Storyboard>
                        <Storyboard x:Key='Pressed State'>
                            <DoubleAnimation Storyboard.TargetName='MainButtonTranslate' 
                                Storyboard.TargetProperty='X' To='1.0' 
                                Duration='0:0:0.05'/>
                            <DoubleAnimation Storyboard.TargetName='MainButtonTranslate' 
                                Storyboard.TargetProperty='Y' To='1.0' 
                                Duration='0:0:0.05'/>
                        </Storyboard>
                        <Storyboard x:Key="Disabled State">                                    
                            <DoubleAnimation Duration="0:0:0" 
                                Storyboard.TargetName="Disabled" 
                                Storyboard.TargetProperty="Opacity" 
                                To="1" />
                        </Storyboard>
                    </Grid.Resources>

                    <!-- drop shadow - needs to be blurred for correct effect -->
                    <Rectangle Fill="#40000000" Margin="1,1,0,0" RadiusX="6" RadiusY="6" />

                    <!-- main rect -->
                    <Grid x:Name="mainButton"
                          Margin="0,0,1,1"
                          RenderTransformOrigin="0.5,0.5">
                        <Grid.RenderTransform>
                            <TransformGroup>
                                <TranslateTransform x:Name="MainButtonTranslate"
                                                X="0.0" Y="0.0"/>
                                <ScaleTransform 
                                x:Name="MainButtonScale"
                                ScaleX="1.0" ScaleY="1.0" 
                                />
                            </TransformGroup>

                        </Grid.RenderTransform>
                        <Rectangle x:Name="mainRect" 
                               Fill="{TemplateBinding Background}"
                               RadiusX="6" RadiusY="6">
                        </Rectangle>

                        <!-- transparent rect -->
                        <Rectangle x:Name="glowRect" Margin="1,1,1,1" RadiusX="5" RadiusY="5">
                            <Rectangle.Fill>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                    <GradientStop Offset="0" Color="#DFFFFFFF"/>
                                    <GradientStop Offset="1" Color="#00FFFFFF"/>
                                </LinearGradientBrush>
                            </Rectangle.Fill>
                        </Rectangle>

                        <ContentPresenter HorizontalAlignment="Center"
                             Margin="4,0,4,0"
                             Content="{TemplateBinding Content}"
                             VerticalAlignment="Center" />

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

As you can see, there is a lot of XAML, but it is fairly straightforward once you know what it is. First I have declared a load of LinearGradientBrushes as static resources. These give me an easy way to specify a different colour scheme for the buttons without having to create a whole new template for each one.

Then there is the actual style. We have two properties. The first sets a default background for us - green, but this can be overridden in XAML. The second provides the template. This consists of the visual elements required to draw our gel button, and a number of animations.

The template XAML itself is not really any different from that shown in my original post, but there are a few exceptions. First we are using a "ContentPresenter" to show the text or image that the button will contain. Second, we are using TemplateBinding to set properties such as Background, Foreground etc. And finally, since we are using Grids, we only need to use Margins to position things correctly, rather than specifying sizes.

The animations are a basic scale for mouse-over which makes the buttons larger, and a translate for pressed. Interestingly, pressing the button seems to cancel the mouse-over animation, which actually has a nice visual effect so I am OK with that. Centring the scale transform was the trickiest part. I tried for ages to get a Converter working that would use a TemplateBinding on Width and Height to divide them by two, but in the end I was helped out on the Silverlight forums with a much easier solution. Simply set the RenderTransformOrigin on the grid to "0.5,0.5" and the scale operates in the middle.

To create one of these buttons is trivially simple. This example uses the GelButton style and changes the Background to Blue.

<Button x:Name="test3" 
   Margin="2" 
   Style="{StaticResource GelButton}" 
   Background="{StaticResource BlueGradientBrush}" 
   Height="24" Width="100" 
   Content="Hello World 3" />

There are some improvements that still need to be made before my GelButton templates are complete

  • Better choice of gradient colours
  • Implement disabled state
  • Implement focus Rectangle
  • Mouse-over effect should be made a little more subtle
  • Shadow effect on the content presenter?

Here's what it looks like:

3 comments:

Anonymous said...

Great post! thanks

Anonymous said...

Dear Mark,
I got an error at
ContentPresenter HorizontalAlignment="Center" Margin="4,0,4,0" Foreground="{TemplateBinding Foreground}" Content="{TemplateBinding Content}" VerticalAlignment="Center"

saying:
The property 'Foreground' does not exist on the type 'ContentPresenter' in the XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml/presentation'. C:\sources\silverlightsdemos\button2\button2\button1\App.xaml

Alex

Unknown said...

anonymous, this was done with Silverlight 2 beta. It needs updating to work with the final release. If I ever get round to it I'll post an update here