Monday 9 June 2008

Basic Button Templating in XAML in Silverlight 2 Beta 2

There has been lots of buzz about the new control templating mechanism introduced with Silverlight 2 beta 2. It looks like the main driver behind this is allowing you to template controls using Expression Blend. Scott Guthrie covers this in depth in this post.

However, I always like to know exactly what is going on with my XAML, and I needed to style a button for my SilverNibbles game, so I decided to see how hard it would be.

As usual, the best place to start is by looking in MSDN at the existing templates for the controls. Although these are often bewilderingly long chunks of XAML, most of the time, you can throw large portions of it away.

For example, my button only needs two visual states - normal and mouse over. I am just going to darken the background slightly when the mouse is over the button:

silverlight-basic-button-templating

I decided to put my style in the App.xaml file in the <Application.Resources> section. To start with, I just customised the font. Notice I have also added in a vsm namespace for use with the Visual State Manager later.

<Style 
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    TargetType="Button"
    x:Key="NewGameButtonStyle">
    <Setter Property="FontFamily" Value="teen bold.ttf#Teen" />
    <Setter Property="FontSize" Value="18" />
</Style>

The next task is to put a Template into the Style and design the basic layout of our button. Although this looks like a lot of code, all it is is a Grid which contains a Border which contains a ContentPresenter. We could actually leave off most the ContentPresenter's properties, as our button does not need that level of customisation (we are only using it in one place). The Border brushes are set using named SolidColorBrush objects so we can animate them later if we wish.

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="Button">
            <Grid>
                <Border BorderThickness="2" 
                        CornerRadius="4"
                        Padding="3"                                    
                        >
                    <Border.Background>
                        <SolidColorBrush x:Name="ButtonBackgroundBrush" Color="AliceBlue"/>
                    </Border.Background>
                    <Border.BorderBrush>
                        <SolidColorBrush x:Name="ButtonBorderBrush" Color="Black"/>
                    </Border.BorderBrush>
                    <ContentPresenter
                      Content="{TemplateBinding Content}"
                      ContentTemplate="{TemplateBinding ContentTemplate}"
                      HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                      Padding="{TemplateBinding Padding}"
                      TextAlignment="{TemplateBinding TextAlignment}"
                      TextDecorations="{TemplateBinding TextDecorations}"
                      TextWrapping="{TemplateBinding TextWrapping}"
                      VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                      Margin="4,2" />
                </Border>
            </Grid>
        </ControlTemplate>
    </Setter.Value>
</Setter>

Now the final task is to use the new VisualStateManager to tell our control what we want to happen on MouseOver. We will define one VisualStateGroup - the "CommonStates" group, which will contain two VisualStates and two VisualTransitions. The first VisualState is the Normal state, for which we don't need to do anything. The second VisualState is the MouseOver state, which contains a StoryBoard that animates the colour of the button's background. Notice that the duration is zero. This is a little confusing, but the duration will actually be set by the VisualTransitions. The VisualTransitions simply let us set the length of time over which we will change to the VisualStates. Here's our VisualStateGroups, which we have put inside the Grid XAML element:

<vsm:VisualStateManager.VisualStateGroups>
    <vsm:VisualStateGroup x:Name="CommonStates">

        <vsm:VisualStateGroup.Transitions>
            <vsm:VisualTransition To="MouseOver" Duration="0:0:0.2" />
            <vsm:VisualTransition To="Normal" Duration="0:0:0.2" />
        </vsm:VisualStateGroup.Transitions>

        <vsm:VisualState x:Name="Normal" />
        <vsm:VisualState x:Name="MouseOver">
            <Storyboard>
                <ColorAnimation 
                Storyboard.TargetName="ButtonBackgroundBrush"
                Storyboard.TargetProperty="Color"
                To="#C0C0C0"
                Duration="0" />
            </Storyboard>
        </vsm:VisualState>
    </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>

So templating controls is still perfectly possible in XAML, if you don't mind writing lots of it, but I guess that the intention now is for it all to be done in Expression Blend.

I'm hoping to find time over the next few weeks to update my series on styling a listbox to work with the new beta 2 templating model.

No comments: