Chapter 10. Giving it style – Silverlight 2 in Action, illustrated edition

Chapter 10. Giving it style

This chapter covers

Chapter 9 described the powerful animation features available in Silverlight 2. These features are useful for creating entertaining illustrations and for adding a degree of richness to your application. This richness can also be applied to controls, as you’ll see in this chapter.

Throughout this chapter, you’ll see how to apply rich styles to your application. These styles are similar to the CSS features you may have seen in the HTML world. In general, a Style declaration will be part of a resource, so we’ll cover resources first. From there, you’ll learn how to create a rich visual Style, then, you’ll see how to expand on the definition of a Style to define a ControlTemplate, empowering you to redefine the visual structure of a Control. Once we’ve covered that subject, you’ll learn how to use the VisualStateManager to deliver engaging effects within your Control elements.

10.1. Being resourceful

In general, it’s a good idea to create reusable items whenever possible. This practice makes your application more maintainable. In fact, creating reusable components is a common idea within object-oriented languages like C#. But, sometimes, you may have items that represent non-executable pieces of data—for instance, an image, a media file, or some XAML. These types of content generally fall into one of three categories: declarative resources, loose resources, and embedded files. We’ll cover all three of these categories in this section, beginning with declarative resources.

10.1.1. Declarative resources

Declarative resources are items intended to be shared across multiple elements. They can be any object you wish to share. For instance, a resource can be used to define a DataTemplate or Storyboard, as hinted at earlier in this book. Resources are also a vital part of the styling and templating features discussed later in this chapter. Before you see those features, let’s examine the basic syntax and usage of a declarative resource (snippet 10.1).

Snippet 10.1. XAML Result: The basic syntax and usage of a resource

This snippet shows a basic declarative resource in the form of a LinearGradientBrush . This GradientBrush is used by both TextBlock elements defined in this snippet . This shared approach is possible because the resource is within the scope of the two TextBlock elements. Within this section, you’ll learn about resource scoping in further detail. In addition, you’ll see how to use declarative resources at design time. This task will give you exposure to the meaning behind the x:Key attribute and StaticResource items shown in snippet 10.1. Finally, this section will end with a discussion of using declarative resources at runtime.

Defining Declarative Resources

Resources must be defined within an appropriately named collection called Resources. This collection is a ResourceDictionary, which is a fancy type of IDictionary. This dictionary holds a collection of resources associated with keys. These resources, and their associated keys, can be defined at both design time and runtime. Both approaches are shown in snippet 10.2.

Snippet 10.2. C# XAML: The same SolidColorBrush defined as a resource at design time and runtime
<StackPanel x:Name="myStackPanel">
<StackPanel.Resources>
<SolidColorBrush x:Key="theSolidColorBrush" Color="Green" />
</StackPanel.Resources>
</StackPanel>

SolidColorBrush brush = new SolidColorBrush();
brush.Color = Colors.Green;
myStackPanel.Resources.Add("theSolidColorBrush", brush);

This snippet shows how to define a resource at design time and runtime. Both approaches require you to specify two items. The first item is the key, which, in this case, is "theSolidColorBrush". The other is the resource itself, which in this case is a SolidColorBrush.

The key of a resource is a string that uniquely identifies it. At runtime, this identifier is set when you add a resource to a ResourceDictionary. Because the Resource-Dictionary class implements the IDictionary interface, you can add a resource by using the Add method. The first parameter of this method represents a key. The ResourceDictionary implements the IDictionary interface, so you should use the x:Key attribute to identify a resource at design time. This approach is necessary because IDictionary items require it and should be used in place of x:Name. Once you’ve selected a key, you may move on to the details of the resource.

The resource itself is the reason for this section. This item can be virtually any object. In general, you’ll most likely use Storyboard, Style, and Template items as resources. Regardless of the type of resource, the item can only be used within its respective scope. We’ll discuss this topic later in the section. But before you see that, we should discuss referencing resources at design time and runtime.

Referencing Declarative Resources at Design Time

After a resource has been defined, it can be referenced at design time through the StaticResource extension. This extension uses the same curly-brace syntax used for data binding.

But, instead of using the Binding element, you use the StaticResource word as shown in snippet 10.3.

Snippet 10.3. XAML: Referencing a resource at design time. Notice the use of the StaticResource keyword.
<TextBlock x:Name="myTextBlock" Text="Hello, World"
Foreground="{StaticResource theSolidColorBrush}" />

This snippet shows the basic syntax for using a resource at design time. The Static-Resource extension expects a single value that must match a key from an accessible ResourceDictionary. The resource must also be defined syntactically before it is referenced. Because of this requirement, snippet 10.4 will not work.

Snippet 10.4. XAML: An invalid use of a resource at design time
<StackPanel x:Name="myStackPanel">
<TextBlock x:Name="myTextBlock" Text="Hello, World"
Foreground="{StaticResource theSolidColorBrush}" />
<StackPanel.Resources>
<SolidColorBrush x:Key="theSolidColorBrush" Color="Green" />
</StackPanel.Resources>
</StackPanel>

