Share via


Make a Styled Button in XAML for Universal Windows Apps

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto1.png

The built-in Windows app XAML controls are great, but sometimes you need something more stylish. XAML styles to the rescue!

Those two pink buttons have all the same properties, but one is the default style and the other is a custom style. They look completely different due to the flexibility of XAML styling. That quasi-3D masterpiece on the left even *depresses *when clicked. Let me show you how...

What is a XAML Style?

XAML controls have properties, such as the background colour or the border shape, set via the Properties pane. If you want more flexibility in your controls you can create a style, which lets you pick and choose what stock functionality and visual properties to keep, and which to customise.

What does this Article Cover?

In this article we'll cover the core concepts of XAML styling by building up the fancy 3D button above from scratch.

I assume you have some XAML and Windows Universal app experience. If you don't know the basics of creating Universal Windows apps I recommend building a small app before thinking about styling (you can start with my Hello World example).    

Skip to the end if you just want the raw code.

This technique works for any XAML-based Windows Store or Windows Phone app, including Universal Apps.

Step 1: Make Like Webster and Create a Dictionary

Create a new project, then create a new Resource Dictionary and call it MyButtonStyle:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto2.png

*A1 Right-click your project and select Add New Item...

*

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto3.png

*2 Select Resource Dictionary, name it, then click Add

*

You'll now have a file called MyButtonStyle.xaml in your project. Double-click the new file to open it in the Visual Studio code editor. All your style code will go in this file.

We'll start by defining the style's name and target type. The target is the kind of UI element the style can be applied to (a Button), and the name gives the style a unique identifier.

Add this code after the namespaces and before the closing </ResourceDictionary> tag:

<Style TargetType="Button" x:Key="MyButtonStyle">
</Style>

Save MyButtonStyle.xaml, then open App.xaml and add a reference to MyButtonStyle.xaml as a merged dictionary:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="MyButtonStyle.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Referencing the dictionary file in App.xaml ensures your style is available to any button within your app.

Step 2: Create a Button

Visual Studio doesn't allow visual editing of resource dictionary items, so we will create a button on a page, assign our new style, then use that button to help design the style.

Open MainPage.xaml and add a button control anywhere on the page.

Change this button's style to MyButtonStyle:

  1. Select the button control (either by clicking on it on the page designer or my clicking on its code in the code editor).

  2. In the Properties pane, scroll to the bottom and expand the Miscellaneous properties group, then click the small square next to Style:    

                                    

    http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto4.png

  3. Choose Local Resource | MyButtonStyle:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto5.png

You've now applied your style to the button (though your style is blank, so the button looks like a regular button).  Make the button a reasonable size and give it a text label in its Content property, something like this:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto6.png

Changes made to the style will be reflected in this button, but changes you make to this button do not affect the style - this button is an instance of a Button control styled with MyButtonStyle.

Step 3: Begin the Button Template

Back in MyButtonStyle.xaml, add the following code between the <Style> tags:    

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate>
</ControlTemplate>
</Setter.Value>
</Setter>

Setter defines a property. The above lines define the style's template, and the <Setter.Value> element is where you will place the actual template content. In this case we use <ControlTemplate> tags to create the control's layout. Save the file and you'll notice that the button on MainPage now appears blank because you've defined a control template, but you have not put anything in it.

Control Template

Our template will use grids and a TextBlock (for the label).

Paste this inside the <ControlTemplate> tags:

<Grid>
    <ContentPresenter>
        <TextBlock
            FontFamily="{TemplateBinding FontFamily}"
            SelectionHighlightColor="{TemplateBinding Foreground}"
            Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}"
            FontSize="{TemplateBinding FontSize}"
            Foreground="{TemplateBinding Foreground}"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Height="Auto"
            Width="Auto"/>
    </ContentPresenter>
</Grid>

Now your button style has a grid that contains a <ContentPresenter>.

A ContentPresenter presents content, which could be anything from text to a complex list of objects.

You may not be familiar with TemplateBindingTemplateBinding binds properties to values in the template, which means they are bound to properties in the Properties panel for a control instance. Therefore the FontFamily property binds to the selected font, and so on. The text binding is different because Content behaves differently to a standard property like Background or Height. That's outside the scope for this article though, so let's move on.

Step 4: Competing the Button Template

The button still looks like the out-of-box button, but that's OK. We're ready to jazz it up with some XAML trickery…

We will create the raised 3D effect by layering three identical shapes of different colours.

In XAML objects are drawn in the order they appear in the code, with items drawn later appearing on top of previously drawn items.

