Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Developing a Custom Control for Silverlight 2.0

0.00/5 (No votes)
14 Apr 2008 2  
In this article, I show the key steps to develop a Silverlight 2.0 custom control
MediaButton Control

Introduction

The main focus of the article is to show you what steps you need to perform to build a custom control for Silverlight 2.0 and reuse that in your Silverlight Projects.

The article is based on Silverlight 2.0 Beta1 which was announced today at MIX08.

In the article, we will create a Control Library and implement our own Button class without any added functionality to the default implementation of the class, but with a different default style. By reusing this control, you'll don't have to add Style attributes to all Button instances in your application or composite controls.

Let's get started.

Creating the Project

Create a Silverlight Class Library Project in Visual Studio 2008.

Creating the Project

Because this will be a Control Library, we've to add a reference to System.Windows.Controls assembly. This assembly will have 1.0.0.0 as version number for Silverlight 2.0 Beta1 (don't ask why :-)) and will come from "C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Libraries\Client\System.Windows.Controls.dll" by default.

In the next step, we will add XmlnsDefinitionAttribute to our control Assembly.
This attribute helps the XAML processor to find and pair XML namespaces and CLR namespaces together. So add this attribute with these parameters:

[assembly: System.Windows.Markup.XmlnsDefinition 
 ("http://schemas.eyedea.hu/silverlight/2008/xaml/presentation", "Eyedea.Controls")]

Creating the Control Class

Rename Class1.cs to MediaButton.cs within Solution Explorer and enable Visual Studio to refactor the Class1 class's name references in the Project.

Next we'll add our XAML file which will hold the default styles for the controls in this Control Library.

Let's add a Text File type of item to the Project, named generic.xaml.

Adding generic.xaml

Select the generic.xaml in the Solution Explorer and set the properties to get this file embedded in a format we need: Resource.
You've to delete the value of the Custom Tool property and set Build Action to "Resource".

Set generic.xaml properties

It's time to edit our main target: MediaButton.cs so open it up.

Add a using statement for the System.Windows.Controls namespace and add inheritance to our MediaButton control as it has to inherit from the built-in Button class.

At this point, our MediaButton class will look like this:

using System.Windows.Controls;

namespace Eyedea.Controls
{
    public class MediaButton : Button
    {
            public MediaButton()
            {
            }
    }
}

Adding the Default Style to our Control

Open generic.xaml.

First, add the default content to the XAML file and a reference to our XML namespace.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Eyedea.Controls;assembly=Eyedea.Controls">
</ResourceDictionary>

Please note the local XML namespace prefix definition which will be used within the style to reference the types in this Control Library.

Add the Style tags to define the place for our style. Within the Style tag's TargetType property, we've to specify the target control. In our case, it's our MediaButton control. You also have to assign the TargetType for the ControlTemplate property as well.

<Style TargetType="local:MediaButton">
    <Setter property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MediaButton">
                <Grid x:Name="RootElement">
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Within a Style you can define the full appearance of a control including StoryBoards for visual state transitions. The names of the visual elements and Storyboards are very important because they are essential part of how the styling works in Silverlight 2.0, but this will be discussed in another article, since it's beyond the scope for now.

The current button design has a fixed width and height so we've to set them with property setters.
We're also specifying minimum and maximum sizes as well to "protect" the design of the button.

Tip: The simple property setters should be placed after the Style tag directly, before the Template property.

<!-- Common properties -->
<Setter property="IsEnabled" Value="true" />
<Setter property="IsTabStop" Value="true" />
<Setter property="Margin" Value="0" />
<Setter property="HorizontalContentAlignment" Value="Center" />
<Setter property="VerticalContentAlignment" Value="Center" />
<Setter property="Cursor" Value="Arrow" />
<Setter property="Foreground" Value="#CC808080" />
<!-- Text related properties -->
<Setter property="TextAlignment" Value="Left" />
<Setter property="TextWrapping" Value="NoWrap" />
<Setter property="FontSize" Value="11" />
<!-- Default Size Constraints -->
<Setter property="Width" Value="50" />
<Setter property="MinWidth" Value="50" />
<Setter property="MaxWidth" Value="50" />
<Setter property="Height" Value="22" />
<Setter property="MinHeight" Value="22" />
<Setter property="MaxHeight" Value="22" />

Add the visual elements to the template by copying the following XAML content of a style which was the result of our first Expression Design How-Do-I video.

The design contains a background rectangle, an outline one, and two highlights which will be animated during user interaction.
At the bottom, you can find a ContentPresenter element which is the placeholder for the button's Content property.

Add the following content inside the Grid tag:

<Grid.Resources>
    <Storyboard x:Key="MouseOver State">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
    Storyboard.TargetName="HighlightTop" 
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.3"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
    Storyboard.TargetName="HighlightBottom" 
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
    Storyboard.TargetName="Border"
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.7"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
    <Storyboard x:Key="Pressed State">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
    Storyboard.TargetName="HighlightTop" 
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
    Storyboard.TargetName="HighlightBottom" 
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.3"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
    Storyboard.TargetName="Border" 
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.5"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
    <Storyboard x:Key="Normal State" />
    <Storyboard x:Key="Disabled State">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
    Storyboard.TargetName="HighlightTop" 
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.1500000" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
    Storyboard.TargetName="HighlightBottom" 
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.1500000" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
    Storyboard.TargetName="ContentPresenter" 
    Storyboard.Targetproperty="(UIElement.Opacity)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.1500000" Value="0.7"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Grid.Resources>