This snippet shows a valid syntactical use of a resource. If you attempt to run this snippet, it would throw an XamlParseException. This behavior is important to recognize if you’re creating a tool that generates XAML. Referencing a resource at design time is a valuable and common part of Silverlight development. Another common part of Silverlight development involves using resources at runtime.

Using Declarative Resources at Runtime

Referencing resources at design time is useful for setting up the initial state of an application. As an application runs, you may need to work with those resources dynamically. To help you accomplish this feat, Silverlight empowers you to search for, insert, edit, and remove resources at runtime.

Searching for a resource at runtime involves referencing the Resources property which is a ResourceDictionary available on every FrameworkElement and Application. Because of this, you can readily search for a declarative resource by using its key. If the resource isn’t found, null will be returned; if the resource is found, its object representation will be returned. Because the return value may be an object, you may need to cast the value to another type, as shown in snippet 10.5.

Snippet 10.5. C#: Retrieving the SolidColorBrush defined in snippet 10.4
object resource = myStackPanel.Resources["theSolidColorBrush"]; 
if (resource != null)
{
SolidColorBrush brush = (SolidColorBrush)(resource);
brush.Color = Colors.Blue;
}

This snippet retrieves the SolidColorBrush defined as a resource in snippet 10.4. Once it’s retrieved, this Brush is changed from Green to Blue. This small, but interesting, change occurs at runtime. When this code is executed, the TextBlock in snippet 10.4 changes to Blue without any additional code because the Silverlight system automatically listens for those changes. But, it doesn’t necessarily listen for when resources are removed.

Resources may be removed at runtime through the Remove method. This method takes a string that represents the key of the resource to delete. Once it’s deleted, this resource can’t be used. If the resource was applied to any items in your Silverlight application, the resources attributes will still be in use; if you remove a resource, you may want to manually update any elements using the declarative resource.

Declarative resources are those defined within your Silverlight application. These resources can be created at either design time or runtime. In addition, declarative resources can be added, edited, and removed at runtime through the readily available Resources property. In addition to declarative resources, Silverlight has another type of resource known as loose resources.

10.1.2. Accessing loose resources

In addition to using resources defined within your XAML, Silverlight enables you to access loose resources. A loose resource is an external entity, which may represent something such as an image hosted on some server on the internet or some publicly visible JSON data. Regardless of the type of content, Silverlight provides the ability to access loose resources. To demonstrate accessing a loose resource, imagine an ASP.NET web application with the structure shown in figure 10.1.

Figure 10.1. A sample website project structure. Notice the four .png files.

This figure shows the structure of a basic ASP . NET web application. This web application has one web page named Default.aspx. Assume that this web page hosts the Silverlight application defined within the MySilverlightApplication.xap nestled within the ClientBin directory. This will become an important fact in a moment. Also notice the four image files that are part of this web application structure: image01.png, image02.png, image03.png, and image04.png. These images represent the loose resources that we’ll use throughout this section.

Throughout this section, you’ll learn two different ways to access loose resources. The first approach involves referencing loose resources whose location is relative to the Silverlight application. The second approach involves using an absolute Uri.

Referencing Relative Loose Resources

Silverlight allows you to access loose resources relative to the site of origin—the location at which the requesting Silverlight application resides. In many cases, your Silver-light application will be stored within a subdirectory. For instance in figure 10.1, the Silverlight application (MySilverlightApplication.xap) is stored within the ClientBin directory, so this directory can be considered the site of origin. If you want to access image01.png in figure 10.1, you could use the Source shown in snippet 10.6.

Snippet 10.6. XAML: A Uri accessing a relative loose resource
<Image x:Name="myImage" Source="image01.png" />

This snippet accesses a resource in the same directory as MySilverlightApplication.xap. This directory represents the site of origin. If you change the Source property to reference "/image01.png", you’d get the same result because the site of origin represents the root directory when a relative URI is used. This syntax will still allow you to reference loose resources in subdirectories. For instance, you could reference image02.png in figure 10.1 using the Source shown in snippet 10.7.

Snippet 10.7. XAML: A Uri accessing a relative loose resource in a subdirectory
<Image x:Name="myImage" Source="directory/image02.png" />

This snippet shows how to reference a loose resource in a subdirectory, demonstrating that you can use subdirectories with relative references. If you reference a .xap file on a remote server, all your references will be relative to that remote reference. This is important because you can’t use a relative URI to access loose resources in directories that are ancestors to the site of origin. This restriction is a security measure to help ensure that preexisting loose resources can’t be used unless you explicitly allow it. To allow this use, you must expose them through the cross-domain policy file mentioned in chapter 6 and use an absolute Uri.

Retrieving Loose Resources with an Absolute Uri

