This is the second in my new series on Getting Started With Silverlight (please see the first article for information on the series and where to get the software you need).
[updated11/8]
Don’t Start with Xaml…
Until recently, just about every introduction to Silverlight started out by talking about Xaml; the markup language of Silverlight, WPF and Workflow. I believe it is time to stop.
A quick note on Xaml – we all agree how to pronounce it: zam-el to rhyme with camel, but not on how to spell it; most folks use upper case, but Rob Relyea told me in private correspondence that Xaml was released via MS Open Specification Promise, and prefers it aesthetically, even if he does conform and write XAML like most others programmers. But I’m starting a movement.
(Apologies to Rob and his brother Dave for initially pointing to the latter when I meant the former and confusing MS OSP with ECMA, for which I should be flogged.) |
Teaching Xaml first made sense when Xaml was the only (or best) way to create controls. But with a working design surface, Xaml is too high a bar to set just to get started.
Many development environments have gone though a similar evolution: start with HTML, then get a WYSIWYGIYAVL (What You See Is What You Get If You Are Very Lucky) editor. ASP.NET by hand, then ASP.NET from the toolbox. Etc. |
I recommend that if you do not know Xaml you not try to learn it (at first) as you can go very far without doing so, it can be a stumbling block, and the tool will provide incredible help for learning the markup as you need it.
I may be the first person to write down that advice. (I may be stoned to death.) But I suspect I won’t be the last.
Silverlight Without Xaml
To get started, open VS2010 and create a new Silverlight project (I use C# but feel free to use any supported language). Let’s name it ThreeApproaches
When Visual Studio settles down you’ll probably see a split window with the designer on top and Xaml on the bottom. Let’s close the Xaml by clicking on the “make top window the only window” button on the far right of the splitter bar
Open the Toolbox if it is not already open ( Ctrl-W X) and pin it in place. Then click on the Grid that is the default layout control. When you click on it, margins will appear and putting the cursor into the top or left margin will offer you a preview of where you might click to create columns or rows respectively. Go ahead and create two rows and two columns, and then shrink the entire grid down to small enough to look ok.
Add Your First Control
Drag a Textblock out of the toolbox and place it more or less in the upper left box and then open the properties window. If the TextBlock’s properties are not displayed, click on the TextBlock to make it the selected control.
Somewhat unusually you set the name of the control at the very top of the Properties window
Notice that there are two tabs: Properties and events. Make sure you have Properties selected, and below that you may want to click on the Categorized button rather than the A-Z to make this a bit easier to follow.
Expand the Layout property and by clicking in the black triangle next to both Height and Width (and clicking Reset) you can set the TextBlock’s dimensions to be set in accordance with whatever string (characters) are begin displayed. Set the remaining values as shown in the next image, and the TextBlock should show in the designer as placed in the upper right hand box, 5 pixels from the right and bottom margins.
(To save space I cut out some rows, but you can leave those set to their default values).
Wasn’t that cool? No Xaml needed. But if you want to learn Xaml, aha! there are two great features to help. First, click on the horizontal split button on the far right of the design window. This will restore the split window you started out with.
Notice that the Xaml is now shown. Scroll down to line 20 and you should see the definition of the TextBlock, now in Xaml. Notice the 1:1 correspondence with the properties you’ve set.
(click on image for full size)
Xaml and Intellisense
Let’s write the second control (TextBox) in Xaml. Click into the Xaml window, and below the TextBlock, type an open angle brace. Intellisense immediately springs forward offering to help you pick the control you want. The more you type, the more Intellisense will narrow in on your choice.
Once you select TextBox, and hit the space bar, again Intellisense jumps in, this time offering suggestions as to the properties you might want to set.
Fill in the property / value pairs as shown below,
1: <TextBox Name="Name"
2: HorizontalAlignment="Left"
3: VerticalAlignment="Bottom"
4: Height="25"
5: Width="75"
6: FontFamily="Georgia"
7: FontSize="14"
8: Grid.Row="0"
9: Grid.Column="1"
10: Margin="5" />
Your TextBox will appear in the upper right corner of the designer, with all the properties set to correspond with what you’ve written in the Xaml.
To make the TextBlock (the first control) consistent with this, click on it in the designer, and scroll down to Text Category and drop down the FontFamily to pick “Georgia.”
Set the size to 14 and click the bold button to set it to Bold.
Return to the Text property (above Layout) and change it from TextBlock to Name? and make sure that the margin is set to 5 in the Layout section.
When all of that is done, hit Control-F5 to run the application and you should see a prompt and a textBox into which you can enter your name.
If you like, you can click in the grid but not on one of the two controls and bring up the properties for the Grid. Scroll down to, and expand, the “Other” category and click the checkbox next to “Show Grid Lines” to reveal the rows and columns you created.
Okay, now you know you can write your controls in Xaml, but why bother when you can just drag them onto the designer from the toolbox and set their properties in the Properties window. I truly believe that the latter approach is faster, less error prone and generally a much better way to get started.
Will you want to hand-code Xaml eventually? Maybe, but my guess is less and less as you get better and better at Visual Studio and, eventually, Expression Blend.
Dynamic Creation of Controls in Code
There is a third way to create controls: dynamically in code.
You can stop reading right here. You won’t need to know this for a long time. I am putting this into this article because (a) this can be a powerful technique when you do need it and (b) for some folks understanding the relationship between dynamically (C#) and declaratively (Xaml) created versions of the same object can be very helpful in groking what Xaml is about.
But your mileage may vary.
It’s All Just Objects
Every object you create in Xaml can also be created at run time in code. To see this, let’s create a second set of prompt and TextBox that will appear when the project is run.
To do so, turn the expander next to MainPage.xaml to reveal the code behind page, MainPage.xaml.cs. (or MainPage.xaml.vb if you are working in VB).
In the constructor, we’ll put a handler for the Loaded event (the loaded event runs when the page is loaded) and we’ll do our work in the event handler Visual Studio creates.
To do this, click into the constructor and type Loaded += then hit Tab twice to let Intellisense create your handler for you. Click in the handler and delete the exception that Intellisense put there to remind you to implement the handler logic.
We’ll talk about events and event handlers in an upcoming mini-tutorial; but for now, you can ignore the details or feel free to experiment (you can’t break anything). |
Creating The TextBlock Dynamically
As noted above, every control can be created as a CLR object, and again Intellisense will help enormously. Begin by instantiating a TextBlock. the Identifier you use (in this case AddressPrompt) will become the Name property.
1: void MainPage_Loaded( object sender, RoutedEventArgs e )
2: {
3: TextBlock AddressPrompt = new TextBlock();
4: }
You will now add each property to the instance of TextBlock, though here you must be sure to be type-safe. Let’s walk through it.
First, you’ll want to set the HorizontalAlignment, which turns out to be an enumerated constant. Again, Intellisense will help by offering the legitimate values
Fill in the Vertical Alignment in the same way.
When you try to fill in the Margin as a value, you’ll not the red squiggly line indicating something is wrong. Hover over the Margin property and the tag will indicate the type of the Margin: Thickness. At this point you can open the help files to read about the Thickness type, or you can just instantiate one and see how that goes. I personally prefer the latter. Not only does Intellisense show you that there are three possible constructors (which you can scroll through with the arrow keys) but it identifies the purpose of each parameter and guides you through filling them in. While I show the third constructor here, which lets you set the left, top, right and bottom margin, we’ll actually use the second constructor which lets you assign one value for all four.
Next we want to set the Height and Width. Hovering over each will reveal that they are doubles, but in this case we want to set them to “auto” – a quick check of the documentation reveals that this is accomplished by assigning the static value Double.NAN – a flag for the compiler to set them automatically.
You can set the FontFamily and FontSize as a string and a double respectively, but set the FontWeight using the enumeration.
Here is the code we have so far
1: TextBlock AddressPrompt = new TextBlock();
2: AddressPrompt.HorizontalAlignment =
3: System.Windows.HorizontalAlignment.Left;
4: AddressPrompt.VerticalAlignment =
5: System.Windows.VerticalAlignment.Bottom;
6: AddressPrompt.Margin = new Thickness( 5d );
7: AddressPrompt.Height = double.NaN;
8: AddressPrompt.Width = double.NaN;
9: AddressPrompt.FontFamily =
10: new FontFamily( "Georgia" );
11: AddressPrompt.FontSize = 14d;
12: AddressPrompt.FontWeight = FontWeights.Bold;
Placing the Control into the Grid
We placed the first controls into the Grid by writing
Grid.Row = "0" Grid.Column = "1"
But of course, neither TextBlock nor TextBox has a Grid.Row property. These are Extended Properties, properties defined in Grid but borrowed by other elements to assist in their placement. The C# equivalent is to call the ??? methods of the Grid class, passing the UIElement (in this case, AddressPrompt) that you want to place in the grid, and then the column number and row respectively.
Grid.SetColumn( AddressPrompt, 0 ); Grid.SetRow( AddressPrompt, 1 );
That done, the last step is to add the new element to the Grid itself, by referencing the Children collection of the particular Grid instance, and calling the Add method on that collection, passing in our Element:
LayoutRoot.Children.Add( AddressPrompt );
We can then follow a very similar process for the Address text box. The one interesting addition I’ll make is to set the color on the text in the TextBox. You do this by setting the Foreground property, and you must assign it a SolidColorBrush as its value. You can instantiate a SolidColorBrush by passing in a Color from the Colors enumeration,
AddressInput.Foreground = new SolidColorBrush( Colors.Blue );
When you look at the designer, you will not see either of these controls; they won’t exist until the program runs. Press Control-F5 and try out your new program, however, and you’ll see that the dynamically instantiated controls are indistinguishable from the declarative (Xaml) controls; at least to all appearances:
You Can, But Don’t.
Even though dynamic declaration of elements takes many more lines of code; C# developers are often tempted to eschew Xaml and go with C# – after all; it is a tool they know.
Xaml has many advantages, however, not least of which is that it is highly toolable. That means that it works extremely well with the Visual Studio Designer and with Expression Blend, which in the long run means far faster development, better looking and easier to maintain applications, and a much easier interaction with designers.
The Source Code
For completeness, here is the Xaml file followed by the C# file:
MainPage.Xaml
<UserControl x:Class="ThreeApproaches.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White"
Height="135"
Width="250">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.ColumnSpan="1"
HorizontalAlignment="Right"
Margin="5"
Name="myFirstTextBlock"
Text="Name?"
VerticalAlignment="Bottom" />
<TextBox Name="Name"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Height="25"
Width="75"
FontFamily="Georgia"
FontSize="14"
Grid.Row="0"
Grid.Column="1"
Margin="5" />
</Grid>
</UserControl>
[Note that I cleaned up the Grid columns and rows, using relative sizing (1*) and making the relative sizes of the columns 1:2 – all of this to be explained in an upcoming Mini-tutorial]
MainPage.Xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace ThreeApproaches
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Loaded += new RoutedEventHandler( MainPage_Loaded );
}
void MainPage_Loaded( object sender, RoutedEventArgs e )
{
TextBlock AddressPrompt = new TextBlock();
AddressPrompt.HorizontalAlignment =
System.Windows.HorizontalAlignment.Left;
AddressPrompt.VerticalAlignment =
System.Windows.VerticalAlignment.Bottom;
AddressPrompt.Margin = new Thickness( 5d );
AddressPrompt.Height = double.NaN;
AddressPrompt.Width = double.NaN;
AddressPrompt.FontFamily =
new FontFamily( "Georgia" );
AddressPrompt.FontSize = 14d;
AddressPrompt.FontWeight =
FontWeights.Bold;
AddressPrompt.Text = "Address ?";
Grid.SetRow( AddressPrompt, 1 );
Grid.SetColumn( AddressPrompt, 0 );
LayoutRoot.Children.Add( AddressPrompt );
TextBox AddressInput = new TextBox();
AddressInput.HorizontalAlignment =
System.Windows.HorizontalAlignment.Left;
AddressInput.VerticalAlignment =
System.Windows.VerticalAlignment.Bottom;
AddressInput.Margin = new Thickness( 5d );
AddressInput.FontFamily =
new FontFamily( "Georgia" );
AddressInput.FontSize = 14d;
AddressInput.Foreground =
new SolidColorBrush( Colors.Blue );
AddressInput.Width = 100d;
AddressInput.Height = 25d;
Grid.SetColumn( AddressInput, 1 );
Grid.SetRow( AddressInput, 1 );
LayoutRoot.Children.Add( AddressInput );
}
}
}
Next in this series: Creating Input Forms with the Silverlight Toolkit
(Note, when the article is posted, the name of the next posting will become a link)