Wrox Home  
Search


Excerpt from Professional Silverlight 2 for ASP.NET Professionals

Customizing with Skins

Sometimes you want to customize your controls beyond changing properties or just applying different styles, for this reason, WPF and Silverlight allows you to do control skinning, sometimes called templating. This technique is also used on the default templates that you can find on your common controls.

In Silverlight 2 the skinning process is achieved using the ControlTemplate control, creating a completely new look and feel without changing the code behind. The template can also be stored as a resource or created dynamically using the code behind as the Control object has the public dependency property called Template.

Figure 11-9 shows you how you can transform the default button into a completely new layout; here's the code involved:

<ControlTemplate x:Key="Customized" TargetType="Button" >
         <Grid x:Name="LayoutRoot">
                <Ellipse Height="43.555" HorizontalAlignment="Left"                
                        VerticalAlignment="Top" Width="99.556" Stroke="#FF22FFDC">
                        <Ellipse.Fill>
                               <LinearGradientBrush EndPoint="0.5,1" 
                                                            StartPoint="0.5,0">
                                       <GradientStop Color="#FFFFFFFF"/>
                                       <GradientStop Color="#FF5ABBFF" Offset="1"/>
                               </LinearGradientBrush>
                        </Ellipse.Fill>
                </Ellipse>
         </Grid>
</ControlTemplate>

The control template object is very simple to create and manipulate, the important information is the target type, which has the same functionality as the style target type. With that information plus the key name for our resource dictionary we are ready to use the template. The content of the template is the visual model that will represent the template; in this case you are giving the button an elliptical look with a gradient background. Now it is time to consume the template.

<Button Template="{StaticResource Customized}" 
         Foreground="Chocolate" Height="45" 
         HorizontalAlignment="Right" Margin="0,30,25,0" VerticalAlignment="Top" 
         Width="130" x:Name="cmdCustomizedButton"/>

The button now will divert the rendering to the content of the template. Remember that you can also assign the template via code behind as it shows the next code snipped. One very interesting feature is that you can change the template on the fly as many times as you want.

Private ControlTemplate previousControl;


if (previousControl == null)
{
         previousControl = cmdDefaultButton.Template;
         cmdDefaultButton.Template = (ControlTemplate)Resources["Customized"];
}
else
{
         cmdDefaultButton.Template = previousControl;
         previousControl = null;
}

Using skins to customize your control still allows you to set the properties of the control but you will notice that they may not have the same desired effect. If for example you change the background color of the new button you will notice that it does not change as the control does not know what to do with the value of that property as it may not fit the new template. But what if you still want that functionality? You can link it using TemplateBinding, which will allow you to copy the values from the source properties into your new template schema. You are going to change the original example using the template binding as it is shown in Figure 11-12.

fg1112

Figure 11-12

To change the foreground color of the text on the button we can still use the foreground color property that the designer is used to, it is considered best practice to allow this as is the expected behavior. The code now looks like this:

<ControlTemplate x:Key="Customized" TargetType="Button">
         <Grid x:Name="LayoutRoot">
                <Ellipse Height="43.555" HorizontalAlignment="Left" 
                        VerticalAlignment="Top" Width="99.556" Stroke="#FF22FFDC">
                        <Ellipse.Fill>
                               <LinearGradientBrush EndPoint="0.5,1" 
                                                            StartPoint="0.5,0">
                                      <GradientStop Color="#FFFFFFFF"/>
                                      <GradientStop Color="#FF5ABBFF" Offset="1"/>
                               </LinearGradientBrush>
                        </Ellipse.Fill>
                </Ellipse>
                <TextBlock Text="Click Me!" 
                        Foreground="{TemplateBinding Foreground}" 
                        HorizontalAlignment="Left" Margin="15,10,0,0" Width="73" 
                        FontSize="16" FontFamily="Comic Sans MS" 
                        VerticalAlignment="Top" Height="18"/>
         </Grid>
 </ControlTemplate>

Now, as you can see you are hard coding the text for the button, if you use TemplateBinding on the text you must be sure that the content is supported, in other words, a string. You can change the example to use the template binding as follows.

<TextBlock Text="{TemplateBinding Content}" 
         Foreground="{TemplateBinding Foreground}" 
         HorizontalAlignment="Left" Margin="15,10,0,0" Width="73" 
         FontSize="16" FontFamily="Comic Sans MS" 
         VerticalAlignment="Top" Height="18"/>

