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:

 

  1. In Visual Studio, select File, New, Project from the menu.
  2. In the Add New Project dialog, in the Silverlight category, select Silverlight Class Library.
  3. 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.
  4. Delete the file Class1.cs in the Solution Explorer (because will not use it).
  5. Right-click the CustomControlsLibrary project in the Solution Explorer, and select Add, New Item from the context menu.
  6. In the Add New Item dialog, select the Silverlight, and then select a Silverlight Templated Control.
  7. 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:

  1. Open the file ThresholdControl.cs.
  2. 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.
  3. 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="&gt;" />

<TextBlock x:Name="EqualTextBlock"

Text="==" />

<TextBlock x:Name="LowTextBlock"

Text="&lt;" />

<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

 

What is a resource (in terms on .net)?

A resource is a noncode piece of an application or compnent like bitmaps, fonts, audio / video files, string tables.

WPF supports 2 different types of resources –

Binary Resource

Binary resources are what the rest of the .NET framework conciders as resources and in WPF apps they are traditional items like bitmaps. You might also be surprised to know that XAML is also stored as a binary resource behiend the scenes. Binary resources are packaged in three different ways:

  • Embedded inside an assembly
  • Loose files that are known to the application at complie time
  • Loose files that are not known to the applcation to the compile time.

Now we can also categorize binary resources into Localizable (change depending on the current culture) and Non Localizable (do not change based on culture).

Lets see how can we define access and localize birary resources.

Defining a Binary Resource

As shown in the image below, you select the Build Action property and set it to either Resource or Content.

Define Resource Prop in VS

Define Resource Prop in VS

Select Build Action as Resource when you want to include your resource in the assembly and Select Build Action as Content when you want your resourcfe to be a loose file and only the existence and relative location of the file sould be included in the assembly.

Now if you would have noticed the above image carefully (you can have a look again if you want) then you will see there is another value by the name of Embedded Resource (this also embeds the resource into the assembly just like Resource). The use of this value is not recomended in WPF as this property belongs to old .net and was ment to be used with Windows Forms. Also if you select your resource to an Embedded Resource then you will not be able to access it in XAML (unless you write some custom code for the same).

[highlight]Note – As a rule of thumb you should embed the resource (with the Resource build action) only when the resouce is localizable or you specificly need single binary or resource within binary else you should prefer Content build action.[/highlight]

Accessing a Binary Resource

In WPF you can access a binary resource from code ox XAML through a URI (uniform resource identifier). And a types converter will help us to specify URIs in XAML as simple strings. The code below demonstrates how simple we can access resources whose build action property has been set to either Resource or Content but they have been included in the project

<Image Height=21Source=slideshow.gif/>

Note – A compiled XAML cannot reference a binary resource in the current  directory via its simple filename unless it has been added to the project. For  accessing files that are not added into the project either give the full path of the resource or access resource using Site of Origin url.

<Image Height=21Source=C:\\Users\\Adam\\Documents\\slideshow.gif/>

<Image Height=21Source=pack://siteOfOrigin:,,,/slideshow.gif/>

Below here is the list present for refernce of how to acces which binary resource.

Resource Type URI Mapping

Resource Type URI Mapping

 

Accessing Resources from Procedural Code

If you want to access resources from your C# or vb.net code the XAML specific shortcuts will not work but the URIO should be fully qualified. Below is an example for the same:

Image image = new Image();