Silverlight gives you the flexibility to access loose resources via an absolute Uri. This gives you the flexibility to access resources from anywhere across the internet. That is, as long as the target server allows it in its cross-domain policy file. This requirement is also necessary if you want to access a resource located up the directory tree from your .xap file. For instance, if the Silverlight website structure in figure 10.1 is hosted at http://www.silverlightinaction.com, you could access image03.png by using the Source shown in snippet 10.8.

Snippet 10.8. XAML: An absolute Uri accessing a loose resource
<Image x:Name=
"myImage" Source="http://www.silverlightinaction.com/image03.png" />

This snippet shows how to access a loose resource via an absolute Uri. As you may have expected, you can also use this approach to access loose resources in subdirectories. This is worth noting in the event you need to access a loose resource in a sibling directory. As an example, imagine you wanted to access image04.png from figure 10.1. To do so, you need to use the Source shown in snippet 10.9.

Snippet 10.9. XAML: An absolute Uri accessing a loose resource in a subdirectory
<Image x:Name="myImage"
Source="http://www.silverlightinaction.com/resources/images/image04.png"
/>

This snippet uses an absolute Uri to access a loose resource in a subdirectory. This Uri points at the location of the resource, and this location will be loaded as a loose resource. There’s also a way to bundle resources along with your Silver-light application.

10.1.3. Bundling resources

The third kind of resource used in Silverlight is referred to as a bundled resource. A bundled resource is an item included in the .xap file of a Silverlight application. The term bundled resource is a made-up expression used solely for the sake of communication. Bundled resources give you a way to include resources specific to a Silverlight application. Two types of bundled resources can be used in a Silverlight application.

Throughout this section, you’ll learn about these two types of bundled resources. The first is known as a content file—a file that’s added to the .xap file and deployed alongside a Silverlight application. The other type of resource is known as an embedded file, which represents an item that gets embedded into a Silverlight assembly. This kind of resource can be useful for helping to hide your valuable resources.

Using Content Files

A content file is one that is added to a .xap file and deployed alongside a Silverlight application. If you define an image as a content file, that image will be included within the resulting .xap file when the Silverlight application is built. In fact, any file with a Build Action of Content will be bundled into the resulting .xap file. Figure 10.2 shows a .png and .xml file being bundled into a .xap file as content files.

Figure 10.2. How to define a file as a content file. When it’s compiled, a content file is added to a .xap file.

Figure 10.2 shows two files that have been added and marked as content files. The first content file, xmlData.xml, is a basic .xml file. Once the project is built, this file ends up in the root of the .xap file structure. The second, image.png, belongs to a nested directory called images. When the project is built, this relative directory structure is carried over to the final .xap file, which can be accessed using a relative path reference. All content files can be referenced by providing a path relative to the application assembly. This approach can be used at design time, as shown in snippet 10.10.

Snippet 10.10. XAML: Referencing a relative content file at design time. This path is based on the structure shown in figure 10.2.
<Image x:Name="myImage" Source="/images/image.png" />

This snippet shows the syntax used to reference a content file at design time. The leading forward-slash (/) informs Silverlight to begin looking at the same level as the application assembly. This approach is the recommended way to include content with a Silverlight application because it makes things more easily accessible. Sometimes, you may come across somebody who does things the old-school way—the content files will be embedded within the Silverlight assembly. Because of this, you will now see how to access these embedded files.

Using Embedded Files

An embedded file is a file embedded within a Silverlight assembly, which may be either an application or a library. Either way, an embedded file becomes a part of an assembly by changing the Build Action to Resource. This file can be retrieved at design time or runtime by providing a special URL structure.

Embedded resources are accessible through a URL that has three parts. The first part names the assembly the resource belongs to. The second piece is a special keyword called Component that declares a resource is being retrieved. The final one is a relative URL that maps to the location of the resource within the assembly. These three items come together to form a URL template that looks like the following:

[AssemblyName];Component/[RelativePath]

This template can be used at design time or runtime. The design-time implementation relies on the element type to convert the resource. At runtime, you must manually convert the resource. First, you retrieve the embedded resource from the assembly through the Application class, as shown in snippet 10.11.

Snippet 10.11. C#: Referencing an assumed embedded file called embedded.png at runtime
StreamResourceInfo resource = Application.GetResourceStream(
new Uri("Assembly;component/embedded.png", UriKind.Relative));

This snippet shows how to retrieve the resource from the assembly. This resource is represented as a StreamResourceInfo, which is part of the System.Windows.Resources namespace. This class instance must be converted to the type appropriate for your situation. As we mentioned earlier, you shouldn’t come across this scenario very often. When it comes to content files, you’ll probably come across a loose resource. In XAML, you’ll most likely use declarative resources. This approach is especially true if you’re giving your elements Style.

10.2. Giving your elements style

As you saw in section 10.1, resources are the nonexecutable parts of your application. These parts are useful for creating items that can be reused at multiple times. In addition to being used at multiple times, resources can also be shared by multiple elements. These two characteristics make resources a natural fit for styling.

