Escolar Documentos
Profissional Documentos
Cultura Documentos
Silverlight 2 has over 3 dozen User Interface (UI) controls. .NET programmers already familiar
with ASP.Net or (especially) WPF, will find using the Silverlight controls very natural and
straight forward.
Silverlight controls were created to look great and provide extensive and customizable
functionality right out of the box. Moreover, all of the standard controls can be modified in
numerous ways to meet your needs.
The look and feel of the control can be tweaked through styles or can be entirely redesigned
through templates, and the behavior of the controls can be modified through event handlers. In
the rare cases when none of that is enough, you can create (or derive) your own customized
controls as well.
This tutorial will cover event handlers. Styles, Templates and Custom Controls will be covered
in future tutorials. .
That is a temporary limitation. For now, one alternative is to use Expression Blend, and we have
a full tutorial on Blend for Programmers that shows how to work with the two environments
together.
Note that controls can also be created dynamically (in code, at run time) as explained at the end
of this tutorial.
Layout Controls
The use of layout controls is both straight-forward and essential to the creation of Silverlight
applications. Layout controls are used to manage the placement of other controls (including other
layout controls!) in your Silverlight application. You can think of the layout controls as
"containers."
• Grid - essentially a table used for positioning objects in rows and columns.
• StackPanel - used to position objects next to one another, or atop one another.
• Canvas - used for absolute positioning (and unchanged from Silverlight 1.0)
They are listed in the order of how frequently they are used. And to be honest, if you learn Grid
and StackPanel, you can probably program for a long time on just those two.
Grids
Grids (not to be confused with DataGrids) offer easy and exact placement by providing a table-
like structure. You declare rows and columns, and then place controls into a specific row/column
location (spanning across rows or columns as needed).
While you can tweak your grids to achieve very precise placement, the basic use of grids is
extremely straight forward.
You typically declare a grid, declare its rows and columns and then start placing controls into
specific cells (e.g., column 1, row 3). To see this at work, let's create our first Silverlight
Application: Easy Grid.
Pick a location for your application and give it a meaningful name; be sure that you are building
against the latest framework.
When you click OK you will next be asked if you'd like to generate a Web Site or a Web
Application (using the top radio button) or just a test page (using the bottom radio button) or if
you'd like to link to an existing web site; all as shown in the next figure.
Figure 1-3. Choose Simple HTML Test Page
If you create just a test page, the project is kept very simple, but if you generate a Web Site or
Web Application Project, then Visual Studio creates two projects in your new solution; the
Silverlight Application and a test application; excellent for test-based programming but more
than we need right now.
Choose the second radio button (Test Page) and let Visual Studio create a simple application for
you. If Page.xaml doesn't open automatically, double click on it in the Solution Explorer. You
should find that Visual Studio has guessed that you want a grid as your main container, and has
created one for you and named it LayoutRoot. (Also note that the very first declaration in each
"page" is a UserControl.
<UserControl x:Class="EasyGrid.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
In fact a Page is a UserControl, and we'll return to that relationship in a later tutorial.
The Grid definition created by Visual Studio 2008 looks like this:
You define the Rows for a table inside the Grid.RowDefinitions element. For each Row, you add
a RowDefinition element, which itself may have various properties, including a specific height,
or if you prefer you may set the height to be proportional to the available space or to take all the
space not taken by other rows.
Height="Auto"
With Auto, the Grid’s space is distributed evenly based on the size of the content within the row.
<RowDefinition Height="*"
However, you can give twice the space to one column or row as another by using 2* (or a 5:7
ration by using 5* and 7*).
<RowDefinition Height="30*"
<RowDefinition Height="40*"
If you combine this with a Minimum or Maximum Height you get finer control over the limits of
the row's range of sizes,
By default child elements of grid take up the least amount of space necessary to to accommodate
the largest content within a cell in a given row or column. You can take greater control over
positioning, however, by using the margin and alignment properties as described below
Sizing Units
To provide the most flexibility, Grid columns and rows are sized by GridLength objects which
use the GridUnitType, which in turn allows you to choose among:
• Auto (size based on the size properties of the object being placed in the grid)
• Pixel (size in pixels)
• Star (size based on a weighted proportion of the available space)
To see the effect of all this we'll create five rows using different sizing rules. We'll also create
three columns with no sizing rules at all (!). We'll then fill our grid with controls and take a look
at some of the effects. Here is the code, which we'll take apart piece by piece.
<UserControl x:Class="EasyGrid.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="30*" MaxHeight="70" />
<RowDefinition Height="40*" MaxHeight="70" />
<RowDefinition Height="*" MinHeight="30" MaxHeight="50" />
<RowDefinition Height="Auto" MinHeight="5" MaxHeight="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock x:Name="SubscriberPrompt"
Grid.Row="2" Grid.Column="0"
Text="Subscriber?" VerticalAlignment="Bottom" />
<CheckBox x:Name="PlanA" Grid.Row="2" Grid.Column="1"
Content="Plan A" IsChecked="true"
VerticalAlignment="Bottom" />
<CheckBox x:Name="PlanB" Grid.Row="2" Grid.Column="2"
Content="Plan B" VerticalAlignment="Bottom" />
First, it is important to understand that the Xaml shown in Example 1-1 is all that is needed to
produce the Silverlight control shown in Figure 1-4. It is true that you can't see the rows and
columns, but that is easily remedied. Find the declaration of the Grid and set its ShowGridLines
property to True (Intellisense will help as shown inFigure 1-5
Figure 1-5. Adding Property ShowGridLines and setting it True
When you do, the grid lines become visible; which can be very useful during design, as long as
you remember to set the property to false before you post your Silverlight application!
As described above, the RowDefinitions define five rows. The first has a fixed height of 50. The
second and third will float, but in a 3:4 proportion with one another, but each with a maximum
height of 70. The next row will take all the remaining space, but will never be allowed to shrink
to less than 30 nor grow to more than 50. The final row will size itself to the object placed in the
row, but yet is constrained to no less than 5 and no more than 30.
After the column definitions we define a TextBlock and a TextBox. We'll discuss these controls
in more depth later, but the former is typically used as a label and the latter is used to allow the
user to put in text.
<TextBlock
x:Name="FirstNamePrompt "
Grid.Row="0"
Grid.Column="0"
Text="First Name:"
Margin="5"/>
The TextBlock has five parameters. They can be aligned one below the other, or strung out all in
a line, any way you like. The first parameter is the name (FirstNamePrompt) and is used to
allow you to address the object in code. If you give an object in Xaml a name, and save the file,
that object is available with no further declaration in your methods
FirstNamePrompt.Text=”Hello”
From any method of this class, and the compiler will know exactly what you mean!
The Grid.Row and Grid.Column properties are called extended properties and are really
"borrowed" from the immediately surrounding grid to position the TextBlock inside the
appropriate cell.
The Text property does just what you expect, it fills the TextBlock with that text.
Margin is a fascinating property, and we'll return to it a couple times. For now, I'll mention that it
takes three forms:
• A single value, which gives a margin of that value "all around" - that is left, right, top and
bottom
• Two values in which case the first is divided evenly among the left and right margin and
the second is divided evenly among the top and bottom
• Four values, in the order Left, Top, Right, Bottom.
The TextBlock label in the first column is followed by an (input) TextBox in the second column.
The properties are very similar except that you must declare the width of your TextBox and we
choose here to give the TextBox a background color.
The third row is filled by a TextBlock and two CheckBox controls. Note that they are aligned by
setting their VerticalAlignment properties and that the first CheckBox has its IsChecked property
set to True so it will be checked when the page is first shown.
The fourth row has a TextBlock that displays Hello and the final row has a TextBlock that
displays World, but does so setting the FontFamily, FontSize and FontWeight.
StackPanels
StackPanels are typically combined with other layout controls. They allow you to stack objects
one on top of the other, or next to each other (like books on a shelf).
One convenience of a StackPanel is that you do not have to provide the position of the objects
held by a StackPanel, they are positioned relative to the object declared earlier in the Stack..
In the following example, you will stack a TextBlock on top of a TextBox which in turn will sit
on top of a Button that will sit on top of a CheckBox (shades of Yertle the Turtle!)
There is quite a bit of information in this code snippet, so let's unpack it piece by piece.
The top and bottom lines show the declaration of the StackPanel in the Xaml file. The
StackPanel is declared with two attributes: a BackgroundColor (Beige) and an Orientation
(which must be either Vertical or Horizontal).
There are many other attributes you can set, as will be true for nearly all the controls These,
along with the methods are conveniently listed in the documentation,
Figure 1-7. Documentation Showing Stack Panel Members
By setting the Orientation to Vertical we are stacking its contents one on top of another rather
than side by side.
Within the StackPanel, the four objects are declared, and the order of their declaration will
determine the order in which they are stacked. Each is set with its TextAlignment property set to
Left so that they will align, and each has its Margin property set. Let's dig just a bit deeper into
the Margin property by looking up its definition in the documentation,
The Margin property is actually an object of type Thickness. As noted earlier, when you are
declaring a Thickness object in Xaml you may do so in one of three ways:
First, you may provide a double that will be the margin for the margin on all four sides (left, top,
right, and bottom) uniformly around the object. Thus, you might write
Thereby isolating the button with a margin of 100 on either side and above and below,
Figure 1-8. Button Margins
Notice that to accommodate the oversided margin, the width of the button was compromised!
The second way to declare a Thickness (and in this case, a Margin) is to provide the sum of the
sides and the sum of the top and bottom (thus, the sides must be equal and the top and bottom
must be equal,
The effect of this declaration is that the left and right margins are 25, and the top and bottom
margins are each 10.
Finally, you may declare each independently, as long as you do so in the required order:
Figure 1-9. Margins are a Thickness Object
That is: Left, Top, Right, Bottom; or in this case, the Left margin is 10, the Top margin is 2, the
Right margin is 0 and the Bottom margin is 1.
Once the four controls are placed in the stack Panel, and aligned, the stack panel is responsible
for their placement,
Notice that the StackPanel is responsible for its own background color, and for stacking its
contents (the four controls) but each control is responsible for its own alignment and margins.
If we want to shift the stack panel to align all the controls into a single row, we'll want to make a
few additional changes. Not all controls default to aligning in the same way (top, center or
bottom) so we'll explicitly set their vertical alignment to "Center," just as we previously set their
horizontal alignment to "Left". Let's also set the margins to provide a bit of space between each
object as the default is to abut each object.
Note that I set the left margin on the text box to 5 (rather than 10) to bring it a bit closer to the
TextBlock that serves as its label.
When you create a managed code Silverlight application in Visual Studio, the Class attribute is
placed for you. Create a new application and call it EventHandler, and then examine the first few
lines, as shown here,
<UserControl x:Class="EventHandler.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
Here I've deleted the Grid created by Visual Studio, and declared a Canvas object. I've also
assigned an event handler named "Canvas_Loader" to the Loaded event (a pre-existing event
common to all canvases that fires when the Canvas is loaded (created).
I then declare a button in the Xaml and set its content to Hello.
This code appears in Page.xaml. In the code behind file, Page.xaml.cs, I must now implement the
event handler with the name I've promised to use,
The first thing to notice is that the name of the method is identical to that as declared in the
Xaml. The second is that this method follows the pattern of all .NET event handlers: it returns
void and takes two parameters: the first of type object (and containing a reference to the object
that raised the event) and the second of type EventArgs or a type that derives from EventArgs; in
this case RoutedEventArgs. We'll come back to RoutedEventArgs in just a moment.
The implementation is that when the Canvas is loaded it grabs the button declared in the Xaml
file and changes its Content property from "Hello" to "Please push me",
Even in this incredibly simple example there are two important things to notice:
1. You did not have to declare myButton in the code behind; it was known simply by
declaring it in the Xaml.
2. The button adjusted its size to accommodate the larger string.
In any case, if you want to declare your event handlers correctly in code, Visual Studio makes it
very easy to do so. The most common way is to wire up the event handlers in the OnLoaded
event handler, with that event (Loaded) wired up in the Page's constructor.
To see this, return to your previous code and remove the event handler from the Xaml. While
you're there, add a check box as shown here,
Notice that the original Canvas is commented out, and replaced by a Canvas that does not have
an event.
In the constructor, type Lo. Intellisense will pop up and offer to help you create the
EventHandling code, landing on the event Loaded, which is exactly what you want. The tip (next
to the Intellisence box) shows the type of the event.
We'll return to the fact that Loaded is a RoutedEventHandler shortly. Press tab to accept Loaded
and type += to begin adding the delegate. (If delegates and events are new to you, you may want
to read this article. It's a bit old, but still accurate. You can also read extensively about delegates
and events in any book on .NET 3.5 or C# or VB.
Intellisense will walk you through each step of wiring up the event handler, and if you let it, will
also create the stub of the event handler method, ultimately placing your cursor in the method
which it prefills with an exception (in case you forget to add a meaningful implementation.
public Page()
{
InitializeComponent();
Loaded += new RoutedEventHandler(Page_Loaded);
Delete the exception and register event handlers for the common events for your button (click)
and your checkbox (Checked and Unchecked)
Be careful here, Intellisense will want to name the methods for Checked and Unchecked
RushOrder_Checked and RushOrder_Unchecked respectively, but there is no need for two
methods. We'll override that by typing in the name we want, RushOrder_Changed, which will
cause both events to use a shared event handler.
In the shared event handler, we'll check the IsChecked status of the checkbox and if it is checked,
we'll change its text to all upper case (just so we know the event handler is working).
and not
if (RushOrder.IsChecked)
because IsChecked is of the new C# 3.0 type nullable bollean ( bool?) which indicates that it
may have three states, not two: true, false or null.
While it is possible to create all your controls and objects in code, best practices dictate that it is
usually better to do so in Xaml. The most compelling reason is that Xaml is highly "toolable" -
that is, it lends itself to round-trip modification in tools such as Visual Studio and Expression and
thus is easier to scale, modify and maintain.
On the other hand, there are times that you can't know at design time which or how many objects
you'll need, and the ability to create objects dynamically can be a fundamental requirement.
We can make a minor modification to our existing program to add another button on the user's
request. Add the following button to the Xaml file (note that we also name the canvas!)
<Canvas x:Name="myCanvas">
<Button x:Name="myButton" Content="Hello"
Canvas.Left="10" Canvas.Top="10"/>
<Button x:Name="Another" Content="Add Another"
Canvas.Left="10" Canvas.Top="50" />
<CheckBox x:Name="rushOrder" Content="Rush"
Canvas.Left="50" Canvas.Top="10" />
The effect is to add a button to the page with the words "Add Another." When the user clicks on
this button, we want to add a button to the UI and we want that button to have its own size,
position and behavior.
We do all of that in the code behind.
The first thing to do is to add an event handler for the new button,
And to add its implementation, in which you'll create a new button and set its properties,
Because the canvas's Left and Top are not actually properties of the Button (but are extended
properties) you set them with the SetValue method, which takes two parameters. The first is the
actual name of the property you want to set (Canvas.LeftProperty and Canvas.TopProperty)
which is not difficult to find as Intellisense will supply it) and the second is the value (in this
case a double).
Because I want to be able to click this button more than once, and I don't want the new buttons
overwriting each other, I need a member variable to keep track of the top value,
After I set it, I can then increment it so the next button will fall below the previous,
b.SetValue(
Canvas.TopProperty,
this.newButtonPosition);
this.newButtonPosition += 30.0;
The second thing to notice about the Button we are adding is that we are also adding an event
handler registration,
This means that every button that is added (each of the three shown in Figure 1-8) is registered to
call new_button_click when it is clicked. You'll need to write that event handler into your
Page.xaml.cs file,
As the buttons are clicked, they change their message and become disabled,
Finally, none of this will work if you don't add the buttons to the page. In this case, however, you
don't want to add them to LayoutRoot as their positioning is in terms of a specific canvas, so
you'll add them to that canvas,
myCanvas.Children.Add(b);
When you run the program, they are members of that canvas, just as surely as if you had added
them individually in the Xaml file.