.But if you are looking for an extendable solution you need to allow the designer to add any type of content. . The content of the control can be another control, or a hierarchy of multiple controls. This poses a problem when the text is expecting a string! If you want to add the content you will need a ContentPresenter control.

The control presenter will do what it says on the label, it will allow you to render content within your template using the template binding command. Use the following code to incorporate this feature:

<ContentPresenter Content="{TemplateBinding Content}" 
                HorizontalAlignment="Center" Margin="0,0,0,0" 
                Width="73"                VerticalAlignment="Center" Height="25"/>

Note how you can extend the property binding to the font objects, which is not as limited as the styling. When you alter the source control with the following changes you have a fully customized button control as it is shown in Figure 1-13.

<Button Template="{StaticResource Customized}" 
        Content="Login" FontSize="16" 
        Foreground="White" Height="45" HorizontalAlignment="Right" 
        Margin="0,30,25,0" VerticalAlignment="Top" Width="130" 
        x:Name="cmdCustomizedButton"/>

fg1113

Figure 11-13

Putting Everything Together

Now that you understand how you can customize the look and feel of the control you are going to use all these techniques to change the appearance of a control by mixing the styling and skinning. Combining the techniques you are going to create a fully flexible control that can be reused in multiple applications without limiting the designers.

If you mix the style and the skin you can have a control that applies a default style and template, allowing further changes if necessary. The following code defines the default behavior of the button and the default template.

<UserControl x:Class="Chapter11.Controls.StyleAndSkinDemo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    FontFamily="Trebuchet MS" FontSize="11"
    Width="400" Height="300" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d">


<UserControl.Resources>
        <Style x:Name="FunkyButton" TargetType="Button">
                <Setter Property="Content" Value="No name"/>
                <Setter Property="Background">       
                        <Setter.Value>
                               <LinearGradientBrush EndPoint="0.5,1" 
                                                             StartPoint="0.5,0">
                                       <GradientStop Color="#FFFFFFFF"/>
                                       <GradientStop Color="#FF3094E8" 
                                                             Offset="0.513"/>
                                       <GradientStop Color="#FFFFFFFF" 
                                                             Offset="0.987"/>
                               </LinearGradientBrush>
                        </Setter.Value>
                </Setter>
                <Setter Property="IsTabStop" Value="true"/>
                <Setter Property="Template">
                        <Setter.Value>
                               <ControlTemplate TargetType="Button">


                <!—- Control Template -->
                <Grid x:Name="LayoutRoot" Background="White">
                        <Border Background="{TemplateBinding Background}" 
                               HorizontalAlignment="Stretch" 
                               VerticalAlignment="Stretch" 
                               CornerRadius="10,10,10,10" 
                               BorderThickness="2,2,1,1">
                               <Border.BorderBrush>
                                       <LinearGradientBrush EndPoint="0.5,1" 
                                                             StartPoint="0.5,0">
                                       <GradientStop Color="#FFBECDE8" 
                                                             Offset="0.004"/>
                                       <GradientStop Color="#FF1264F5" Offset="1"/>
                                       </LinearGradientBrush>
                               </Border.BorderBrush>
                        </Border>
                        <TextBlock HorizontalAlignment="Center" 
                                       VerticalAlignment="Center" 
                                       FontFamily="Lucida Sans Unicode" 
                                       FontSize="{TemplateBinding FontSize}"
                                       Text="{TemplateBinding Content}" 
                                       TextAlignment="Center" 
                                       TextWrapping="Wrap"/>
                        </Grid>
                        </ControlTemplate>
                </Setter.Value>
                </Setter>
        </Style>
</UserControl.Resources>
 
 <!—- Presentation -->
 <Grid>
        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
                <Button Width="200" Height="50" 
                        Content="Silverlight Default Style" FontSize="14"/>
                <Button Style="{StaticResource FunkyButton}" Width="200" 
                        Height="50" FontSize="14"/>
                <Button Style="{StaticResource FunkyButton}" Background="White" 
                        Width="200" Height="50" Content="My customized version" 
                        FontSize="14"/>
        </StackPanel>
 </Grid>
</UserControl>

fg1114

Figure 11-14

Visual States

So far you have seen how you can customize the visual aspects of the control but after running the examples and playing with them you may have noticed that the default styles includes different visual styles depending on the state of the button. Indeed, what if you want to trigger animations? For this reason Silverlight includes the visual states that you can define for your controls.

Each of the target controls that you are using publishes a list of visual states that you can consume. This means that you can override how the control will look when there is a state transition without altering how this transition is calculated. For example, in the button example, you can see the visual states published:

[TemplateVisualStateAttribute(Name = "Unfocused", GroupName = "FocusStates")]
[TemplateVisualStateAttribute(Name = "MouseOver", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "Pressed", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "Focused", GroupName = "FocusStates")]
[TemplateVisualStateAttribute(Name = "Disabled", GroupName = "CommonStates")]
[TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")]
public class Button : ButtonBase

It is important to note the attribute name and the group, because they can be used to override the visual changes when you customize the control.

To understand how the visual states are managed you first need to look into the VisualStateManager. This object will help you organize the visual states and transitions, adding the necessary storyboards that will occur during the state changes. The visual state manager is included in the System.Windows namespace, therefore the first thing that you need is to add the namespace to your control:

xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"

With this namespace you can start declaring the visual states on the control template. The first definition is the visual state groups container, VisualStateManager.VisualStateGroups, that will contain all the visual groups. If you review the list of states exposed on the button control you will notice that there is a group name; this name is the one defined by the individual group object called VisualStateGroup. The following example shows how you can add a color transition animation when the mouse hovers over the new customized button:

<VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="MouseOver">
                       <Storyboard>
                               <ColorAnimation Storyboard.TargetName="MainBorder" 
                               Storyboard.TargetProperty = 
                               "(Border.BorderBrush).(SolidColorBrush.Color)" 
                               To="Black"/>
                       </Storyboard>
                </VisualState>
        </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

In this case, when the target button triggers the state change to MouseOver the animation will be triggered changing the border color to black, again, without you having to change a single line in the code behind.

You can add a state transition storyboard if you want to trigger a visual change while the control is transitioning from one state to the other one. The object responsible for grouping the transitions is VisualStateGroup.Transitions. Each transition is represented by a VisualTransition object that allows you to define the states to monitor and the duration of the intervention. The following code shows how you can add an extra color transition while the control is moving from the MouseOver state to the Normal state:

<VisualStateGroup.Transitions>
        <VisualTransition From="MouseOver" To="Normal"  GeneratedDuration="0:0:1.5">
                <Storyboard>
                        <ColorAnimation Storyboard.TargetName="MainBorder" 
                        Storyboard.TargetProperty =          
                        "(Border.BorderBrush).(SolidColorBrush.Color)" To="Red"/>
                </Storyboard>
        </VisualTransition>                                 
</VisualStateGroup.Transitions>

Blend 2.5 also offers visual state management functionality to make it easier to design and visualize the changes using the user interface. Figure 11-15 shows how you can change the visual state.

fg1115

Figure 11-15

Blend lists the main states inherit from the base control and allows you to graphically add new visual state groups, visual states, and the transitions between them. This really helps you to reduce the amount of plumbing code that you need to write, as this generates all the XAML for you.

Now, you can alter the original example to add a visual state change when the user moves the mouse over your new customized button. The code shows you how you can integrate the visual states within your project:

<ControlTemplate TargetType="Button" >
         <Grid x:Name="LayoutRoot" Background="White">
         <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CommonStates">
                        <VisualStateGroup.Transitions>
                               <VisualTransition From="MouseOver" 
                                       To="Normal"  GeneratedDuration="0:0:1.5">
                                       <Storyboard>
                                              <ColorAnimation 
                                              Storyboard.TargetName="MainBorder" 
                                              Storyboard.TargetProperty =
                               "(Border.BorderBrush).(SolidColorBrush.Color)" 
                                              To="Red"/>
                                       </Storyboard>
                               </VisualTransition>
                        </VisualStateGroup.Transitions>
                
                        <VisualState x:Name="MouseOver">
                               <Storyboard>
                                       <ColorAnimation 
                                              Storyboard.TargetName="MainBorder" 
                                              Storyboard.TargetProperty = 
                               "(Border.BorderBrush).(SolidColorBrush.Color)" 
                                              To="Black"/>
                               </Storyboard>
                        </VisualState>
                        <VisualState x:Name="Normal">
                               <Storyboard>
                                       <ColorAnimation 
                                              Storyboard.TargetName="MainBorder" 
                                              Storyboard.TargetProperty = 
                               "(Border.BorderBrush).(SolidColorBrush.Color)" 
                                              To="#FFBECDE8"/>
                               </Storyboard>
                        </VisualState>
                </VisualStateGroup>
         </VisualStateManager.VisualStateGroups>
 
         <Border x:Name="MainBorder" Background="{TemplateBinding Background}" 
                HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                CornerRadius="10,10,10,10" BorderThickness="2,2,1,1">
                <Border.BorderBrush>
                        <SolidColorBrush Color="#FFBECDE8"/>
                </Border.BorderBrush>
         </Border>
         
         <TextBlock x:Name="MainContent" 
                        HorizontalAlignment="Center" 
                        VerticalAlignment="Center" 
                        FontFamily="Lucida Sans Unicode" 
                        FontSize="{TemplateBinding FontSize}"
                        Text="{TemplateBinding Content}" 
                        TextAlignment="Center" 
                        TextWrapping="Wrap"/>
         </Grid>
</ControlTemplate>

Custom Controls

The custom controls section is the last part of this chapter, the main reason why we are introducing this topic here is because it uses all the features that you learned about in this chapter. If you have been following the examples and the code you may have noticed that in certain scenarios the default behavior of a control is not good enough. At times grouping them in user controls or changing how they look is not enough.

The Silverlight team adopted the WPF model of free customization completely open to developers. This means that there are no complex or undocumented models because the same techniques that the Silverlight team used to create the default toolbox can be used by developers and designers. You will discover how much easier is to develop fully customized controls using Silverlight 2 than with ASP.NET.

What is a Custom Control?

. Imagine that you are part of the Silverlight team and your first task is to create the text box control. You have learned all the previously discussed techniques around user controls and visual customization but you may struggle to implement them without the basic controls. This is the place to start coding a full control from scratch; this means that you will be responsible for defining the functionality and how the control will look, always considering the extensibility model that WPF and Silverlight brings to the scene.

This means that now you need to code all the control logic based on the Control base class instead of the UserControl as presented so far in this chapter. Figure 11-16 is a visual representation of the control logic and presentation up to this point.

fg1116

Figure 11-16

Figure 11-16 shows that now you own the control logic and the default style. When the designer implements your control he should be able to override the visuals and still interact with the control logic and the state transitions. It seems like a lot of work but trust us, you will see how the internal architecture allows you to customize it with minimal pain.

Because the custom control will not be included by default on the designer's toolbox it is important to distribute the control and add it to the project. This leads you to the best practice of grouping all the custom controls in different assemblies so you can reuse them in several projects. The assembly will be incorporated into the XAP package and delivered to the final users with all the necessary resources.

Note that as you are building a custom control you can use other framework elements in your customization. For example the Calendar control uses text blocks and buttons and is a fully customized control.

Scenarios

Deciding when to create a custom control is not easy; it will have a real impact in your project because you will need to dedicate effort to designing the control logic and appearance. At the same time you need to think about what should be further customizable. The more options you offer, the more chances you create for something to go wrong.

One of the main scenarios where we suggest using custom controls is when you really need functionality that requires custom logic that can be easily redeployed and used in other projects, for example a thumbnail photo viewer. Such a control should not exist on the current toolbox and there is a need for visual customization. With all these variables in place the first scenario that can be addressed is when your business is selling custom controls. There are many companies that today are taking advantage of the lack of even basic controls in Silverlight and producing them in mass. You can see today that controls like combo boxes, visual sliders, and complex grids are not present by default, but they are already available for purchase. This leads us to another recommendation: if the control already exists, buy it. Trust us, controls are not very expensive and a company already went through the pain of customizing it, testing it and supporting it. This can really reduce the risk in your project.

Check to see if your requirement can be fulfilled with a simple user control. When evaluating a control, the rule of thumb is if the control requires a lot of complexity and multiple controls you will be better off using a user control. If the control is quite simple and has a simple and well-defined objective you are probably in the custom control arena.

Your First Custom Control

In this section you learn to develop a new custom control. We have identified that we need a value scroller that will present the content in a visually rich style.

For this project we are going to start from the basics of creating a custom control, adding more functionality and complexity using the techniques you learned earlier in this chapter. (It would be good idea to review them if you just jumped into this section.) Finally we'll introduce new concepts around the interaction between the visuals and the control logic.

The custom control has been evaluated and here are the requirements:

  1. It will be presented as a simple control that can be reused in multiple projects and can be customized by the designers.
  2. It will scroll content presenting one item at the time.
  3. It will have two buttons to move the content forward and backwards.

The completed custom control will look like Figure 11-17:

fg1117

Figure 11-17

Now that you have all the details you can start building the custom control. The first thing that you need is to add a new item into your Silverlight project. If you want you can create a separate assembly for it as the steps are the same except that you need to add the reference and change the custom namespace on your control.

Use the following code to create a new class and rename it ValueScroller. It needs to inherit from Control to be recognized as a valid UIElement.

public class ValueScroller : Control
{
         public ValueScroller()
         {
                this.IsTabStop = true;
         }
}

IsTabStop specifies that you want your control to be included on the tab list. Now you switch to your user control that will present your custom control and add the namespace so it can be parsed:

<UserControl x:Class="Chapter11.Controls.CustomControlDemo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:Chapter11.Controls"
    FontFamily="Trebuchet MS" FontSize="11"
    Width="400" Height="300" Background="White">
    
 <Grid Width="400" Height="200" Background="White">
        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
                <custom:ValueScroller Width="200" Height="25"/>
        </StackPanel>
    </Grid>
</UserControl>

As soon as the namespace is included you can access it in your XAML designer. You may have noticed that some properties in the control are inherited from Control like for example the width and the height. The project will compile without problem but nothing will be rendered because you have just created the skeleton for the control logic.

Previously you customized the visual parts of a control through the use styles and skins using control templates. In this control you take the same approach. When you drag and drop a button for example, the default template is applied, in our case it is exactly the same as we will need in a default view. How do you define the default style? Let's explore the built-in style model.

Built-in Style

The default style is grouped in a file called generic.xaml; this is the same model that you may find in WPF. This file contains the default built-in style for your custom control. Note that this file may have more than one definition.

This file is just a resource dictionary with styles and templates that will be consumed by custom controls. Because the contents are resources you should treat it as a resource file and should not compile it as a traditional XAML file.

Go back to the project and create a new folder called "Themes", then add a new XML file and rename it as generic.xaml. Once the file is included just go to the properties section and change the build action to Resource. Also remove the Custom Tool entry as you do not need it.

You need to define the dictionary in the same way you would in an application or user control dictionary. For this go to the generic.xaml file and change the content as follows:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:Chapter11.Controls">
</ResourceDictionary>

You are adding the custom namespace where your control is located because you will need it to define the target type in your styles and templates. Now you can start designing your control. Inside of the resource dictionary we are going to add a new style, the following code shows you how to do it.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:Chapter11.Controls">


<!-- Built-in Style -->
<Style TargetType="custom:ValueScroller">
         <Setter Property="Template">
         <Setter.Value>
                <ControlTemplate TargetType="custom:ValueScroller">
                       <Grid x:Name="MainRoot" Background="White">
                               <Border x:Name="MainBorder" 
                                       HorizontalAlignment="Stretch" 
                                       VerticalAlignment="Stretch" 
                                       BorderBrush="#FF5E5E5E" 
                                       BorderThickness="1,1,1,1">
                               </Border>
                                       
                               <TextBlock  x:Name="MainText" 
                                       FontFamily="Verdana" 
                                       FontSize="14" 
                                       FontStyle="Normal" 
                                       TextWrapping="Wrap" Height="17" 
                                       HorizontalAlignment="Center" 
                                       VerticalAlignment="Center" />
                                       
                               <Button x:Name="RightButton" 
                                       HorizontalAlignment="Right" 
                                       VerticalAlignment="Stretch" Width="24" 
                                       BorderBrush="#FFC6BDBD">
                                       <Grid>
                                              <Image Source="RightArrow.png" 
                                                     Width="16" Height="16"/>
                                       </Grid>
                                       </Button>
                                       <Button x:Name="LeftButton" 
                                              HorizontalAlignment="Left" 
                                              VerticalAlignment="Stretch" 
                                              Width="24" BorderBrush="#FFC6BDBD">
                                       <Grid>
                                              <Image Source="LeftArrow.png" 
                                                     Width="16" Height="16"/>
                                       </Grid>
                               </Button>
                       </Grid>
                </ControlTemplate>
         </Setter.Value>
         </Setter>
</Style>
</ResourceDictionary>

You should be familiar with this syntax by now. The important area to highlight is the TargetType, which is the new custom control. With the generic.xaml file added to your project now you need to link your control to the style. For this you we go back to the code behind and add the following line in the constructor.

public ValueScroller()
{
         this.IsTabStop = true;
         this.DefaultStyleKey = typeof(ValueScroller);
}

When you run the project now you can see that the control now has a visual identity!

Custom Properties

To extend the default properties that you are publishing using the base class Control you need to add new properties to your control. In this example you add two types of properties. The first one will be a dependency property and the second one a standard property. The idea is to show you how you can interact with both types.

The dependency property is necessary if you want to allow any type of binding in your control including template binding for customizing the style. In this case you have a demo text property that will be rendered when the control is first rendered. If you open the code behind just add the following line inside the control class.

public static DependencyProperty DemoTextProperty =
         DependencyProperty.Register("DemoText", typeof(string), 
         typeof(ValueScroller), null);


/// <summary>
/// Demonstration text
/// </summary>
public string DemoText
{
         get { return (string)GetValue(DemoTextProperty); }
         set { SetValue(DemoTextProperty, value); }
}

We have just declared the dependency property and the traditional property accessor. Now you can use the property in your built in template as well as in our control presentation. The example below shows you how you can change the generic.xaml file in order to use the property.

 <!- - Generic.XAML -->


<Style TargetType="custom:ValueScroller">
         <Setter Property="DemoText" Value="Start Scrolling!"/>
         <Setter Property="Template">
                <Setter.Value>
                        <ControlTemplate TargetType="custom:ValueScroller">
                               <!- - Other code removed -- > 
                               <TextBlock  x:Name="MainText" 
                                       FontFamily="Verdana" FontSize="14" 
                                       FontStyle="Normal" 
                                       Text="{TemplateBinding DemoText}" 
                                       TextWrapping="Wrap" Height="17" 
                                       HorizontalAlignment="Center" 
                                       VerticalAlignment="Center" />
                        </ControlTemplate>
                </Setter.Value>
         </Setter>
</Style>

When we consume the custom control on our user control container we can also access the property that we have just added.

<!- - Control Implementation -->


<StackPanel Orientation="Vertical" VerticalAlignment="Center">
         <custom:ValueScroller x:Name="MyControl" 
                               Width="200" Height="25" 
                               DemoText="Current Item"/>
</StackPanel>

This does not stop you from using normal properties. In this example you are going to add the source of the scrolling object using an array of strings, for this you can add the property and then handling the event using the code behind your implementation. The array declaration should be located on the our new custom control code behind as it is shown in the following code:

private string[] source;
/// <summary>
/// Source
/// </summary>
public string[] Source
{
         get { return this.source; }
         set { this.source = value; }
}

We can now use the property on the code behind of the user control that is containing your custom control. If our example, we are going to fill the array with a default list; for this we need to handle the "Loaded" event as follows:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
         MyControl.Source = new string[] { "Easy", "Medium", "Hard" };
}