Styling is a way to consistently share the same property values across multiple elements. To see why this is a good idea, imagine needing to create a typical forms-style application. This application must use TextBox elements that have a bold, 9 pt Verdana font for input. In addition, you want to give the TextBox elements a subtle gradient background to make them more appealing. Without styles, you may decide to implement these visual enhancements as shown in snippet 10.12.

Snippet 10.12. XAML Result: The brute-force approach to defining the visual properties of multiple elements. The text in the TextBox elements is entered at runtime.

<Grid x:Name="myGrid">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>

<TextBlock Text="First Name: " />
<TextBox Height="24" Width="180" Grid.Column="1"
FontFamily="Verdana" FontSize="12" FontWeight="Bold">
<TextBox.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFFF" Offset="1"/>
<GradientStop Color="#FFD0D0D0" Offset="0"/>
</LinearGradientBrush>
</TextBox.Background>
</TextBox>

<TextBlock Text="LastName: " Grid.Row="1" />
<TextBox Height="24" Width="180" Grid.Row="1" Grid.Column="1"
FontFamily="Verdana" FontSize="12" FontWeight="Bold">
<TextBox.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFFF" Offset="1"/>
<GradientStop Color="#FFD0D0D0" Offset="0"/>
</LinearGradientBrush>
</TextBox.Background>
</TextBox>
</Grid>

This snippet shows the brute-force approach to defining the visual properties of multiple elements. This snippet uses two TextBox elements with the same values for the Height, Width, FontFamily, FontSize, and FontWeight properties. In addition, the same complex LinearGradientBrush definition is used for the Background of both TextBox elements. Unfortunately, this approach isn’t scalable. For instance, if you need to change the font of the TextBox items, you’d have to make the change to each item—but you can overcome this minor inconvenience with a Style.

A Style is a way to share the same property values across multiple elements. Throughout this section, you’ll learn how to create and use a Style. This approach will help you avoid the maintenance nightmare shown in snippet 10.12. You’ll first see how to define the visual properties of a control through a Style. From there, you’ll learn how to share a Style definition across multiple elements.

10.2.1. Defining the look

To define the look of an element using a Style, you must simply set the Style property. This instruction may sound redundant, but the property name is the same as the type name. The Style property is available on every FrameworkElement., so virtually every control in the Silverlight framework can be styled. You can do this by taking advantage of the Style class’s Setters collection.

The Setters collection stores the entire definition of a Style. This definition is made up of individual property/value pairs similar to those seen in CSS within the HTML world. In Silverlight, each combination is defined within a Setter element, which lets you assign a value to a single visual property. Interestingly, this approach can be used to set both simple and complex property values.

Setting Simple Property Values

A simple property is a property that can be set at design time with a primitively typed value. A primitively typed value is something like an int or string value. These kinds of values can be used at design time to set the property values for properties like FontSize and FontFamily. The term simple property is used for a lack of a better name. In reality, there’s no difference between a normal property and a simple property. Snippet 10.13 shows how to use five simple properties as part of a Style.

Snippet 10.13. XAML Result: A basic Style definition. The text in the TextBox is entered at runtime.

This snippet shows how to define a Style that uses five simple properties , each of which is defined within a Setter element. These elements are automatically added to the Style object’s Setters collection. More importantly, each of the items in this collection is defined by two publicly visible attributes.

The two attributes that define a Setter are called Property and Value. The Property attribute determines which property the Value will be used with. The Property must be a DependencyProperty, but the Value property can be set to any object. Because of this fact, the Setter element is very flexible, making it a natural fit for simple properties. In addition, it’s also a natural fit for another kind of fictional property: the complex property.

Setting Complex Property Values

A complex property is a property whose value is a general-purpose object. In general, these kinds of properties have nested properties themselves that must be set. For example, the LinearGradientBrush used in snippet 10.8 could be considered a complex property value. Now, imagine trying to use this LinearGradientBrush as part of a Style. The approach shown in snippet 10.9 won’t work. Instead, you must use an approach similar to that shown in snippet 10.14.

Snippet 10.14. XAML Result: An example of using a complex property value as part of a Style definition. This snippet builds on snippet 10.13. The text in the result is entered at runtime.

This snippet shows how to define a more complex property as part of a Style. There aren’t any new elements shown in this snippet, but it does show you how to break out the Setter.Value into a nested property itself. This approach gives you the flexibility to use something as complex as a LinearGradientBrush. The approaches shown in both snippets 10.13 and 10.14 haven’t solved the problem of scalability. These two snippets have just shown the syntax of a Style. To solve to problem of scalability, you must understand how to target your Style definitions.

10.2.2. Targeting style definitions

In CSS, style definitions are made up of two parts. One part represents the name of the style, the other part is the name of the HTML tag the style is applicable to. A Style in Silverlight also uses these two parts.

