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.
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.
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
".
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 StoryBoard
s for visual state transitions. The names of the visual elements and Storyboard
s 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.
<!---->
<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" />
<!---->
<Setter property="TextAlignment" Value="Left" />
<Setter property="TextWrapping" Value="NoWrap" />
<Setter property="FontSize" Value="11" />
<!---->
<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.
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.
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.
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