Click here to Skip to main content
15,867,750 members
Articles / Desktop Programming / XAML

Developing a Custom Control for Silverlight 2.0

Rate me:
Please Sign up or sign in to vote.
4.73/5 (22 votes)
14 Apr 2008CPOL5 min read 177.5K   850   60   31
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:

C#
[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:

C#
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.

XML
<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.

XML
<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.

XML
<!-- 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:

XML
<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:

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Eyedea Ltd.
Hungary Hungary
I'm a former Microsoft Senior Consultant who established his own Company at the beginning of 2008. Our Company is focusing on .Net development, mainly Silverlight 3.0 and WPF projects.

I was awarded as ASP.Net MVP back in 2004.

Comments and Discussions

 
GeneralMergedDictionaries for Generic.xaml Pin
Jürgen Bäurle19-Dec-08 4:21
Jürgen Bäurle19-Dec-08 4:21 
GeneralCustom and User Controls in RC0 Pin
ykrylov29-Sep-08 9:39
ykrylov29-Sep-08 9:39 
GeneralStoryboard is not working Pin
kmd4-Sep-08 13:40
kmd4-Sep-08 13:40 
GeneralRe: Storyboard is not working Pin
Attila Hajdrik4-Sep-08 21:26
Attila Hajdrik4-Sep-08 21:26 
GeneralCorrection for Beta 2 Version: 2.0.30523.8 Pin
M_A_MaDeRo31-Jul-08 18:41
M_A_MaDeRo31-Jul-08 18:41 
General[Message Removed] Pin
nompel20-Sep-08 15:05
nompel20-Sep-08 15:05 
GeneralSystem.Windows.Controls.dll Pin
hitesh12323-Jun-08 20:46
hitesh12323-Jun-08 20:46 
GeneralWhere can I get more information about [assembly: System.Windows.Markup.XmlnsDefinition("http://schemas.eyedea.hu/silverlight/2008/xaml/media", "Eyedea.Controls")] Pin
MaciekMisztal20-May-08 11:19
MaciekMisztal20-May-08 11:19 
GeneralCan't assign .Click events ! Pin
MaciekMisztal16-May-08 3:37
MaciekMisztal16-May-08 3:37 
GeneralRe: Can't assign .Click events ! Pin
ppanedas17-May-08 14:35
ppanedas17-May-08 14:35 
GeneralRe: Can't assign .Click events ! Pin
Attila Hajdrik17-May-08 14:40
Attila Hajdrik17-May-08 14:40 
GeneralRe: Can't assign .Click events ! [modified] Pin
MaciekMisztal20-May-08 11:21
MaciekMisztal20-May-08 11:21 
GeneralYou should take a look at this website Pin
Yang Yu14-Apr-08 17:46
Yang Yu14-Apr-08 17:46 
GeneralVideo link broken Pin
User 27100914-Apr-08 7:05
User 27100914-Apr-08 7:05 
GeneralRe: Video link broken Pin
Attila Hajdrik14-Apr-08 11:02
Attila Hajdrik14-Apr-08 11:02 
GeneralRe: Video link broken Pin
User 27100915-Apr-08 2:42
User 27100915-Apr-08 2:42 
GeneralArticle too wide Pin
User 27100914-Apr-08 6:48
User 27100914-Apr-08 6:48 
GeneralRe: Article too wide Pin
Attila Hajdrik14-Apr-08 10:50
Attila Hajdrik14-Apr-08 10:50 
GeneralRe: Article too wide Pin
User 27100915-Apr-08 2:37
User 27100915-Apr-08 2:37 
QuestionWhy?? Pin
yassir hannoun23-Mar-08 10:11
yassir hannoun23-Mar-08 10:11 
AnswerRe: Why?? Pin
Attila Hajdrik24-Mar-08 0:51
Attila Hajdrik24-Mar-08 0:51 
GeneralNeed help Pin
Ihtesham23-Mar-08 0:56
Ihtesham23-Mar-08 0:56 
AnswerRe: Need help Pin
Attila Hajdrik23-Mar-08 7:19
Attila Hajdrik23-Mar-08 7:19 
GeneralInheritance Question Pin
Dewey21-Mar-08 23:31
Dewey21-Mar-08 23:31 
GeneralRe: Inheritance Question Pin
Attila Hajdrik23-Mar-08 7:17
Attila Hajdrik23-Mar-08 7:17 
Hi Dewey!

When I wrote the article I was in the middle of hunting a styling bug and ButtonBase.cs left there because of this.

I updated the zip file and removed the ButtonBase.cs

Sorry for your troubles Wink | ;-)

Attila

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.