The first part of a Style uniquely identifies a Style definition. As shown in the previous two code snippets, this part is optional. It becomes a requirement only if you create a Style as a resource. If you choose this approach, you must specify the x:Key attribute to unique identify the Style, and you must also specify the other part, the TargetType.

The second part of a Style definition is represented by the TargetType property. This property signals the System.Type that a Style is applicable to. This property doesn’t need to be set if you define a Style within an element as shown in snippets 10.9 and 10.10. If you define a Style as a resource, you must be set this property as shown in snippet 10.15.

 

Note

Style definitions are static. In Silverlight, once a Style is set, it can’t be set again. This means that you can’t dynamically update a Style definition in code at runtime

 

Snippet 10.15. XAML: Defining the TargetType of a Style
<Grid x:Name="myGrid" Background="White">
<Grid.Resources>
<Style x:Key="textStyle" TargetType="TextBox">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Height" Value="24" />
<Setter Property="Width" Value="180" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFFF" Offset="1"/>
<GradientStop Color="#FFD0D0D0" Offset="0"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>

<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>

<TextBlock Text="First Name: " />
<TextBox Grid.Column="1" Style="{StaticResource textStyle}" />

<TextBlock Text="LastName: " Grid.Row="1" />
<TextBox Grid.Row="1" Grid.Column="1"
Style="{StaticResource textStyle}" />
</Grid>

This snippet shows a Style defined as a resource. The Style in this case is configured to be used with TextBox elements as set through the TargetType property . If you were to attempt to use this Style with an element of another type, an error would occur.

10.3. Creating templates

The styling features shown in section 10.2 are a welcome addition to the Silverlight world. These items allow you to quickly create a consistent look throughout an application. This look can be shared throughout an application by defining the styles as resources. Occasionally, the styling options can be somewhat limiting. To overcome these limitations, you can use a template.

A template empowers you to redefine the entire visual representation of an element, giving you the flexibility to make any Control look any way you want. When doing this, you do not sacrifice the behavior of the Control. You could create a Button that looks and feels like an octagon and still reacts to the Click event. Over the course of this section, you’ll experience the full power of a template by building a control template. You’ll also see how to elegantly create a reusable template.

10.3.1. Building a control template

When you build a control template, it will ultimately be used with a Control. Every Control in Silverlight exposes a property called Template. This property is a Control-Template, which lets you take complete control over how a Control looks. In a sense, when you set this property, you’re resetting the control’s appearance, giving you a clean slate to work with. From this clean slate, you can make a Control look however you want. For instance, snippet 10.16 changes the look of a Button to make it look more elliptical.

Snippet 10.16. XAML Result: Changing the look of a Button through a Template

This snippet shows a basic ControlTemplate . This ControlTemplate is associated with a Button through its Template property . Notably, if you were to define a ControlTemplate as a resource, you would associate the template with a specific type through the TargetType property. This property behaves the same way as it did with the Style class. Interestingly, when a template is used with a ContentControl, the Content property does not behave the same way.

Over the course of this section, you’ll learn how to display content within a ControlTemplate. This content will generally be placed inside of a Panel or Border because a ControlTemplate can have only one root element. This root element can then be used to house the contents of a ContentControl or an ItemsControl. You’ll also see how to customize the way in which the Items of an ItemsControl are arranged. But first, you’ll see how to use properties that are part of the target control in your templates.

Considering Control Properties

Ultimately, the purpose of a ControlTemplate is to define the appearance of a Control. This Control may have properties set that should be used within your template. For instance, you may want to use the Background or FontFamily property values of a Control in your ControlTemplate. In these types of situations, you should use a TemplateBinding.

A TemplateBinding is a special type of data binding used within a Control-Template. This data binding uses the Control that the ControlTemplate is applied to as its data source. The data source is identified as a specific property within that Control. This property is referenced by name when you create a TemplateBinding. An example of such a reference is shown in snippet 10.17.

Snippet 10.17. XAML Result: Using a TemplateBinding to use the target elements properties

This snippet shows the basic syntax of a TemplateBinding . This syntax mimics the data-binding syntax explained in chapter 4. In the case of snippet 10.17, this binding causes the Height and Width property values of the Button to be used by the Ellipse in the ControlTemplate. These property values are simple in comparison to what the value of the Content property could be though. If you’re going to display the Content of a ContentControl in a ControlTemplate, you may want to consider using another approach.

Displaying the Content

You may have noticed that the Content of the Button elements in the past two snippets hasn’t been shown because, when you define a ControlTemplate, you must tell Silverlight where to place that Content. To help you do this task, Silverlight provides two FrameworkElement instances: ContentPresenter and ItemsPresenter.

The ContentPresenter class empowers you to specify where the Content of a ContentControl should be shown. It may be easiest to think of this element as a placeholder for some piece of Content. Beyond that, the syntax of a ContentPresenter is the element itself, as shown in snippet 10.18.

