0% found this document useful (0 votes)
93 views

User Controls: Usercontrol

UserControls are the basic unit of reusable Xaml and the code that goes with it. You can create UserControls in two ways. One is to create a new application and let Blend or Visual Studio create them for you. The other is to explicitly add one to your application.

Uploaded by

api-19623880
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
93 views

User Controls: Usercontrol

UserControls are the basic unit of reusable Xaml and the code that goes with it. You can create UserControls in two ways. One is to create a new application and let Blend or Visual Studio create them for you. The other is to explicitly add one to your application.

Uploaded by

api-19623880
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

User Controls

UserControls are the basic unit of reusable Xaml and the code that goes with it. You can create
UserControls in many ways. One of the two most common ways is to create a new application
and let Blend or Visual Studio create them for you. This seems surprising until you look closely
at Page.xaml and App.xaml and realize that they are in fact UserControls.

I created an application called KeyboardControl and the very first line in Page.xaml is this:

<UserControl x:Class="KeyboardControl.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="600" Height="350">

The code-behind begins like this:

namespace KeyboardControl
{
public partial class Page : UserControl
{

So we've been creating UserControls for some time.

The second way to create UserControls is to explicitly add one to your application. If you right
click the project and choose Add®New Item, the dialog that comes up will offer a template with
Silverlight User Control as one of the options.

The UserControl that is created is exactly like Page.xaml except of course that it has whatever
name you give it.

You can add user controls to your application for a couple reasons, but the most common is to
encapsulate some functionality that you'd like to reuse; in this application or, less commonly, in
another.

The best way to see how this works is to create some useful functionality and then to put it into a
user control and reuse it.

Start by Creating Something Worth Reusing

The key to reusability is having something worth reusing. We'll start by recreating an enhanced
form first demonstrated by Karen Corby at Mix08 and used here with her permission and
encouragement. You can fill out this form by hand, but it also supports keyboard shortcuts;
specifically, Ctrl-M adds the address of Microsoft, and Ctrl-C adds the address of the Computer
History Museum in Mountainview California.
Figure 4-1. The Form Filled in by Pressing Control-C

Creating the Keyboard Project First

This project will assume you've worked your way through the previous tutorials and thus will
progress quickly through

• Creating an application
• Adding controls and adding event handlers
• Using styles

Because setting up the form and the grid can be tedious, I've also included a starter application
(KeyboardStarter) with the source code.

So, to get started, fire up Visual Studio 2008 and create a new application called
KeyboardControl

The image above shows a text block with Home Address, and then what amounts to a grid with a
border and prompts on the left and text boxes on the right. That suggests that the First control
will be a stack panel so that we can easily stack one control on top of another, and within that
stack panel a Grid.

<UserControl x:Class="KeyboardControl.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="500">

<StackPanel Background="White">
<TextBlock Text="Home Address" FontFamily="Verdana" FontSize="24"
HorizontalAlignment="Left" Margin="15,0,0,0"/>

<Grid x:Name="AddressGrid" >


<!--More Here-->
</Grid>
</Border>
</StackPanel>
</UserControl>

Set the background color for the grid to a soft color (I personally like Bisque). While you're at it,
surround your Grid with a Black Border. Give your grid 5 rows plus top and borders of 10 and
two columns with left and right borders of 10 and a center padding of 10, as shown,

<Border BorderBrush="Black" BorderThickness="1" Margin="15">


<Grid x:Name="AddressGrid" Background="Bisque" >
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="10" />
</Grid.ColumnDefinitions>
<!--More Here-->
</Grid>
</Border>

Setting the Styles for the Controls

The grid will be filled with four prompts and four text boxes, but a quick look at the earlier
diagram shows that these are hand styled as was done in Beta 1. We will have an extensive
tutorial on the new and much improved Styling and Templating support in Beta 2 coming quite
soon, but the code shown here will still work. You can just copy and paste it in App.xaml within
the Application.Resources tags.

Each Style identifies its TargetType (e.g., Button or TextBlock) and then sets a value for each
property it wants to control the style of. We are creating only two styles, one for TextBlocks that
will be used as labels, and one for TextBoxes to gather data.

All the TextBlocks will be Bottom and Left aligned, and will have their text set to Verdana, size
18 and the text will be blue. These TextBlocks will also have a margin of 5 all around.

<Application.Resources>
<Style TargetType="TextBlock" x:Key="TextBlockPrompt">
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Margin" Value="5" />
</Style>

All the TextBoxes will set their font to Verdana, but at 18 point, bold black; also aligned bottom
and left. The TextBox itself ill be 250 by 30 and they too will set a margin of 5 all around.

<Style TargetType="TextBox" x:Key="TextBoxStyle">


<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Width" Value="250" />
<Setter Property="Height" Value="30" />
<Setter Property="Margin" Value="5" />
</Style>

</Application.Resources>

Creating the Controls in the Grid

Rather than adding the style information piece by piece to the TextBlock we can now bind the
style information to the Style attribute, using the "key" to identify which style we want to use.

<TextBlock Text="Location: "


Style="{StaticResource TextBlockPrompt}"
Grid.Row="2" Grid.Column="1" />

This is exactly as if I had written

<TextBlock Text="Location: "


VerticalAlignment="Bottom"
HorizontalAlignment="Left"
FontFamily="Verdana"
FontSize="18"
FontWeight="Medium"
Foreground="Blue"
Margin="5"
Grid.Row="2" Grid.Column="1" />

The line
Style="{StaticResource TextBlockPrompt}"

Reaches into App.xaml and finds the style resource whose key is TextBlockPrompt and assigns
all these values in one swell foop.

In the same way, we can assign the TextBox style to the associated text box.

<TextBox x:Name="Location"
Style="{StaticResource TextBoxStyle}"
Text ="Silverlight Central"
Grid.Row="2" Grid.Column="3" />

They make a matched set, and we can do the same for the other three controls as well.

<TextBox x:Name="Address1"
Style="{StaticResource TextBoxStyle}"
Text="100 Main Street"
Grid.Row="3" Grid.Column="3" />

<TextBlock Text="Address Line 2: "


Style="{StaticResource TextBlockPrompt}"
Grid.Row="4" Grid.Column="1" />

<TextBox x:Name="Address2"
Style="{StaticResource TextBoxStyle}"
Text="Apartment 100"
Grid.Row="4" Grid.Column="3" />

<TextBlock Text="City, State, Zip "


Style="{StaticResource TextBlockPrompt}"
Grid.Row="5" Grid.Column="1"/>

<TextBox x:Name="City"
Style="{StaticResource TextBoxStyle}"
Text="Boston, MA 01001"
Grid.Row="5" Grid.Column="3"/>

Notice that the Text for each of the TextBox controls is hard coded and displayed as coded when
you run the application,
Figure 4-2. The Form with Hard-wired values

It is fair to say that as written, this application is of limited value.

Two Way Data Binding

As discussed in the Tutorial on Data Binding one powerful way to bind the data that will fill the
address to data that you might persist in, for example, a database or an XML file or any other
storage, is by using a business object to mediate between your User Interface (the Silverlight
application) and the storage.

Let's create an address object that will map to the data we wish to display (though a typical
business object will probably not be so tightly coupled to a particular UI page). We will do with
address exactly what we did with the book object in that tutorial. Rather than making you wade
through all that code, I'll simply provide the Properties and their underlying member variables in
the next table:

Private Member Variable Public Property


location Location
address1 Address1
address2 Address2
city City

The class structure is very similar to what you saw in the tutorial on data binding,

using System.Collections.Generic;
using System.ComponentModel;

namespace KeyboardControl
{
public class Address : INotifyPropertyChanged
{

With each of the properties providing a backing variable, a getter and a setter that also calls the
notification event.

Once you've filled in all the public properties you are ready to replace the hard coded Text Block
values with the data bound values,

<TextBox x:Name="Location"
Style="{StaticResource TextBoxStyle}"
Text ="{Binding Location, Mode=TwoWay }"
Grid.Row="2" Grid.Column="3" />

<TextBox x:Name="Address1"
Style="{StaticResource TextBoxStyle}"
Text ="{Binding Address1, Mode=TwoWay }"
Grid.Row="3" Grid.Column="3" />

<TextBox x:Name="Address2"
Style="{StaticResource TextBoxStyle}"
Text ="{Binding Address2, Mode=TwoWay }"
Grid.Row="4" Grid.Column="3" />

<TextBox x:Name="City"
Style="{StaticResource TextBoxStyle}"
Text ="{Binding City, Mode=TwoWay }"
Grid.Row="5" Grid.Column="3"/>

And Now For Something Completely Different

Let's review what we have: a "form" that prompts for address information, and an Address
object that can either hold or provide that information.

Note that the "form" is implemented as the contents of the Grid whose name is "Address" and
which contains the four TextBlocks and the four TextBoxes.

What we'd like to add is the ability for the form itself to respond to two keyboard shortcuts.
When we press Control-M we'd like the form to be filled in with the address information for
Microsoft, and when we press Control-C we'd like to fill in the address of the Computer History
Museum
Figure 4-3. The Computer History Museum Building

Keyboard Events
To accomplish this, we'll respond to keyboard events received by the stack panel: specifically the
KeyDown event.

The Silverlight Help files document that the KeyDown event is a bubbling event (see Tutorial
#1 for a full explanation of bubbling).

First Get It Working

We can start simple and respond to any keyboard event by filling the form with the Microsoft
address. We'll give the class a private Address object which we'll allocate memory for in the
constructor. We'll also handle the standard Loaded event that fires when the page is loaded.

public partial class Page : UserControl


{
private Address theAddress;
public Page()
{
InitializeComponent();
theAddress = new Address();
Loaded += new RoutedEventHandler(Page_Loaded);
}

In Page_Loaded we want to create an event handler for KeyDown when any keydown event is
registered on any control within the grid, which you'll remember we named AddressGrid in
Page.xaml like this,
<Grid x:Name="AddressGrid" Background="Bisque" >

When Page.xaml is saved, that identifier is immediately available in the code behind, and we can
access its properties and events, including its KeyDown event,

Figure 4-4. Define it in .xaml and use it in the code-behind

void Page_Loaded(object sender, RoutedEventArgs e)


{
AddressGrid.KeyDown += new KeyEventHandler(AddressGrid_KeyDown);
}

Visual Studio 2008 will offer to create the shell of an event handler for us, which is just what we
want. For now, we'll respond to the KeyDown indiscriminately; whatever key is pressed, we'll
fill in Microsoft's address.

This approach is called by various names, depending on how you feel about it:

• Get it working and keep it working


• Successive approximation
• Wasting time on work you'll just throw away

I'm a big believer in successive approximation, by changing fewer things between test runs, I
have fewer things to look at when it doesn't work.

void AddressGrid_KeyDown(object sender, KeyEventArgs e)


{
theAddress.Location = "Microsoft";
theAddress.Address1 = "One Microsoft Way";
theAddress.Address2 = "Building 10";
theAddress.City = "Redmond, WA 98052";
this.DataContext = theAddress;
}

Note that we end the event by setting the DataContext of the page to the Address object we just
populated. That has the effect of providing a specific object for the controls to bind to. When you
run this application nothing happens. Click inside any of the four text boxes, however, and then
press any key at all and the address is filled in. The cleanest way to do this is to press the shift
key!

Figure 4-5. Pressing any key works for now

Discriminating Among Keys

Clearly this isn't quite cooked. We want to fill in the Microsoft Address on Control-M, the
Computer Museum's address on Control-C. A small revision to the event handler is needed,

void AddressGrid_KeyDown(object sender, KeyEventArgs e)


{
if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control)
{
theAddress.Location = "The Computer History Museum ";
theAddress.Address1 = "No Longer in Boston!";
theAddress.Address2 = "1401 N. Shoreline Blvd";
theAddress.City = "Mountain View, CA 94043";
}
if (e.Key == Key.M && Keyboard.Modifiers == ModifierKeys.Control)
{
theAddress.Location = "Microsoft";
theAddress.Address1 = "One Microsoft Way";
theAddress.Address2 = "Building 10";
theAddress.City = "Redmond, WA 98052";
}
this.DataContext = theAddress;
}
A quick fix to the width of the outermost user control in Page.xaml (changing Width from 500 to
600) and to the Width Setter Property in the TextBox Style in App.xaml, (changing the value
from 250 to 350) and we have room for "The Computer History Museum"

<Style TargetType="TextBox" x:Key="TextBoxStyle">


<Setter Property="Width" Value="350" />
</Style>

Bring up the application, anywhere in the form, and press Control-C; then Control-M and then
Control-C. Fun, eh?

Figure 4-6. After pressing Control-C

By setting a break point where the key is examined, you can actually watch the test for the
Control-C combination; either hovering over the values, or looking at the Watch (or
QuickWatch) window,

Figure 4-7. Using the debugger to see what is held in EventArgs

User Control
This is, arguably, useful. In fact, we might want to have a Home Address, a Work Address and a
Billing Address, and it might be nice to have the control-key work for all three. Thus, we have
two choices.
1. Duplicate the xaml and code behind for each instance (home, work, billing)
2. Factor out the common xaml and supporting code into a control

I won't waste your time hammering on why we'll choose #2; if you've come this far you have a
visceral revulsion at the idea of choice #1 and are eager to get on to seeing how you create the
UserControl.

You begin, no surprise, by right clicking on the project and choosing Add. Within the templates
section of the dialog box choose Silverlight User Control. Name your new control Address.xaml

Figure 4-8. Creating the Address User Control

Two files are immediately added to your project

• Address.xaml
• Address.xaml.cs

Address.xaml will look amazingly familiar when you first open it.

<UserControl x:Class="KeyboardFun.Address"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">

</Grid>
</UserControl>

It is, essentially, the same as Page.xaml, except the names have been changed to protect the
innocent namespace.
Implementation

The rest, I'm afraid, is mechanical.

Return to Page.xaml and scoop out the Border and everything within it. The simplest way to do
this is to collapse the Border control (if outlining mode is off in Visual Studio 2008 you can
easily turn it on from the Edit menu when the design or code window has focus,

Figure 4-9. Turning on outlining

Collapse the Border and then use Control-X to cut it from its current position, and Control-V to
paste it into the new .xaml file, replacing the grid that was placed there by Visual Studio
2008

Notice that your control is in place but a bit narrow. Delete the Width and Height attributes from
the outer User Control,

<UserControl x:Class="UserControlDemo.AddressUserControl"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
> <!--no specific size-->
<Border BorderBrush="Black" BorderThickness="1" Margin="15">

As soon as you delete these the control will be centered and it will expand to fit the controls,
Figure 4-10. The new user control auto-sized

Adding Code

All the following steps occur in AddressUserControl.xaml.cs:

1. Add the Address member variable as you did earlier


2. In the constructor allocate memory for the Address object and set the Loaded event
handler
3. In the Page_Loaded implementation add an event handler for the KeyDown event on the
grid

public partial class AddressUserControl : UserControl


{
private Address theAddress = null;
public AddressUserControl()
{
InitializeComponent();
theAddress = new Address();
Loaded += new RoutedEventHandler(Page_Loaded);
}

void Page_Loaded(object sender, RoutedEventArgs e)


{
AddressGrid.KeyDown += new KeyEventHandler(AddressGrid_KeyDown);
}

4. The implementation of the AddressGrid.KeyDown is cut and paste out of Page.xaml.cs,

void AddressGrid_KeyDown(object sender, KeyEventArgs e)


{
if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control)
{
theAddress.Location = "The Computer History Museum ";
theAddress.Address1 = "No Longer in Boston!";
theAddress.Address2 = "1401 N. Shoreline Blvd";
theAddress.City = "Mountain View, CA 94043";
}
if (e.Key == Key.M && Keyboard.Modifiers == ModifierKeys.Control)
{
theAddress.Location = "Microsoft";
theAddress.Address1 = "One Microsoft Way";
theAddress.Address2 = "Building 10";
theAddress.City = "Redmond, WA 98052";
}
this.DataContext = theAddress;
} // end KeyDown event handler
} // end class AddressUserControl

Using The User Control

That's it for creating the User Control, but it isn't of much value if you don't place it back into
your Page.xaml file. Here's how.

1. Save all your files


2. At the top of Page.xaml add a namespace for your control, with a prefix of your choosing
(I'll use jl). Intellisense is incredibly eager to help.

Figure 4-11. Adding the namespace

You end up with this:

xmlns:jl="clr-namespace:UserControlDemo"

3. You are now ready to substitute the User control for all the contents of the Border in
Page.xaml. If you've not deleted Border already do so now, and replace with your control, just as
you might place any other control into the StackPanel,

<jl:AddressUserControl x:Name="HomeAddress" />


Reuse, reuse, reuse...

Let's add one more of our user controls to the StackPanel and change the prompt on the first, so
that it looks like this,

<UserControl x:Class="UserControlDemo.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:jl="clr-namespace:UserControlDemo"
Width="600" Height="500">
<StackPanel Background="White">
<TextBlock Text="Event Address" FontFamily="Verdana" FontSize="24"
HorizontalAlignment="Left" Margin="15,0,0,0"/>
<jl:AddressUserControl x:Name="HomeAddress" />

<TextBlock Text="Billing Address" FontFamily="Verdana" FontSize="24"


HorizontalAlignment="Left" Margin="15,0,0,0"/>
<jl:AddressUserControl x:Name="BillingAddress" />
</StackPanel>
</UserControl>

Note that adding a second instance of the AddressUserControl requires only making sure they
have different names. Before you run the program, take a peek at Page.xaml.cs,

using System.Windows.Controls;

namespace UserControlDemo
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();

}
}
}

All the logic has been exported and encapsulated in the User Control. You can add 2 (or 20)
Address User Controls to your UI and add no code whatsoever. Yet, when you run the program,
each works independently, and each supports the Control-keys as implemented,
Figure 4-12. Two instances of one user control

What Have You Learned Dorothy?


If you don't need a full blown custom control (ready to ship to customers), refactoring UI and its
supporting code into User Controls is, once you've done it, almost trivial; and incredibly
convenient, and makes for much more scalable code.

I will follow with a tutorial on Custom Controls, but there are a few more fundamental topics I
want to be sure to cover first.

Note, the source code for this tutorial comes in three parts:

1. KeyboardStarter starts you off with a grid and the basic controls and styles you already
know about
2. KeyboardControl is the interim application with the KeyDown event handler
3. UserControlDemo is the final product with the KeyDown event handling logic factored
out into a User control.

You might also like