Now you have a control that can expose a property and present the visual style. It's time to add the custom logic and the link between the visuals and the logic. For this, you need to understand the parts model in Silverlight 2.

Parts Model

One thing that you may have noticed is that you don't have that seamless link between the XAML page and the code behind that you are used to in the user control environment. The XAML code is stored in generic.xaml and you are linking it to the code behind using the TargetType at the moment.

But, how can you link the control logic when the user clicks on one of the scrolling buttons? The answer is located on the parts model.

The parts model is divided into two groups; the first one is the elements parts, where you define which elements your control needs to work. These elements should exist on the XAML template. Without them the control may lose the functionality. (Note that we are saying "should" as reminder that you can slightly change how the control works by using the visual styles and the templating model used earlier in the button example.)

The other group is the state parts, these parts define the control states that can be used using the visual state manager

The parts model defines the contract between the control logic and the visuals giving you a clear separation between the implementations, there is no other link between them. This is an excellent approach to dividing the effort of the developers and the designers.

Elements Parts

The elements parts allow you to define which object are you going to need to execute the logic of your control. This means that the visual template should incorporate them in order to get them at runtime. The following illustration (Figure 11-18) shows the relationship when the control initializes.

fg1118

Figure 11-18

When the control initializes it will look for the parts by name on the current instance. This will give you access to the controls in order to handle the events or to perform alteration to the properties, for example to change the content of the text block.