Snippet 10.18. XAML Result: Using a ContentPresenter to display the Content of a ContentControl

<Button x:Name="myButton" Content="Hello" Height="20" Width="60">
<Button.Template>
<ControlTemplate>
<Border Width="{TemplateBinding Width}" CornerRadius="8"
BorderThickness="1" BorderBrush="Black" Background="Blue">
<ContentPresenter HorizontalAlignment="Center" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>

This snippet shows the general usage of a ContentPresenter. As you can see, this object is a placeholder designed to be used inside a ControlTemplate. This element is generally limited to ContentControl scenarios and isn’t usually used in ItemsControl situations. For these scenarios, you may want to consider an ItemsPresenter such as the one shown in snippet 10.19.

Snippet 10.19. XAML Result: Using an ItemsPresenter to display the Items of an ItemsControl

<ListBox x:Name="myListBox">
<ListBox.Template>
<ControlTemplate>
<Border CornerRadius="20,7,20,7" BorderThickness="4,2,4,2"
BorderBrush="LimeGreen" Padding="10">
<ItemsPresenter />
</Border>
</ControlTemplate>
</ListBox.Template>

<ListBox.Items>
<ListBoxItem><TextBlock>Item 1</TextBlock></ListBoxItem>
<ListBoxItem><TextBlock>Item 2</TextBlock></ListBoxItem>
<ListBoxItem><TextBlock>Item 3</TextBlock></ListBoxItem>
</ListBox.Items>
</ListBox>

This snippet shows a ListBox with a ControlTemplate applied to it. The Items of that ListBox are positioned according to the ItemsPresenter . This element is important because it determines where the Items will be positioned in a ControlTemplate, but the ItemsPresenter doesn’t determine how the Items will be arranged. That’s the role of the ItemsPanel.

Controlling Item Arrangement

The Items of an ItemsControl control are arranged according to the ItemsPanel property. This property is a special kind of template that defines the Panel that will be used to lay out the Items. By default, this property is set to use a StackPanel with a Vertical Orientation. In reality, you’re free to use any of the Panel elements discussed in chapter 3. You could use a Horizontal StackPanel, as shown in snippet 10.20.

Snippet 10.20. XAML Result: Declaring the ItemsPanel to arrange the Items of an ItemsControl

<ListBox x:Name="myListBox">
<ListBox.Template>
<ControlTemplate>
<Border CornerRadius="20,7,20,7" BorderThickness="4,2,4,2"
BorderBrush="LimeGreen" Padding="10">
<ItemsPresenter />
</Border>
</ControlTemplate>
</ListBox.Template>

<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>

<ListBox.Items>
<ListBoxItem>
<TextBlock Padding="5">Item 1</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock Padding="5">Item 2</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock Padding="5">Item 3</TextBlock>
</ListBoxItem>
</ListBox.Items>
</ListBox>

This snippet uses a Horizontal StackPanel as the ItemsPanel to arrange the Items in the ListBox horizontally. It’s highly likely that you’ll only use a StackPanel as an ItemsPanel. Although you can technically use another Panel element, the other options require actual code. This code will be based entirely on your particular situation, so we won’t cover that topic in this section.

The ControlTemplate class empowers you to redefine the way a Control looks. This new definition can use the target Control property values through a TemplateBinding. When it comes to displaying the Content of a ContentControl, you should use a ContentPresenter within a ControlTemplate. If this ControlTemplate is associated with an ItemsControl, you may need to use an ItemsPresenter to show the Items. These Items can be rendered in new ways thanks to the ItemsPanel property. Once you’ve settled on a ControlTemplate, you may want to use it across multiple controls. Thankfully, Silverlight makes it easy to create reusable templates.

10.3.2. Creating reusable templates

Creating individual templates can be useful when you want to give individualized attention to your controls; templates can also be useful for creating a truly unique, yet consistent, user experience. To help make it easier to deliver a consistent user experience, Silverlight allows you to define a ControlTemplate as part of a Style (snippet 10.21).

Snippet 10.21. XAML Result: Using a ControlTemplate within a Style

This snippet shows a ControlTemplate defined within a Style . This ControlTemplate is the ControlTemplate used in snippet 10.17. We chose this for the sake of its simplicity. You can also define a ControlTemplate as complex as you need it to be. You can even go as far as defining a ControlTemplate that considers visual states.

10.4. Dealing with visual states

Templates give you the flexibility to completely dictate how a Control looks, but the template explanation given in section 10.3 is only useful for defining the default look of a Control. This default look represents the normal state of a Control. In reality, most controls have multiple states. For instance, a Button can be in a pressed or disabled state. To empower you to manage how a Control looks in such a state, Silver-light provides something known as the VisualStateManager.