image.Source = new BitmapImage(new Uri(“pack://application:,,,/logo.jpg”));

So the above lines of code will instantiate a System.Windows.Media.Imaging.BitmapImage object which ultimately derives from the abstract ImageSource type and this will work with popular image formats such as JPEG, PNG, GIF, BMP. The use of pack://application:,,,/ works only with resources belonging to the current project marked as Resource or Content. To reference relative loose files with no relation to the project, the easiest approach is to use a siteOfOrigin-based URI.

Localizing a Binary Resource

If your application contains some binary resources that are specific to certain cultures then we can partition them into satellite assemblies (one for each culture) and get them loaded automatically when appropriate. And if you are having binary resources for localization then there is a very good chance that you have string in your UI for localization. Here we could make use of LocBaml (sample tool in windows SDK) which will make it very easy to manage localization of strings and other items without removing them from XAML and manually apply a level of indirection. Lets have a look as to how can we use LocBaml and Satellite Assemblies. Remember this is just an overview on how you can proceed with it. To specify a default culture for resources and automatically build an appropriate satellite assembly, you must add a UICulture element to the project file. Visual Studio doesn’t have a means to set this within its environment, so you can open the project file in your favorite text editor instead. The UICulture element should be added under any or all  PropertyGroup elements corresponding to the build configurations you want to affect (Debug, Release, and so on), or to a property group unrelated to build configuration so it automatically applies to all of them. This setting should look as follows for a default culture of American English:

<Project …>

<PropertyGroup>

<UICulture>en-US</UICulture>

……

 

If you rebuild your project with this setting in place, you’ll find an en-US folder alongside your assembly, containing the satellite assembly named  assemblyName.resources.dll. You should also mark your assembly with the assembly-level NeutralResourcesLanguage custom attribute with a value matching your default UICulture setting, as follows:

[assembly: NeutralResourcesLanguage(“en-US”, UltimateResourceFallbackLocation.Satellite)]

The next step is to apply a Uid directive from the XAML language namespace (x:Uid) to every object element that needs to be localized. The value of each directive should be a unique identifier. This would be extremely tedious to do by hand, but it fortunately can be done automatically by invoking MSBuild from a command prompt, as follows:

msbuild/t:updateuid ProjectName.csproj

Running this gives every object element in every XAML file in the project an x:Uid directive with a unique value. You can add this MSBuild task inside your project before the Build task, although this might produce too much noise if you rebuild often.

After compiling a project that has been enhanced with Uids, you can run the LocBaml tool from the Windows SDK on a .resources file generated by the build process (found in the obj\debug directory), as follows:

LocBaml /parseProjectName.g.en-US.resources /out:en-US.csv

This generates a simple .csv text file containing all the property values you should need to localize. You can edit the contents of this file so it correctly corresponds to a new culture. (There’s no magic in this part of localization!) If you save the file, you can then use LocBaml in the reverse direction to generate a new satellite assembly from the .csv file!

For example, if you changed the contents of the .csv file to match the French Canadian culture, you could save the file as fr-CA.csv and then run LocBaml as follows:

LocBaml /generate ProjectName.resources.dll /trans:fr-CA.csv /cul:fr-CA

This new satellite assembly needs to be copied to a folder alongside the main assembly with a name that matches the culture (fr-CA in this case).

To test a different culture, you can set System.Threading.Thread.CurrentThread.CurrentUICulture (and System.Threading.Thread.CurrentThread.CurrentCulture) to an instance of the desired CultureInfo.

Logical Resource

As you might have gussed logical resources are the ones which are arbitary .NET objects stored in an element’s resources property (meant to be shared by multiple child elements). The base clases of both FrameworkElement and FrameworkContentElement have a Resource Property. The examples of logical resources are
styles,data provides, etc.

Lets see an XAML code for a logical resource which is Brush. The following example shows a simple windows with a row of buttons along the bottom which can be used in a photo gallery user interface. This example shows how to apply custom brush to each Button’s Background and BorderBrush.

<Window xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation Title =”Simple Window” Background=”Yellow”>

    <DockPanel>

        <StackPanel DockPanel.Dock=”Bottom” Orientation=”Horizontal” HorizontalAlignment=”Center”>

            <Button Background=”Yellow” BorderBrush=”Red” Margin=”5″>

                <Image Height=”21″ Source=”zoom.gif”/>

            </Button>

            <Button Background=”Yellow” BorderBrush=”Red” Margin=”5″>

                <Image Height=”21″ Source=”defaultThumbnailSize.gif”/>

            </Button>

            <Button Background=”Yellow” BorderBrush=”Red” Margin=”5″>

                <Image Height=”21″ Source=”previous.gif”/>

            </Button>

            <Button Background=”Yellow” BorderBrush=”Red” Margin=”5″>

                <Image Height=”21″ Source=”slideshow.gif”/>

            </Button>

            <Button Background=”Yellow” BorderBrush=”Red” Margin=”5″>

                <Image Height=”21″ Source=”next.gif”/>

            </Button>

            <Button Background=”Yellow” BorderBrush=”Red” Margin=”5″>

                <Image Height=”21″ Source=”counterclockwise.gif”/>

            </Button>

            <Button Background=”Yellow” BorderBrush=”Red” Margin=”5″>

                <Image Height=”21″ Source=”clockwise.gif”/>

            </Button>

            <Button Background=”Yellow” BorderBrush=”Red” Margin=”5″>

                <Image Height=”21″ Source=”delete.gif”/>

            </Button>

        </StackPanel>

        <ListBox/>

    </DockPanel>

</Window>

Applying Custom Color Brushes Without Using Logical Resources

Applying Custom Color Brushes Without Using Logical Resources

Now lets implement Logical Resources

<Window xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:x=”http://schemas.microsoft.com/winfx/2006/xamlTitle=”Simple Window”>

<Window.Resources>

<LinearGradientBrush x:Key=”backgroundBrush” StartPoint=”0,0″ EndPoint=”1,1″>

<GradientStop Color=”Blue” Offset=”0″/>

<GradientStop Color=”White” Offset=”0.5″/>

<GradientStop Color=”Red” Offset=”1″/>

</LinearGradientBrush>

<SolidColorBrush x:Key=”borderBrush”>Red</SolidColorBrush>

</Window.Resources>

<Window.Background>

<StaticResource ResourceKey=”backgroundBrush”/>

</Window.Background>

<DockPanel>

<StackPanel DockPanel.Dock=”Bottom” Orientation=”Horizontal” HorizontalAlignment =”Center”>

<Button Background=”{StaticResource backgroundBrush}” BorderBrush=”{StaticResource borderBrush}” Margin=”5″>

<Image Height=”21″ Source=”zoom.gif”/>

</Button>

<Button Background=”{StaticResource backgroundBrush}” BorderBrush=”{StaticResource borderBrush}” Margin=”5″>

<Image Height=”21″ Source=”defaultThumbnailSize.gif”/>

</Button>

<Button Background=”{StaticResource backgroundBrush}” BorderBrush=”{StaticResource borderBrush}” Margin=”5″>

<Image Height=”21″ Source=”previous.gif”/>

</Button>

<Button Background=”{StaticResource backgroundBrush}” BorderBrush=”{StaticResource borderBrush}” Margin=”5″>

<Image Height=”21″ Source=”slideshow.gif”/>

</Button>

<Button Background=”{StaticResource backgroundBrush}” BorderBrush=”{StaticResource borderBrush}” Margin=”5″>

<Image Height=”21″ Source=”next.gif”/>

</Button>

<Button Background=”{StaticResource backgroundBrush}” BorderBrush=”{StaticResource borderBrush}” Margin=”5″>

<Image Height=”21″ Source=”counterclockwise.gif”/>

</Button>

<Button Background=”{StaticResource backgroundBrush}” BorderBrush =”{StaticResource borderBrush}” Margin=”5″>

<Image Height=”21″ Source=”clockwise.gif”/>

</Button>

<Button Background=”{StaticResource backgroundBrush}” BorderBrush =”{StaticResource borderBrush}” Margin=”5″>

<Image Height=”21″ Source=”delete.gif”/>

</Button>

</StackPanel>

<ListBox/>

</DockPanel>

</Window>

 

Consolidating Color Brushes with Logical Resources

Consolidating Color Brushes with Logical Resources