It is important to define the element parts as attributes of your control; in this way a designer or external tools can query the different elements that your control will need. The following code shows how you add the elements in the code behind of the ValueScroller example.:

 [TemplatePart(Name = "MainBorder", Type = typeof(FrameworkElement))]
[TemplatePart(Name="MainRoot", Type=typeof(FrameworkElement))]
[TemplatePart(Name="LeftButton", Type=typeof(Button))]
[TemplatePart(Name = "RightButton", Type = typeof(Button))]
[TemplatePart(Name = "MainText", Type = typeof(TextBlock))]


[TemplatePart(Name = "LostFocusAnimation", Type = typeof(Storyboard))]
[TemplatePart(Name = "FocusAnimation", Type = typeof(Storyboard))]
public class ValueScroller : Control

The template element parts can be of the type that you need. Here you are looking for generic framework elements. (This is considered a best practice because you allow the designer to add any control he needs.) You are also looking for some specific types and a couple of storyboards to control the focus animation.

Because you already defined the templates as attributes you need to add the necessary code to get the instance and to start adding the logic around them. For this you need to override the method OnApplyTemplate that is exposed by your base class. This method will be executed once the default or custom template is applied on your control—perfect timing for your customization. The following code shows our ValueScroller code behind after overriding OnApplyTemplate.