The VisualStateManager is an element that manages the statesand the transitioning between states. This element belongs to the System.Windows namespace. Because the VisualStateManager is in this namespace, it is ready to be utilized in your Control definitions. Before you can fully utilize the VisualStateManager, you must gain an understanding of components involved in state and transition management. Once you have an understanding of these components, you can leverage the power of the VisualStateManager itself. As you’ll see in the conclusion of this section, this power can be wielded across multiple elements with the help of a Style.

10.4.1. Understanding the components

The VisualStateManager relies on a variety of components to do its job. These components make up something referred to as the parts and states model. This model is designed to separate a Control element’s appearance from its behavior, ensuring that you can customize the visual pieces of a Control without having to change its underlying logic. To enable this feat, the parts and states model relies on three components: states, transitions, and parts.

States

A state is used to reflect a particular aspect of a Control. For instance, the Button has one state that defines how it looks by default. If a user moves the mouse over this Button, it will enter another state. If the Button is pressed, it will change to yet another state. These three states are shown in table 10.1.

Table 10.1. Several states of a Button. Each state has a slightly different visual appearance.

Normal

MouseOver

Pressed

This table shows three of the states exposed by the Button class. In reality, the Button class has many more states. These states are exposed to the VisualStateManager with the help of the TemplateVisualStateAttribute. This attribute can be used by a Control to identify the states a Control can be in. In addition, because a Control can simultaneously be in multiple states, the TemplateVisualStateAttribute exposes the group that a state belongs to. The states and groups available on the Button class are listed in table 10.2.

Table 10.2. The states and groups of the Button class

State

GroupName

Disabled CommonStates
MouseOver CommonStates
Normal CommonStates
Pressed CommonStates
Focused FocusStates
Unfocused FocusStates

Each state is identified by a Name property, which is part of the TemplateVisualStateAttribute. This property is complemented by another called GroupName, which determines the grouping for the state. The reason for this property is to logically group together visual states; this is necessary because a Control can be in multiple states at the same time. For instance, a Button can simultaneously be in a Focused state as well as a Pressed state because the Pressed state is in a different group than the Focused state. Perhaps a better example is a CheckBox in a Checked state while also being in a Normal state. Either way, the main thing to understand is that groups are exclusive—a Control can be in multiple states as long as those states belong to different groups. States that are part of the same group have the ability to transition between one another.

Transitions

A transition defines the way a Control looks as it changes from one state to another. This change is represented as a Storyboard, so you’re free to implement a smooth shift between two states. You can even do this at a fine granular level because of the inclusion of parts.

Parts

A part represents a specific element within a ControlTemplate. A part is generally used when some underlying logic may need to change an area of a ControlTemplate. For instance, the thumb on a Slider will change anytime a user clicks the track. This event will cause some underlying logic to move the position of the thumb. Both the thumb and track are defined as parts, as shown in figure 10.3.

Figure 10.3. The parts of a Slider

This figure shows the two main parts of a Slider, which has more parts. These parts are defined by the TemplatePartAttribute, which enables you to specify the Name and Type of a UIElement that represents a part within a Control. This attribute is used to transmit data about the element that represents the part within the parts and states model. And now that this model has been explained, let’s look at how to leverage it with the VisualStateManager.

10.5. Leveraging the VisualStateManager

The VisualStateManager is used by a ControlTemplate to manage the change between states. This change can be used to generate two different kinds of effects. The first is known as a state-based effect, which can be useful for doing something like creating an enlarged Button if a user has the mouse over it. The other type is known as a transitioning effect, which is useful for creating a fluid interface for controls that may change between states of the same group. Both kinds of effects will be covered in this section.

Creating State-Based Effects

A state-based effect is a transition that occurs at the moment a Control enters a VisualState. When a Control enters this state, the Storyboard associated with the VisualState begins. This Storyboard is defined as part of a ControlTemplate. The Storyboard can be useful for creating a glowing effect or a ballooning effect (snippet 10.22).

Snippet 10.22. XAML: Creating a Button that enlarges when a user hovers over it

This snippet defines an effect that occurs when a user triggers the MouseOver Visual-State for the Button . All the items in this snippet have been described in the previous chapters. You should note three main things. First, the VisualStateGroups element tells the ControlTemplate that some custom Storyboard is going to be used for a state. Second, this state belongs to a predefined group, which is described by the VisualStateGroup element. Third, the VisualState items associated with this group are detailed inside the element. This approach is useful for creating effects when a Control enters a state. However, the effect created in snippet 10.22 would be better defined as a VisualStateTransition.

Define Transitioning Effects

In addition to state-based transitions, the VisualStateManager empowers you to define a transition between states. You can trigger this transition in code by calling the GoToState method. To define a transition in XAML, you must use an element called VisualStateTransition, which allows you to associate a Storyboard with a change between two states. The beginning state is identified by a string property named From. The state being transitioned to is specified by a string property called To. Snippet 10.23 defines a transition that changes the Button in snippet 10.22 back to a Normal state.

Snippet 10.23. XAML: Creating a Button that transitions when a user hovers, or leaves, it

