Extending your Silverlight 4 Application with Controls
We could not only use and extend the controls present in the visual studio toolbox for Silverlight but also there are multiple providers offering additional controls for Silverlight framework.
Extending XAML
XAML stands for eXtensible Application Markup Language and hence it’s possible to add import external elements into a document without breaking rules.
Mapping a Prefix to CLR Namespace
Now we can define a set XML namespace (xmlns) in XML and can map a unique identifier to a prefix so that the XML parser can use additional rules while loading the document.
For Example
Lets say we want to add double value in the document resources. Now as you would know that XAML is by default configured for User Interface elements, the default XML namespaces will not map to Double types so we have to add the line of code shown in Bold in your XAML other than the normal code.
<UserControl x:Class="DoubleInResources.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sys="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <UserControl.Resources> <sys:Double x:Key="ButtonsWidth">200</sys:Double> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <Button Width="{StaticResource ButtonsWidth}" Height="{StaticResource ButtonsWidth}" Content="Click Me!!" /> </Grid> </UserControl>
So we are including the mscorlib namespace which contains the Double type definition and then referencing the same in UserControl.Resources and then we have referenced the same as a static resource.
Note
When we are working with XAML we encounter two types of namespaces
CLR Namespaces – Used in .NET code to group the classes logically.
XML Namespaces – Used to extend XML document with additional declarations.
Prefix is not always needed
Silverlight elements are defined into two namespaces. The first one is a Unique Resource Identifier (URI) mapped to the default xmlns (http://schemas.microsoft.com/winfx/2006/xaml/presentation).
In fact, multiple CLR namespaces (such as System.Windows.Controls, System.Windows.Shapes, and so on) are mapped to this URI. This allows us to use all the types within these namespaces without having to use a prefix. For example, we write <Button Click=”Button_Click” /> and not <anyPrefix:Button Click=”Button_Click” />. Note that this URI is not a website’s address, and entering it into a web browser will not lead you anywhere. It is just a Unique Resource Identifier, a unique name.
The other namespace used by Silverlight by default is http://schemas.microsoft.com/winfx/2006/xaml, another URI, which is mapped to the x prefix. Inside this namespace are defined additional properties that can be applied to any element.
How to add a namespace to any element
You can a namespace mapping to a control as well as shown below:
<Button xmlns:controls=”clr-namespace:MyApplication.Controls”>
<controls:MyControl />
</Button>
How to define your own CLR and Mapping CLR Namespaces
We can map our own URI to a group of namespaces and this is useful because we can consolidate multiple CLR namespaces into one single URI and also this would hide the CLR namespaces that our code is using. And later when we decide to move some classes to different CLR namespaces we don’t need to change the XAML code. This is also very useful in creating data objects and it also makes data binding easier.
For Example:
Add the following code to the AssemblyInfo.cs
[assembly: XmlnsDefinition("http://www.mycompany.com", "DoubleInResources")] [assembly: XmlnsDefinition("http://www.mycompany.com", "DoubleInResources.Controls")] [assembly: XmlnsDefinition("http://www.mycompany.com", "DoubleInResources.DataAccess")]
And then after building the solution we can reference it as shown below:
What is a Control?
I know you would say that you know what it is but lets start with a formal definition of Control. A Control is an element of software, encapsulating some functionality related to user interface. Now in Silverlight there are two kinds of controls User Control and Custom Control.
User Controls
A user control is a logical group of other controls. It is typically used to separate a user interface in smaller parts that are easier to code and design. In fact, in Silverlight, all the pages of an application are user controls.
The App class (defined in App.xaml and App.xaml.cs) is the main point of entry for the Silverlight application. This is also where the MainPage control is created and assigned.
The Application_Startup Event Handler in App.xaml.cs is as follows:
If you rename the MainPage control to a different name, you must also change the name in the RootVisual assignment, or else your application will not compile anymore.
Custom Controls
The custom controls are made of code only as against XAML (Front End) and a code behind file. All controls build in Silverlight are lookless. The custom control file defines only the controls functionality i.e. Properties and methods and its behavior is defined by its states and parts.
For the controls to be visible, a XAML front end must be defined, though. An invisible control is not very usable! One control can have multiple appearances, defined in as many control templates. We talk out a separation of concerns: The control’s code defines its functionality; the control’s template defines its appearance. Typically, a developer implements the control, whereas a designer styles and templates it.
Design a Custom Control
Let’s take the example of a Custom with the following functionality:
- The user defines a threshold and a value, both of type Double.
- If the value is higher than the threshold, the control is in High state.
- If the value is lower than the threshold, the control is in Low state.
- If the value is equal to the threshold, the control is in Equal state.
- Both the threshold and the value can change dynamically, be data bound, animated, and so forth.
- The user can click one part of the control to increment the value by one unit, and another part to decrement by one unit.
- The control can be disabled, in which case clicking does not change the value.
Now as you see we have the functionality of the custom control but not how the control will look so the developer can start working the designers can do the designing part simultaneously.
Let’s get started:
- In Visual Studio, select File, New, Project from the menu.
- In the Add New Project dialog, in the Silverlight category, select Silverlight Class Library.
- Enter the name CustomControlsLibrary and click OK. Make sure that you select Silverlight 4 in the next dialog. This creates an assembly, a library that can be referenced in multiple applications but cannot be run on its own.
- Delete the file Class1.cs in the Solution Explorer (because will not use it).
- Right-click the CustomControlsLibrary project in the Solution Explorer, and select Add, New Item from the context menu.
- In the Add New Item dialog, select the Silverlight, and then select a Silverlight Templated Control.
- Enter the name ThresholdControl.cs and click Add.
Defining the Parts and States
These steps create a C# code file, a folder named Themes, and a XAML file named
Generic.xaml. We will investigate this last file later; for now let’s declare the parts and
states for this control:
- Open the file ThresholdControl.cs.
- According to the requirements, the control has two state groups. We will call these the Common states (Normal, Disabled) and the Threshold states (High, Equal, Low). Note that the states within a state group are mutually exclusive; that is, the control cannot be simultaneously in Normal and in Disabled state. However, it can be Normal and High, or Normal and Low, and so forth. Defining the states and states groups is done with the TemplateVisualState attribute on the class.
- The requirements also state that the control has two parts with a special meaning: Clicking them increments or decrements the value. Here too, we use an attribute to define the parts on the class: the TemplatePart attribute.
The ThresholdControl.cs should look like this
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace CustomControlsLibrary { [TemplatePart(Name = "IncrementPart", Type = typeof(UIElement))] [TemplatePart(Name = "DecrementPart", Type = typeof(UIElement))] [TemplateVisualState(GroupName = "Common", Name = "Normal")] [TemplateVisualState(GroupName = "Common", Name = "Disabled")] [TemplateVisualState(GroupName = "Threshold", Name = "High")] [TemplateVisualState(GroupName = "Threshold", Name = "Equal")] [TemplateVisualState(GroupName = "Threshold", Name = "Low")] public class ThresholdControl : Control { public ThresholdControl() { this.DefaultStyleKey = typeof(ThresholdControl); } public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(ThresholdControl), new PropertyMetadata(0.0, OnValueChanged)); public double Threshold { get { return (double)GetValue(ThresholdProperty); } set { SetValue(ThresholdProperty, value); } } public static readonly DependencyProperty ThresholdProperty = DependencyProperty.Register("Threshold", typeof(double), typeof(ThresholdControl), new PropertyMetadata(0.0, OnValueChanged)); private static void OnValueChanged(object s, DependencyPropertyChangedEventArgs e) { var sender = s as ThresholdControl; if (sender != null) { sender.GoToThresholdState(true); } } private void GoToThresholdState(bool useTransitions) { if (Value > Threshold) { VisualStateManager.GoToState(this, "High", useTransitions); } else { if (Value < Threshold) { VisualStateManager.GoToState(this, "Low", useTransitions); } else { VisualStateManager.GoToState(this, "Equal", useTransitions); } } } public override void OnApplyTemplate() { base.OnApplyTemplate(); var incrementPart = GetTemplateChild("IncrementPart") as UIElement; if (incrementPart != null) { incrementPart.MouseLeftButtonDown += new MouseButtonEventHandler(IncremementPartMouseLeftButtonDown); } var decrementPart = GetTemplateChild("DecrementPart") as UIElement; if (decrementPart != null) { incrementPart.MouseLeftButtonDown += new MouseButtonEventHandler(DecremementPartMouseLeftButtonDown); } GoToThresholdState(false); } void IncremementPartMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Value++; } void DecremementPartMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Value--; } } }
And the Generic.xaml should look like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CustomControlsLibrary"> <Style TargetType="local:ThresholdControl"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:ThresholdControl"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="Threshold"> <VisualState x:Name="High"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="EqualTextBlock"> <EasingDoubleKeyFrame KeyTime="0" Value="0" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="LowTextBlock"> <EasingDoubleKeyFrame KeyTime="0" Value="0" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> <!--...--> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="30" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="30" /> </Grid.ColumnDefinitions> <Border Background="Blue" x:Name="DecrementPart" Cursor="Hand"> <TextBlock Text="-" /> </Border> <StackPanel Grid.Column="1" Orientation="Vertical" Margin="10"> <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Value}" /> <TextBlock x:Name="HighTextBlock" Text=">" /> <TextBlock x:Name="EqualTextBlock" Text="==" /> <TextBlock x:Name="LowTextBlock" Text="<" /> <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Threshold}" /> </StackPanel> <Border Background="Red" x:Name="IncrementPart" Grid.Column="2" Cursor="Hand"> <TextBlock Text="+" /> </Border> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
You can find the complete Source Code at http://min.us/mKwMNIhKq