private FrameworkElement mainBorder;
private Storyboard focusAnimation;
private Storyboard lostfocusAnimation;
private FrameworkElement mainRoot;
private Button leftButton;
private Button rightButton;
private TextBlock mainText;


public override void OnApplyTemplate()
{
         base.OnApplyTemplate();


         // Get the parts
         mainRoot = (FrameworkElement)GetTemplateChild("MainRoot");
         mainBorder = (FrameworkElement)GetTemplateChild("MainBorder");
         leftButton = (Button)GetTemplateChild("LeftButton");
         rightButton = (Button)GetTemplateChild("RightButton");
         mainText = (TextBlock)GetTemplateChild("MainText");


         // Get the resources
         if (mainRoot != null)
         {
                focusAnimation = (Storyboard)mainRoot.Resources["FocusAnimation"];
                lostfocusAnimation = 
                        (Storyboard)mainRoot.Resources["LostFocusAnimation"];
         }


         InitInternalEvents();
}


private void InitInternalEvents()
{
         this.MouseEnter += new MouseEventHandler(ValueScroller_MouseEnter);
         this.MouseLeave += new MouseEventHandler(ValueScroller_MouseLeave);


         if (leftButton != null)
                leftButton.Click += new RoutedEventHandler(leftButton_Click);


         if (rightButton != null)
                rightButton.Click += new RoutedEventHandler(rightButton_Click);
}