<Rectangle Fill="#FF000000" Margin="2,2,2,2" RadiusX="1" RadiusY="1" Opacity="0.3"/>
<Rectangle x:Name="Border" Stroke="#FF808080" RadiusX="2" RadiusY="2" Opacity="0.3"/>
<Path x:Name="HighlightTop" Margin="2,2,2,11" Opacity="0.2" 
    Data="M0,1 C0,0.45 0.45,0 1,0 L45,0 C45.55,0 46,0.45 46,1 C46,1 46,9 46,9 
    C46,9 0,9 0,9 C0,9 0,1 0,1 z">
    <Path.Fill>
        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
            <GradientStop Color="#FFFFFFFF" Offset="0"/>
            <GradientStop Color="#FFE1E1E1" Offset="1"/>
        </LinearGradientBrush>
    </Path.Fill>
</Path>
<Path x:Name="HighlightBottom" Margin="2,11,2,2" Opacity="0" Data="M0,0 C0,0 31,0 
    46,0 C46,0 46,8 46,8 C46,8.55 45.55,9 45,9 L1,9 C0.45,9 0,8.55 0,8 C0,8 
    0,0 0,0 z">
    <Path.Fill>
        <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
            <GradientStop Color="#FFD6D6D6" Offset="0"/>
            <GradientStop Color="#FFFFFFFF" Offset="1"/>
        </LinearGradientBrush>
    </Path.Fill>
</Path>
<ContentPresenter x:Name="ContentPresenter"
    Content="{TemplateBinding Content}"
    ContentTemplate="{TemplateBinding ContentTemplate}"
    FontFamily="{TemplateBinding FontFamily}"
    FontSize="{TemplateBinding FontSize}"
    FontStretch="{TemplateBinding FontStretch}"
    FontStyle="{TemplateBinding FontStyle}"
    FontWeight="{TemplateBinding FontWeight}"
    Foreground="{TemplateBinding Foreground}"
    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
    Padding="{TemplateBinding Padding}"
    TextAlignment="{TemplateBinding TextAlignment}"
    TextDecorations="{TemplateBinding TextDecorations}"
    TextWrapping="{TemplateBinding TextWrapping}"
    VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
/>

Build the Project. If everything goes fine, at this point, you've a working Custom Control Library.

Testing the Control

To test this control, we have to create a Silverlight Application Project. In Solution Explorer, right click the Solution's node and add a new Project to the Solution of type Silverlight Application.

Adding Test Project

For Silverlight Application, Visual Studio asks us about the testing method we like to use for this Silverlight Application. For now, a test HTML page will be fine for us.

Test Project type

For real world scenarios like mashups, a Web Application is needed since the access of external resources are different for Silverlight Applications which runs from the FileSystem or for applications running from a WebServer.

Mark the TestApplication Project as our StartUp Project by right clicking the Project's node and selecting "Set as StartUp Project".

Adding our Control to the Test Project

To use the control in our TestApplication, we've to add a reference to the Control Library Project.

Adding reference to Test Project

Open up Page.xaml in the designer and switch to XAML view. To use the MediaButton control within the page, we've to add a XML namespace definition to the UserControl tag.

The test page will contain a 4 by 3 Grid and 2 MediaButton instances with "Play" and "Stop" content.

Overwrite the content of Page.xaml with the following block:

<UserControl x:Class="TestApplication.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:eyedea="clr-namespace:Eyedea.Controls;assembly=Eyedea.Controls"
    Width="320" Height="240">
        <Grid x:Name="LayoutRoot" Background="Black" Margin="50,50,50,50">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.RenderTransform>
                <ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="2" ScaleY="2" />
            </Grid.RenderTransform>
            <Rectangle Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1" 
        Stroke="#FF808080" RadiusX="2" RadiusY="2" Opacity="0.3"/>
            <eyedea:MediaButton Grid.Column="1" Grid.Row="1" Margin="2,2,2,2" 
        Content="Play">
            </eyedea:MediaButton>
            <eyedea:MediaButton Grid.Column="2" Grid.Row="1" Margin="2,2,2,2" 
        Content="Stop">
            </eyedea:MediaButton>
        </Grid>
</UserControl>

Hit F5 and try out the control. For Silverlight 2.0, keyboard support is much better than it was in Silverlight 1.0. Now you get full Tabbing support out of the box. Try this out by pressing Tab several times to get into the Silverlight content from the browser.

What's Next

  • I'm planning to write an article where I will build up a skinnable control from ground up based on the knowledge in this article.
  • And a lot more for Silverlight 2.0 ;-)

Points of Interest

History

  • 03/05/2008 - Original submission
  • 03/06/2008 - Added English Video
  • 03/23/2008 - Updated source archive - removed ButtonBase.cs

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here