CHAPTER 7 ■ NAVIGATION
242
because it allows you to use links that link not just to the entry point of an application but also
to some record or state inside that application.
■ Tip With a little more effort, you can use deep linking as a starting point for search engine optimization
(SEO). The basic idea is to create multiple HTML or ASP.NET pages that lead to different parts of your
Silverlight application. Each page will point to the same XAP file, but the URI will link to a different page inside
that application. Web search engines can then add multiple index entries for your application, one for each
HTML or ASP.NET page that leads into it.
URI integration is obviously a convenient feature, but it also raises a few questions,
which are outlined in the following sections.
What Happens If the Page Has More Than One Frame?
The URI fragment indicates the page that should appear in the frame, but it doesn’t include the
frame name. It turns out that this system really only works for Silverlight applications that have
a single frame. (Applications that contain two or more frames are considered to be a relatively
rare occurrence.)
If you have more than one frame, they will all share the same navigation path. As a
result, when your code calls Navigate() in one frame, or when the user enters a URI that
includes a page name as a fragment, the same content will be loaded into every frame. To avoid
this problem, you must pick a single frame that represents the main application content. This
frame will control the URI and the browser history list. Every other frame will be responsible for
tracking its navigation privately, with no browser interaction. To implement this design, set the
JournalOwnership property of each additional frame to OwnJournal. From that point on, the
only way to perform navigation in these frames is with code that calls the Navigate() method.
What Happens If the Startup Page Doesn’t Include a Frame Control?
Pages with multiple frames aren’t the only potential problem with the navigation system’s use
of URIs. Another issue occurs if the application can’t load the requested content because
there’s no frame in the application’s root visual. This situation can occur if you’re using one of
the dynamic user interface tricks described earlier–for example, using code to create the
Frame object or swap in another page that contains a frame. In this situation, the application
if (e.Uri.ToString().ToLower().Contains("RestrictedPage.xaml"))
{
e.Cancel = true;
}
}
You’ll notice that this code doesn’t match the entire URI but simply checks for the
presence of a restricted page name. This is to avoid potential canonicalization problems–in
other words, allowing access to restricted pages by failing to account for the many different
ways the same URI can be written. Here’s an example of functionally equivalent but differently
written URIs:
localhost://Navigation/TestPage.html#/Page1.xaml
localhost://Navigation/TestPage.html#/FakeFolder/ /Page1.xaml
This example assumes that you never want to perform navigation to
RestrictedPage.xaml. The Navigating event does not distinguish whether the user has edited the
URI, or if the navigation attempt is the result of the user clicking the link or your code calling
the Navigate() method. Presumably, the application will use RestrictedPage.xaml in some other
way–for example, with code that manually instantiates the user control and loads it into
another container.
History Support
The navigation features of the Frame control also integrate with the browser. Each time you call
the Navigate() method, Silverlight adds a new entry in the history list (see Figure 7-6). The first
page of your application appears in the history list first, with the title of the HTML entry page.
Each subsequent page appears under that in the history list, using the user-control file name for
the display text (such as Page1.xaml). In the “Pages” section later in this chapter, you’ll learn
how you can supply your own, more descriptive title text using a custom page.
CHAPTER 7 ■ NAVIGATION
244
xmlns:x="
x:Class="Navigation.App" xmlns:navigation=
"clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation">
<Application.Resources>
<navigation:UriMapper x:Key="PageMapper">
</navigation:UriMapper>
</Application.Resources>
</Application>
You then need to link your UriMapper to your frame by setting the Frame.UriMapper
property:
<navigation:Frame x:Name="mainFrame" UriMapper="{StaticResource PageMapper}">
</navigation:Frame>
Now, you can add your URI mappings inside the UriMapper. Here’s an example:
<navigation:UriMapper x:Key="PageMapper">
<navigation:UriMapping Uri="Home" MappedUri
="/Views/HomePage.xaml" />
</navigation:UriMapper>
If your application is located here
localhost://Navigation/TestPage.html
you can use this simplified URI
localhost://Navigation/TestPage.html#Home
which is mapped to this URI:
localhost://Navigation/TestPage.html#/Views/HomePage.xaml
The only catch is that it’s up to you to use the simplified URI when you call the
will be mapped to this:
localhost://Navigation/TestPage.html#/Views/ProductPage.xaml?id=324
The easiest way to retrieve the id query-string argument in the ProductPage.xaml code
is to use the NavigationContext object described later in the “Pages” section.
Forward and Backward Navigation
As you’ve learned, you can set the Frame.JournalOwnership property to determine whether the
frame uses the browser’s history-tracking system (the default) or is responsible for keeping the
record of visited pages on its own (which is called the journal). If you opt for the latter by setting
the JournalOwnership property to OwnJournal, your frame won’t integrate with the browser
history or use the URI system described earlier. You’ll need to provide a way for the user to
navigate through the page history. The most common way to add this sort of support is to
create your own Forward and Backward buttons.
Custom Forward and Backward buttons are also necessary if you’re building an out-of-
browser application, like the sort described in Chapter 6. That’s because an application running
in a stand-alone window doesn’t have access to any browser features and doesn’t include any
browser user interface (including the Back and Forward buttons). In this situation, you’re
forced to supply your own navigation buttons for programmatic navigation, even if you haven’t
changed the JournalOwnership property.
If you’re not sure whether your application is running in a browser or in a stand-alone
window, check the Application.IsRunningOutOfBrowser property. For example, the following
code shows a panel with navigation buttons when the application is hosted in a stand-alone
window. You can use this in the Loaded event handler for your root visual.
if (App.Current.IsRunningOutOfBrowser)
pnlNavigationButtons.Visibility = Visibility.Visible;
Designing Forward and Backward buttons is easy. You can use any element you like–
the trick is simply to step forward or backward through the page history by calling the GoBack()
and GoForward() methods of the Frame class. You can also check the CanGoBack property
Here’s a StackPanel that creates a strip of three navigation links:
<StackPanel Margin="5" HorizontalAlignment="Center" Orientation="Horizontal">
<HyperlinkButton NavigateUri="/Page1.xaml" Content="Page 1" Margin="3" />
<HyperlinkButton NavigateUri="/Page2.xaml" Content="Page 2" Margin="3" />
<HyperlinkButton NavigateUri="Home" Content="Home" Margin="3" />
</StackPanel>
Although the concept hasn’t changed, this approach allows you to keep the URIs in the
XAML markup and leave your code simple and uncluttered by extraneous details.
Pages
The previous examples all used navigation to load user controls into a frame. Although this
design works, it’s far more common to use a custom class that derives from Page instead of a
user control, because the Page class provides convenient hooks into the navigation system and
(optionally) automatic state management.
To add a page to a Visual Studio project, right-click the project name in the Solution
Explorer, and choose Add ➤ New Item. Then, select the Silverlight Page template, enter a page
name, and click Add. Aside from the root element, the markup you place in a page is the same
CHAPTER 7 ■ NAVIGATION
248
as the markup you put in a user control. Here’s a reworked example that changes Page1.xaml
from a user control into a page by modifying the root element and setting the Title property:
<navigation:Page x:Class="Navigation.Page1"
xmlns="
xmlns:x="
xmlns:navigation=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
Title="Sample Page">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock TextWrapping="Wrap">This is the unremarkable content in
A typical completed URI might look something like this:
CHAPTER 7 ■ NAVIGATION
249
/Product.xaml?id=402&type=12
You can retrieve the product ID information in the destination page with code like this:
int productID, type;
if (this.NavigationContext.QueryString.ContainsKey("productID"))
productID = Int32.Parse(this.NavigationContext.QueryString["productID"]);
if (this.NavigationContext.QueryString.ContainsKey("type"))
type = Int32.Parse(this.NavigationContext.QueryString["type"]);
Of course, there are other ways to share information between pages, such as storing it
in the application object. The difference is that query-string arguments are preserved in the
URI, so users who bookmark the link can reuse it later to return to an exact point in the
application (for example, the query string allows you to create links that point to particular
items in a catalog of data). On the down side, query-string arguments are visible to any user
who takes the time to look at the URI, and they can be tampered with.
State Storage
Ordinarily, when the user travels to a page using the Forward and Backward buttons or the
history list, the page is re-created from scratch. When the user leaves the page, the page object
is discarded from memory. One consequence of this design is that if a page has user input
controls (for example, a text box), they’re reset to their default values on a return visit. Similarly,
any member variables in the page class are reset to their initial values.
The do-it-yourself state-management approach described earlier lets you avoid this
issue by caching the entire page object in memory. Silverlight allows a similar trick with its own
navigation system using the Page.NavigationCacheMode property.
The default value of NavigationCacheMode is Disabled, which means no caching is
more selective form of state management that stores just a few details from the current page in
memory, rather than caching the entire page object. Simply store page state when
OnNavigatedFrom() is called and retrieve it when OnNavigatedTo() is called. Where you store
the state is up to you–you can store it in the App class, or you can use static members in your
custom page class, as done here with a single string:
public partial class CustomCachedPage : Page
{ public static string TextBoxState { get; set; }
}
Here’s the page code that uses this property to store the data from a single text box and
retrieve it when the user returns to the page later:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
// Store the text box data.
CustomCachedPage.TextBoxState = txtCached.Text;
base.OnNavigatedFrom(e);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Retrieve the text box data.
if (CustomCachedPage.TextBoxState != null)
txtCached.Text = CustomCachedPage.TextBoxState;
base.OnNavigatedTo(e);
}
Navigation Templates
You now know everything you need to use Silverlight’s Frame and Page classes to create a
Silverlight’s built-in Silverlight navigation system, which enables features like history tracking
and deep linking.
CHAPTER 7 ■ NAVIGATION
252
253
CHAPTER 8
■ ■ ■
Shapes and Geometries
Silverlight’s 2-D drawing support is the basic foundation for many of its more sophisticated
features, such as custom-drawn controls, interactive graphics, and animation. Even if you don’t
plan to create customized art for your application, you need to have a solid understanding of
Silverlight’s drawing fundamentals. You’ll use it to add professional yet straightforward
touches, like reflection effects. You’ll also need it to add interactivity to your graphics–for
example, to make shapes move or change in response to user actions.
Silverlight supports a surprisingly large subset of the drawing features from WPF. In
this chapter, you’ll explore the shape model, swhich allows you to construct graphics out of
rectangles, ellipses, lines, and curves. You’ll also see how you can convert existing vector art to
the XAML format you need, which lets you reuse existing graphics rather than build them from
scratch.
■ What’s New The basic 2-D drawing model hasn’t changed in Silverlight 3. Experienced Silverlight
developers will find just one change in this chapter: the new Viewbox control. You used it in Chapter 3 to resize
an entire user interface, and now you’ll see how a similar trick to help you build scalable graphics. You’ll find
several more add-ons to Silverlight’s drawing model in the next chapter, which introduces simulated 3-D
drawing, pixel shader effects, and a writeable bitmap.
Basic Shapes
The simplest way to draw 2-D graphical content in a Silverlight user interface is to use shapes:
Ellipse, and Rectangle are all straightforward; Polyline is a connected series of straight lines; and
CHAPTER 8 ■ SHAPES AND GEOMETRIES
255
Polygon is a closed shape made up of a connected series of straight lines. Finally, the Path class
is an all-in-one superpower that can combine basic shapes in a single element.
Although the Shape class can’t do anything on its own, it defines a small set of
important properties, which are listed in Table 8-1.
Table 8-1. Shape Properties
Name Description
Fill Sets the brush object that paints the surface of the shape (everything
inside its borders).
Stroke Sets the brush object that paints the edge of the shape (its border).
StrokeThickness Sets the thickness of the border, in pixels.
StrokeStartLineCap and
StrokeEndLineCap
Determine the contour of the edge of the beginning and end of the
line. These properties have an effect only for the Line, Polyline, and
(sometimes) Path shapes. All other shapes are closed and so have no
starting and ending point.
StrokeDashArray,
StrokeDashOffset, and
StrokeDashCap
Allow you to create a dashed border around a shape. You can control
the size and frequency of the dashes and how the edge where each
dash line begins and ends is contoured.
StrokeLineJoin and
StrokeMiterLimit
Determine the contour of the corners of a shape. Technically, these
properties affect the vertices where different lines meet, such as the
The Ellipse class doesn’t add any properties. The Rectangle class adds two: RadiusX
and RadiusY. When set to nonzero values, these properties allow you to create nicely rounded
corners.
You can think of RadiusX and RadiusY as describing an ellipse that’s used to fill in the
corners of the rectangle. For example, if you set both properties to 10, Silverlight draws your
corners using the edge of a circle that’s 10 pixels wide. As you make your radius larger, more of
your rectangle is rounded off. If you increase RadiusY more than RadiusX, your corners round
off more gradually along the left and right sides and more sharply along the top and bottom
edges. If you increase the RadiusX property to match your rectangle’s width and increase
RadiusY to match its height, you end up converting your rectangle into an ordinary ellipse.
Figure 8-3 shows a few rectangles with rounded corners.
CHAPTER 8 ■ SHAPES AND GEOMETRIES
257
Figure 8-3. Rounded corners
Sizing and Placing Shapes
As you already know, hard-coded sizes usually aren’t the ideal approach to creating user
interfaces. They limit your ability to handle dynamic content, and they make it more difficult to
localize your application into other languages.
When you’re drawing shapes, these concerns don’t always apply. Often, you need
tighter control over shape placement. However, in some cases, you can make your design a
little more flexible with proportional sizing. Both the Ellipse and Rectangle element have the
ability to size themselves to fill the available space.
If you don’t supply the Height and Width properties, the shape is sized based on its
container. For example, you can use this stripped-down markup to create an ellipse that fills a
page:
<Grid>
<Ellipse Fill="Yellow" Stroke="Blue"></Ellipse>
</Grid>
Figure 8-4 shows the difference between Fill, Uniform, and UniformToFill.
Figure 8-4. Filling three cells in a Grid
CHAPTER 8 ■ SHAPES AND GEOMETRIES
259
Usually, a Stretch value of Fill is the same as setting both HorizontalAlignment and
VerticalAlignment to Stretch. The difference occurs if you choose to set a fixed Width or Height
value on your shape. In this case, the HorizontalAlignment and VerticalAlignment values are
ignored. But the Stretch setting still has an effect: it determines how your shape content is sized
within the bounds you’ve given it.
■ Tip In most cases, you’ll size a shape explicitly or allow it to stretch to fit. You won’t combine both
approaches.
So far, you’ve seen how to size a rectangle and an ellipse; but what about placing them
where you want them? Silverlight shapes use the same layout system as any other element.
However, some layout containers aren’t as appropriate. For example, StackPanel, DockPanel,
and WrapPanel often aren’t what you want because they’re designed to separate elements. Grid
is more flexible because it allows you to place as many elements as you want in the same cell
(although it doesn’t let you position them in different parts of that cell). The ideal container is
the Canvas, which forces you to specify the coordinates of each shape using the attached Left,
Top, Right, and Bottom properties. This gives you complete control over how shapes overlap:
<Canvas>
<Ellipse Fill="Yellow" Stroke="Blue" Canvas.Left="100" Canvas.Top="50"
Width="100" Height="50"></Ellipse>
<Rectangle Fill="Yellow" Stroke="Blue" Canvas.Left="30" Canvas.Top="40"
Width="100" Height="50"></Rectangle>
</Canvas>
With the Canvas, the order of your tags is important. In the previous example, the
rectangle is superimposed on the ellipse because the ellipse appears first in the list and so is
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock>The first row of a Grid.</TextBlock>
<controlsToolkit:Viewbox Grid.Row="1" HorizontalAlignment="Left" >
<Canvas Width="200" Height="150">
<Ellipse Fill="Yellow"
Stroke="Blue" Canvas.Left="10" Canvas.Top="50"
Width="100" Height="50" HorizontalAlignment="Left"></Ellipse>
<Rectangle Fill="Yellow" Stroke="Blue" Canvas.Left="30" Canvas.Top="40"
Width="100" Height="50" HorizontalAlignment="Left"></Rectangle>
</Canvas>
</controlsToolkit:Viewbox>
</Grid>
Figure 8-6 shows how the Viewbox adjusts itself as the window is resized. The first row
is unchanged. However, the second row expands to fill the extra space. As you can see, the
shape in the Viewbox changes proportionately as the page grows.
Like all shapes, Viewbox has a Stretch property, which takes a default value of
Uniform. However, you can use any of the other values from Table 8-2. You can also get slightly
more control by using the StretchDirection property. By default, this property takes the value
Both, but you can use UpOnly to create content that can grow but won’t shrink beyond its
original size, and DownOnly to create content that can shrink but not grow.
CHAPTER 8 ■ SHAPES AND GEOMETRIES
261
However, the easiest way to get the size you really want in a Viewbox is to wrap your content in
an element that has a fixed size, whether it’s a Canvas, a Button, or something else. This fixed
size then becomes the initial size that the Viewbox uses for its calculations. Hard-coding a size
this way doesn’t limit the flexibility of your layout because the Viewbox is sized proportionately
based on the available space and its layout container.
Line
The Line shape represents a straight line that connects one point to another. The starting and
ending points are set by four properties: X1 and Y1 (for the first point) and X2 and Y2 (for the
second). For example, here’s a line that stretches from (0, 0) to (10, 100):
<Line Stroke="Blue" X1="0" Y1="0" X2="10" Y2="100"></Line>
The Fill property has no effect for a line. You must set the Stroke property.
The coordinates you use in a line are relative to the upper-left corner where the line is
placed. For example, if you place the previous line in a StackPanel, the coordinate (0, 0) points
to wherever that item in the StackPanel is placed. It may be the upper-left corner of the page,
but it probably isn’t. If the StackPanel uses a nonzero margin, or if the line is preceded by other
elements, the line begins at a point (0, 0) some distance down from the top of the page.
It’s perfectly reasonable to use negative coordinates for a line. You can use coordinates
that take your line out of its allocated space and draw over of any other part of the page. This
isn’t possible with the Rectangle and Ellipse elements that you’ve seen so far. However, this
behavior also has a drawback: lines can’t use the flow content model. That means there’s no
point setting properties such as Margin, HorizontalAlignment, and VerticalAlignment on a line,
because they won’t have any effect. The same limitation applies to the Polyline and Polygon
shapes.
If you place a Line element in a Canvas, the attached position properties (such as Top
and Left) still apply. They determine the starting position of the line. In other words, the two
line coordinates are offset by that amount. Consider this line:
<Line Stroke="Blue" X1="0" Y1="0" X2="10" Y2="100"
Canvas.Left="5" Canvas.Top="100"></Line>
130):
<Canvas>
<Polyline Stroke="Blue" StrokeThickness="5" Points="10,150 30,140 50,160 70,130
90,170 110,120 130,180 150,110 170,190 190,100 210,240">
</Polyline>
</Canvas>
Figure 8-7 shows the final line.
CHAPTER 8 ■ SHAPES AND GEOMETRIES
264
Figure 8-7. A line with several segments
At this point, it may occur to you that it would be easier to fill the Points collection
programmatically, using some sort of loop that automatically increments X and Y values
accordingly. This is true if you need to create highly dynamic graphics–for example, a chart
that varies its appearance based on a set of data you extract from a database. But if you want to
build a fixed piece of graphical content, you don’t want to worry about the specific coordinates
of your shapes. Instead, you (or a designer) will use another tool, such as Expression Design, to
draw the appropriate graphics, and then export them to XAML.
Polygon
Polygon is virtually the same as Polyline. Like the Polyline class, the Polygon class has a Points
collection that takes a list of coordinates. The only difference is that the Polygon adds a final
line segment that connects the final point to the starting point. You can fill the interior of this
shape using the Fill property. Figure 8-8 shows the previous polyline as a polygon with a yellow
fill.
<Polygon Stroke="Blue" StrokeThickness="5" Points="10,150 30,140 50,160 70,130
90,170 110,120 130,180 150,110 170,190 190,100 210,240" Fill="Yellow">
</Polygon>
CHAPTER 8 ■ SHAPES AND GEOMETRIES
from the previous example, the interior region is filled if you set the FillRule to Nonzero. Figure
8-10 shows why. (In this example, the points are numbered in the order they’re drawn, and
arrows show the direction in which each line is drawn.)
■ Note If there is an odd number of lines, the difference between the two counts can’t be zero. Thus, the
Nonzero fill rule always fills at least as much as the EvenOdd rule, plus possibly a bit more.