The code shows how you can get the elements calling the GetTemplateChild function; remember that the names should match in order to receive the instance. Keep in mind that the designer may not implement all the elements; therefore some of them may return null. For this reason, when you are querying the resources like the storyboards you need to check whether the element exists.

You can see how you can also access the element's resources using the object instances that you have just obtained. In this example you are using two animations to change the background. Here is an example of one of them that will be accessed by key:

<Grid.Resources>
         <Storyboard x:Key="FocusAnimation">
                <ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
                Storyboard.TargetName="MainBorder" 
                Storyboard.TargetProperty = 
                "(Border.Background).(GradientBrush.GradientStops)[0].
                 (GradientStop.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00" Value="#FFCECECE"/>
                <SplineColorKeyFrame KeyTime="00:00:01" Value="#FFFFFBFB"/>
                </ColorAnimationUsingKeyFrames>
         </Storyboard>
</Grid.Resources>

Finally, with the object instances in your hands you can start adding the control logic. You can see how easy is to link the visual styles with the control logic without coupling the two files. this is amazing architecture that really simplifies the efforts involved in developing a custom control.

#region Event Handling
         void rightButton_Click(object sender, RoutedEventArgs e)
         {
                MoveNext();
         }


         void leftButton_Click(object sender, RoutedEventArgs e)
         {
                MovePrevious();
         }


         void ValueScroller_MouseLeave(object sender, MouseEventArgs e)
         {
                lostfocusAnimation.Begin();
         }


         void ValueScroller_MouseEnter(object sender, MouseEventArgs e)
         {
                focusAnimation.Begin();
         }
#endregion


#region Control Logic
         /// <summary>
         /// Next item
         /// </summary>
         private void MoveNext()
         {
                if (++currentIndex > Source.GetUpperBound(0))
                       currentIndex = 0;


                ShowContent(currentIndex);
         }
         /// <summary>
         /// Previous item
         /// </summary>
         private void MovePrevious()
         {
                if (--currentIndex < 0)
                       currentIndex = Source.GetUpperBound(0);


                ShowContent(currentIndex);
         }
         /// <summary>
         /// Shows the content
         /// </summary>
         /// <param name="index"></param>
         private void ShowContent(int index)
         {
                if (Source != null)
                {
                       if (index >= Source.GetLowerBound(0) &&
                               index <= Source.GetUpperBound(0))
                       {
                               mainText.Text = Source[index];
                       }
                       return;
                }
         }
#endregion

The code behind now is fully functional and the default template can be override by a new template if necessary, because your logic will not break. Now, to provide consistent behavior to allow proper state overriding you need to implement the necessary attributes and logic to interact with the visual state manager.

Visual State Parts

On this example you have seen how you can obtain resources and trigger changes based on the control logic. But there is a more elegant way to perform these operations that will give the designer better control over how each state should look like. Providing visual states allows the designer to override completely how the control looks on each state.

To define the different states you need to add the visual state parts. These parts are defined using attributes; in this case you are not using TemplatePart, instead you use TemplateVisualState.

To apply this technique in this example replace the element parts that were defining the storyboards with the new visual state in the control code behind.

[TemplatePart(Name = "MainBorder", Type = typeof(FrameworkElement))]
[TemplatePart(Name="MainRoot", Type=typeof(FrameworkElement))]
[TemplatePart(Name="LeftButton", Type=typeof(Button))]
[TemplatePart(Name = "RightButton", Type = typeof(Button))]
[TemplatePart(Name = "MainText", Type = typeof(TextBlock))]


[TemplateVisualState(Name = "Normal", GroupName = "CommonStates")]
[TemplateVisualState(Name = "MouseOver", GroupName = "CommonStates")]
public class ValueScroller : Control

Now your control has two states: the normal state and the mouse over state. You can group the states using the group names. This is sometimes useful when you have multiple states and sub states; defining a correct group hierarchy can help designers understand the logic behind them.

Change the generic.xaml file to transform those resources in two states using the visual state manager like this:

<vsm:VisualStateManager.VisualStateGroups>
         <vsm:VisualStateGroup x:Name="CommonStates">
                <vsm:VisualState x:Name="Normal">
                        <Storyboard>
                               <ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
                               Storyboard.TargetName="MainBorder" 
                               Storyboard.TargetProperty = 
                               "(Border.Background).(GradientBrush.GradientStops)
                                [2].(GradientStop.Color)">
                                       
                               <SplineColorKeyFrame KeyTime="00:00:00" 
                                      Value="#FF00749F"/>
                               <SplineColorKeyFrame KeyTime="00:00:01" 
                                      Value="#FFFFFFFF"/>
                               </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                </vsm:VisualState>
                <vsm:VisualState x:Name="MouseOver">
                        <Storyboard>
                               <!- - Removed for simplicity -- >
                        </Storyboard>
                </vsm:VisualState>
         </vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>

Note that I have removed some of the code for simplicity; you can find the full source code on the examples website (wrox.com). But is clear now how you are moving the storyboards from the resources to the visual state manager. Remember to add the visual state manager namespace in the generic.xaml otherwise the "vsm" namespace will not be recognized!

xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"The last part missing is how you change states within your control logic. You have access to the VisualStateManager object within your code; you can use it to transition from state to state. The first parameter is the control where the state is changing, the second is the name of the new visual state, and the third refers to the triggering transitions which you can disallow if you need to do so. The code now looks like this:

void ValueScroller_MouseLeave(object sender, MouseEventArgs e)
{
        VisualStateManager.GoToState(this, "Normal", true);
}


void ValueScroller_MouseEnter(object sender, MouseEventArgs e)
{
        VisualStateManager.GoToState(this, "MouseOver", true);
}

As you can see this code is much more elegant and allows the designer to trigger more than one storyboard if necessary instead of having the static ones derived from the elements parts.

With the knowledge and the tools to conquer the custom controls space, it is time for you to start experimenting for yourself and come out with awesome controls!

Summary

This chapter has been a journey. The idea was to discover what is possible with Silverlight 2 learning individual features and, trying to find the best implementation for each.

You have been exploring how the user controls works and in which scenarios are they useful. This is a concept that you may be quite familiar with because ASP.NET developers constantly use it. Grouping controls in parent controls is the most common customization that you may encounter. It is powerful and very simple with Silverlight 2.

The visual aspects in the rich internet application world are extremely important. For this reason you looked at how you can customize controls using styles and skins. Sometimes the default look and feel is not good enough but the control logic is spot on. This is a common technique that allows designers to easily change how their application looks. We have introduced the visual states and the tools of the trade that can help you to understand the internal logic of a control, customizing each state with one or more animations.

Finally we put everything together in a discussion of custom controls. In this section we described the special scenarios where this technique is useful and provided a simple example that applied each feature. The introduction of the generic.xaml is an important milestone in your learning as it is the heart of the built-in styles that you see on each control. Finally you learned how to create custom states and how your control can trigger changes on it, allowing the designer to fully customize your newly created custom control.