To create the shadow we will draw a button shape in the button's Background colour, then we'll draw an identical shape on top of it, but make it black and semi-transparent. This gives the effect of darkening the image drawn beneath it (imagine you're putting black cellophane over the first image). We'll then draw a third shape on top of the shadow but a little higher on the screen, and voila, a 3D button.

This is the grid that we'll use:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto7.png

The darkened layer is drawn on rows 2 & 3, and the top layer is drawn on rows 1 & 2. The order of the layers in the code is important because the transparent black layer must go in front of the bottom layer and the top layer must go on top of that. This gives us the following effect:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto8.png

We will create our grid rows with relative sizing so the button always stays in proportion. The top and bottom rows are '1*' high, and the middle row is '7*' high.

Replace the code within the <ControlTemplate> block with the following:

<Grid>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
                <RowDefinition Height="7*"/>
                <RowDefinition/>
             </Grid.RowDefinitions>
        <Grid x:Name="grid" Margin="0" Grid.RowSpan="2" Grid.Row="1">
            <Border
               BorderBrush="{TemplateBinding Background}"
               BorderThickness="1"
               CornerRadius="6"
               Background="{TemplateBinding Background}"/>
            <Border Background="Black" Opacity="0.25" BorderBrush="Black" CornerRadius="6"/>
         </Grid>
         <Grid Margin="0" Grid.Row="0" Grid.RowSpan="2">
             <Border
                  BorderBrush="{TemplateBinding Background}"
                  BorderThickness="1"
                  CornerRadius="6"
                  Background="{TemplateBinding Background}"/>
              <ContentPresenter>
                 <TextBlock
                      FontFamily="{TemplateBinding FontFamily}"
                      SelectionHighlightColor="{TemplateBinding Foreground}"
                      FontSize="{TemplateBinding FontSize}"
                      Foreground="{TemplateBinding Foreground}"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      Height="Auto"
                      Width="Auto"
                      Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
              </ContentPresenter>
        </Grid>
    </Grid>
</Grid>

Save the changes.

I'll point out things that may be unfamiliar:

  • The Border elements have a CornerRadius property, which gives the button rounded corners. Change the value to make the corners more or less rounded.
  • The black border must match the border it is darkening exactly otherwise the darkening effect won't match correctly.
  • You can change the black border's opacity for a darker or lighter shadow effect.

Check your Progress

Open MainPage.xaml. The button still looks plain. Select your button and change its properties to test its new style. Give it a background colour, a foreground (for the text), set the page's background colour, change the button's Content text and font. Your button should look raised and shaded like this:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto9.png

Beautiful!

Step 5: Making it Click

Now that your button looks great, run your app and click it. As you might have guessed, clicking the button doesn't do anything (because you haven't coded it to do anything); you'll also notice it doesn't feel like a button at all. A button should react when you click it!

We will use Blend to animate the button as it's a great tool for such things.

  • Right-click MainPage.xaml in the Visual Studio Solution Explorer and select Open in Blend... (save your file if Visual Studio prompts you).

Blend will open MainPage.xaml. From here you can directly edit the button's template (i.e. its style). In the left-hand pane (or on the button on the design surface), right-click the button and select Edit Template | Edit Current...

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto10.png

You are now directly editing your style template.

Select the States tab:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto11.png

 States determine how a control looks or moves under different conditions. For example, our button should look depressed when clicked.

Click the Add State Group button to create a group of states, then rename the group you've created to 'CommonStates':

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto12.png

Click the Add state button three times to create three states. Rename these three states to 'Normal', 'PointerOver', and 'Pressed':

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto13.png

Even though the PointerOver state is the same as normal we need to include it so that the button reverts to a normal state after clicking even if the mouse cursor is still over the button. You can remove this state to see the difference for yourself.

Select Pressed from the state list. You'll notice a red dot icon and a red border around the designer. This indicates that you are now 'recording' changes to the control. Any changes you make to the control in recording mode become the visual changes for the Pressed state.

Select the Grid item lowest on the tree (this is the button's face) in the control's tree on the left:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto14.png

We want to move this surface's position down to the next grid row. In the Properties pane on the right, change the Row value from 0 to 1 and press Enter to apply the change. You'll notice the button now appears 'pressed', with the top layer moving over the darker portion to simulate a 3D button press.

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto15.png

Click the small red dot to turn recording mode off:

http://grogansoft.com/blog/wp-content/uploads/2015/03/030415_1122_MakingButto16.png

Save all files (Ctrl-Shift-S), then go back to Visual Studio. Visual Studio will detect that you've made changes and prompt you to reload the file, so do that.

Now run your app again and the button will depress when clicked!

What Next?

Experiment with the button. You can apply your style to any button, and you can change the text font and size, use different colours, and so on. Notice when you change the button colour the shading at the bottom automatically becomes a darker shade of the main (Background) colour.

Here are some ways you can play around with this example:

  • Replace the button text with an image.
  • Reverse the 3D 'direction' of the button.
  • Add a slight tint to the button when the mouse is over it.
  • Try styling a different type of control.

 Full Code

Here's the full code for the button style, including the code created by Blend when creating visual states in the designer. Make sure you add a reference to this since it is a resource dictionary. It's best to reference this in App.xaml to ensure it is in scope for the whole app.

<ResourceDictionary
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
    xmlns:local="using:TestControls">
 
    <Style TargetType="Button" x:Key="MyButtonStyle">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="grid">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <x:Int32>1</x:Int32>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="PointerOver"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                                <RowDefinition Height="7*"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Grid Margin="0" Grid.RowSpan="2" Grid.Row="1">
                                <Border
                                        BorderBrush="{TemplateBinding Background}"
                                        BorderThickness="1"
                                        CornerRadius="6"
                                        Background="{TemplateBinding Background}"/>
                                <Border Background="Black" Opacity="0.25" BorderBrush="Black" CornerRadius="6"/>
                            </Grid>
                            <Grid x:Name="grid" Margin="0" Grid.Row="0" Grid.RowSpan="2">
                                <Border
                                    BorderBrush="{TemplateBinding Background}"
                                    BorderThickness="1"
                                    CornerRadius="6"
                                    Background="{TemplateBinding Background}"/>
                                <ContentPresenter>
                                    <TextBlock
                                       FontFamily="{TemplateBinding FontFamily}"
                                        SelectionHighlightColor="{TemplateBinding Foreground}"
                                        FontSize="{TemplateBinding FontSize}"
                                        Foreground="{TemplateBinding Foreground}"
                                        HorizontalAlignment="Center"
                                        VerticalAlignment="Center"
                                        Height="Auto"
                                        Width="Auto"
                                        Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                                </ContentPresenter>
                            </Grid>
                       </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>