This snippet shows the definitions of two VisualTransition elements. The first VisualTransition scales the Button up as it changes from the Normal VisualState to the MouseOver VisualState . The second VisualTransition scales the Button down as it goes from the MouseOver VisualState back to the Normal VisualState . These two transitions are necessary because, otherwise, the Button from snippet 10.22 would be stuck looking like it was in a MouseOver state. There are two other interesting tidbits in this snippet.

First, you’ll also notice the addition of the two VisualState definitions . These are necessary to keep the transition animations in place. Without these definitions, the transition animations would be lost. The other interesting piece in this snippet is the use of the VisualStateGroup element . You can only create transitions between states that belong to the same group because, as we stated earlier, a Control can be in multiple states, as long as those states belong to different groups. Creating transitions between states empowers you to create deeper and richer controls, so it’s only natural to want to share these effects with multiple Control instances.

10.6. Sharing your effects

Because the effects you create with the VisualTransition and VisualState elements are part of a ControlTemplate, you can define them as part of a Style. This process was shown in snippet 10.21 of section 10.3.2. For the sake of completeness, snippet 10.24 shows the transitions from the previous snippet defined as part of a Style.

 

Share your effects throughout your application

The Application class exposes a ResourceDictionary called Resources. This empowers you to define your visual effects, control templates, and styles within the App.xaml file. This approach enables you to use these visual definitions across all pages in your Silverlight application.

 

Snippet 10.24. XAML: Creating a Button that enlarges when a user hovers over it
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<Style x:Key="buttonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid RenderTransformOrigin=".5,.5">
<Grid.RenderTransform>
<ScaleTransform x:Name="myTransform"/>
</Grid.RenderTransform>

<Ellipse x:Name="myEllipse" RenderTransformOrigin=".5,.5"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.3,0.2">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5"
ScaleX="1.075" ScaleY="1.141"/>
<SkewTransform CenterX="0.5" CenterY="0.5"/>
<RotateTransform CenterX="0.5" CenterY="0.5"/>
<TranslateTransform X="-0.04" Y="0.07"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Color="#FFD9D9D9" Offset="0.004" />
<GradientStop Color="#FF2103BA" Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>

<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation To="1.0"
Storyboard.TargetName="myTransform"
Storyboard.TargetProperty="ScaleX" />
<DoubleAnimation To="1.0"
Storyboard.TargetName="myTransform"
Storyboard.TargetProperty="ScaleY" />
</Storyboard>
</VisualState>

<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation To="1.25"
Storyboard.TargetName="myTransform"
Storyboard.TargetProperty="ScaleX" />
<DoubleAnimation To="1.25"
Storyboard.TargetName="myTransform"
Storyboard.TargetProperty="ScaleY" />
</Storyboard>
</VisualState>

<VisualStateGroup.Transitions>
<VisualTransition From="Normal" To="MouseOver">
<Storyboard Duration="00:00:01">
<DoubleAnimation From="1.0" To="1.25"
Storyboard.TargetName="myTransform"
Storyboard.TargetProperty="ScaleX" />
<DoubleAnimation From="1.0" To="1.25"
Storyboard.TargetName="myTransform"
Storyboard.TargetProperty="ScaleY" />
</Storyboard>
</VisualTransition>

<VisualTransition From="MouseOver" To="Normal">
<Storyboard Duration="00:00:01">
<DoubleAnimation From="1.25" To="1.0"
Storyboard.TargetName="myTransform"
Storyboard.TargetProperty="ScaleX" />
<DoubleAnimation From="1.25" To="1.0"
Storyboard.TargetName="myTransform"
Storyboard.TargetProperty="ScaleY" />
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>

<Button x:Name="myButton" Width="75" Height="75" Content="Push Me"
Style="{StaticResource buttonStyle}" />
</Grid>

This snippet shows a Button ControlTemplate defined as a Style. This Control-Template uses the VisualState and VisualTransition elements from snippet 10.23. This snippet puts everything together from the sections of this chapter. The main thing to note is you can leverage the VisualStateManager within a Style declaration. This is exciting news because there can be a lot of XAML involved in creating effects for the various states and transitions of a Control. These states and transitions are part of something known as the parts and states model, which is supported by Microsoft Expression Blend. Because of this convenience, you’re empowered to create some of the richest controls available on the internet.

10.7. Summary

This chapter showed the flexibility of Silverlight’s control model. This control model empowers you to dictate the various visual states and appearance of a Control. As shown in this chapter, the visual states of a control can be customized with a Visual-StateManager. This component can be used in conjunction with a Template to make a Control look however you want. For more simple situations, you may consider using just a Style. Regardless of the degree of customization, you can share your alterations across multiple components by using resources.

The features displayed in this chapter are powerful enhancements that you will generally implement at design time. These enhancements can be used to differentiate the experience you create for your users. To enhance the experience at runtime, you may want to consider implementing some of the features found in chapter 11.