Part 4 - Understanding XAML Layout and Events
Download the source code for this lesson at http://absolutebeginner.codeplex.com/
In this lesson I want to talk about layout, or in other words, how controls are positioned and arranged on your app's user interface.
So, my game plan:
(1) We'll start by talking about the two primary elements used in layout and positioning: the Grid element and StackPanel element.
(2) With regards to the Grid, I'll talk about defining rows and columns and various sizing options and techniques you can use to fully unlock the power of the Grid
(3) With regards to the StackPanel, we’ll discuss how to change the orientation of items inside the StackPanel, and how to affect the alignment of items as well.
(4) Finally, we'll talk about how event handers are "wired up" from both XAML and in C#.
Understanding the Basics of Grids
The Grid element is used for laying out other controls ... it allows you to define rows and columns, and then each control can request which row and column they want to be placed inside of.
When you use the Blank App Template, you’re provided very little layout guidance to get started.
Note: I typically don’t like to talk about previous versions of things that are no longer relevant to beginners, however since the following ideas I’m about to talk about were so prevalent in articles and books I feel it is necessary to talk about this.
In the past, the Page templates provided a high level <Grid> element named "LayoutRoot" that created three distinct areas:
(1) A very short top-most row for the app name
(2) A second short row for the page’s name
(3) One very large row (the remainder of the space available) that contained another Grid called the “ContentPanel”. The intent of the ContentPanel was that you would add the remainder of your XAML layout code there.
Keep that in mind because you may see articles and books based on previous versions of Phone tooling support in Visual Studio to the “ContentPanel”.
However, in the tooling support for Phone 8.1, the Page template contains a single empty grid:
This Grid appears that there's no rows or columns defined. However, by default, there's always one RowDefinition and one ColumnDefinition even if it's not explicitly defined, and these take up the full vertical and horizontal space available to represent one large "cell" in the Grid. Any items placed between the opening and closing <Grid></Grid> elements are understood to be inside that single implicitly defined "cell".
Grid RowDefinitions and ColumnDefinitions and defining sizes
I’ll create a quick example of a Grid that defines two rows to illustrate two primary ways of row height sizes (Project: RowDefinitions):
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<Rectangle Height="100" Fill="Beige" Grid.Row="0" />
<Rectangle Grid.Row="1" Fill="SteelBlue" />
First, notice how the two Rectangles place themselves inside of the two rows ... by using an attribute Grid.Row="0" and Grid.Row=”1” respectively. You reference both rows and columns using a zero-based numbering scheme.
Second, notice the two different row heights … Height=”Auto” and Height=”*”. There are three syntaxes that you can use to help persuade the sizing for each row and column. I used the term "persuade" intentionally. With XAML layout, heights and widths are relative and can be influenced by a number of factors. All of these factors are considered by the layout engine to determine the actual placement of items on the page.
For example, "Auto" means that the height for the row should be tall enough to accommodate all of the controls that are placed inside of it. If the tallest control (in this case, a Rectangle) is 100 pixels tall, then that's the actual height of the row. If it's only 50 pixels, then THAT is the height of the row. Therefore, "Auto" means the height is relative to the controls inside of the row.
The asterisk is known as "star sizing" and means that the height of the row should take up all the rest of the height available.
Here's a quick example of another way to use "star sizing" ... I created a project that has three rows defined in the ContentPanel. Notice the heights of each one (Project: StarSizing):
<RowDefinition Height="1*" />
<RowDefinition Height="2*" />
<RowDefinition Height="3*" />
<Rectangle Fill="Red" Grid.Row="0" />
<Rectangle Fill="Blue" Grid.Row="1" />
<Rectangle Fill="Green" Grid.Row="2" />
This produces the following result:
Putting a number before the asterisk, I'm saying "Of all the space available, give me 1 or 2 or 3 shares of the remainder". Since the sum of all those rows adds up to six, each 1* is equivalent to 1/6th of the height available. Therefore, 3* would get half of the height available as depicted in the output of this example:
Besides Auto and star sizing, you can also specify widths and heights (as well as margins) in terms of pixels. In fact, when only numbers are present, it represents that number of pixels. Generally, it's not a good idea to use exact pixels in layouts for widths and heights because of the likelihood that screens -- even Windows Phone screens, can have different dimensions. Instead, it's preferable to use relative values like Auto and star sizing for layout.
One thing to note from this example is that control widths and heights are assumed to be 100% unless otherwise specified. That's why the rectangles take up the entire "cell" width. This is why in Lesson 2 the button first occupied the entire grid, and why I had to specify I wanted the button to be 200 x 100 pixels instead.
I want to also point out that a Grid can have a collection of ColumnDefinitions as you can see from this example app I created called GridsRowsAndColumns. Here we have a 3 by 3 grid:
<Setter Property="FontSize" Value="42" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<TextBlock Grid.Row="1" Grid.Column="1">5</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="2">6</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="1">8</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="2">9</TextBlock>
... and the result is a simple grid with a number in each "cell":
The other thing I want you to notice about this example is that when you don't specify a Grid.Row or Grid.Column, it is assumed to mean the first row (row 0) or first column (column 0). Relying on the defaults keeps your code more concise.
Grid cell Alignments and Margins
I have another example app called AlignmentAndMargins. This XAML:
Produces this result:
Most of this example should be obvious if you stare at it for a few moments, but there are several finer distinctions I want to make about Alignments and Margins. First, it points out how VerticalAlignment and HorizontalAlignment work, even in a given Grid cell (and the same will hold true in a StackPanel as well). The ___Alignment attributes PULL controls TOWARDS their boundaries. By contrast, the Margin attributes PUSH controls AWAY from their boundaries. The second observation is the odd way in which Margins are defined ... as a series of numeric values separated by commas. This convention was borrowed from Cascading Style Sheets. The numbers represent the margin pixel values in a clock-wise fashion starting from the left side. So,
Means, 10 pixels from the left boundary, 20 pixels from the top boundary, 30 pixels from the right boundary, 40 pixels from the bottom boundary. In this case, the boundary is a single Grid cell (since the Content panel only has not defined any additional RowDefinitions and ColumnDefinitions).
A bit earlier I said that it's generally a better idea to use relatives sizes like Auto and * to define heights and widths ... why, then, are margins are defined using pixels? Margins are expressed in exact pixels because they are usually just small values to provide spacing or padding between two relative values and can therefore be a fixed value without negatively impacting the overall layout of the page.
A StackPanel will arrange your controls in a flow from top to bottom or from left to right depending on the Orientation property you set.
Here’s an example of using A LOT of StackPanels to achieve a intricate layout:
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Rectangle Width="200" Height="200" Fill="Bisque" />
<Rectangle Width="100" Height="100" Fill="Azure" />
<Rectangle Width="100" Height="50" Fill="RosyBrown" />
<Rectangle Width="100" Height="50" Fill="DarkCyan" />
<Rectangle Width="100" Height="100" Fill="Tomato" />
<Rectangle Width="100" Height="50" Fill="BurlyWood" />
<Rectangle Width="100" Height="50" Fill="SaddleBrown" />
That XAML produces:
I would recommend taking a few moments to attempt to match up the StackPanels and the Rectangles to see how this is produced. The top-most StackPanel creates a “row” of content. The StackPanel’s height is dictated by the height of the largest item, which is the Bisque colored rectangle. I also set the VerticalAlignment=”Top”, otherwise it will default to the middle of the Phone’s screen.
Since we’re stacking horizontally, I’ve added a StackPanel that will be positioned to the right of the Bisque colored rectangle and it will arrange items in a vertical fashion. Inside of that StackPanel I create two other StackPanels that are oriented horizontally. Thus, the intricacy continues until I have a series of rectangles that resemble something Frank Lloyd Wright would have been proud of.
As we continue throughout this series, I’ll use multiple StackPanels to organize the various XAML controls (elements) on screen. In fact, I’ve grown to the point where I prefer the flexibility of the StackPanel to the rigid nature of the Grid. I suppose that is a personal preference.
If you watched the C# Fundamentals series on Channel9 or Microsoft Virtual Academy, in one of the last videos I talk about events. In the Windows Phone API, Pages and Controls raise events for key moments in their life cycle OR for interactions with the end user. In this series, we've already "wired up" the Click event of our “Click Me” button with a method that I called an "event handler". When I use the term "event handler" I'm merely referring to a method that is associated with an event. I use the term "wired up" to mean that the event is tied to a particular event handler method. Some events events can be triggered by a user, like the Click event of a Button control. Other events happen during the lifetime of an event, like a Loaded event that occurs after the given Page or Control object has been instantiated by the Phone APIs Runtime engine.
There are two ways to "wire up" an event for a Page or Control to an event handler method. The first is to set it using the attribute syntax of XAML. We've already done this in our “Click Me” example:
If you'll recall, we typed:
And before we typed in the closing double-quotation mark, Intellisense asked if we wanted to select or create an event handler. We told it we wanted to create a new event handler, and Visual Studio named the event handler using the naming convention:
... so in our case ...
Visual Studio also created a method stub in the code-behind for the XAML page, in our case, the MainPage.xaml.cs. Keep in mind that these are Visual Studio automations. We could have hit the escape key on the keyboard when Intellisense first popped up and typed that code in by hand.
A second way to associate an event with an event handler is to use the Properties window in Visual Studio.
(1) First, you must make sure you've selected the Control you want to edit by placing your mouse cursor in that element. The name of that element will appear in the Name field at the top letting you know the context of the settings below. In other words, any settings you make will be to the button as opposed to another control on the page.
(2) Click the lightning bolt icon. This will switch from a listing of Properties / attributes that can be changed in the Properties window to the Events that can be handled for this Control.
(3) Double-click on a particular textbox to create an association between that Control's event and an event handler. Double-clicking will automatically name the event and create a method stub for you in the code behind.
The third technique (that we'll use several times in this series) is to wire up the event handler in C# code. See line 35 below for an example:
The += operator can be used in other contexts to mean "add and set" ... so:
x += 1;
... is the same as ...
x = x + 1;
The same is true here ... we want to "add and set" the Click event to one more event handler method. Yes, the Click event of myButton can be set to trigger the execution of MULTIPLE event handlers! Here we're saying "When the Click event is triggered, add the clickMeButton_Click event handler to the list of event handlers that should be executed." One Click event could trigger on or more methods executing. In our app, I would anticipate that only this one event handler, clickMeButton_Click, would execute.
You might wonder: why doesn't that line of code look like this:
myButton.Click += clickMeButton_Click();
Notice the open and closed parenthesis on the end of the _Click. Recall from the C# Fundamentals series ... the () is the method invocation operator. In other words, when we use () we are telling the Runtime to EXECUTE the method on that line of code IMMEDIATELY. That's NOT what we want in this instance. We only want to associate or point to the clickMeButton_Click method.
One other interesting note about event handler methods. With my addition of line 35 in the code above, I now have two events both pointing to the same event handler method clickMeButton_Click. That's perfectly legitimate. How, then, could we determine which button actually triggered the execution of the method?
If you look at the definition of the clickMeButton_Click method:
private void clickMeButton_Click(object sender,
The Windows Phone Runtime will pass along the sender as an input parameter. Since we could associate this event with any Control, the sender is of type Object (the type that virtually all data types in the .NET Framework ultimately derive from), and so one of the first things we'll need to do is do a few checks to determine the ACTUAL data type (Are you a Button control? Are you a Rectangle control?) and then cast the Object to that specific data type. Once we cast the Object to a Button (for example) then we can access the Button's properties, etc.
The method signature in the code snippet above is typical methods in the Windows Phone API. In addition to the sender input parameter, there's a RoutedEventArgs type used for those events that pass along extra information. You'll see how these are used in advanced scenarios, however I don't believe we'll see them used in this series.
To recap, the big takeaway in this lesson was the ways in which we can influence the layout of Pages and Controls ... we looked at Grid and Stack layout. With regards to Grid layout we learned about the different ways to define the heights and widths of Rows and Columns (respectively) using Auto sizing, star sizing and pixel sizing. We talked about how VerticalAlignment and HorizontalAlignment pull controls towards boundaries while Margins push Controls away from boundaries. Then we talked about the different ways to wire up events to event handler methods, and the anatomy of an event handler method's input parameters.