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'l 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'l talk about defining rows and columns and various sizing options and techniques you can use to fully unlock the power of the Grid.
3. Next, we'l learn about the StackPanel and just for fun, we'l convert our app from a Grid layout to a StackPanel layout—this wil teach us about some key differences between the Grid and StackPanel.
4. Final y, we'l talk about how event handers are "wired up" from both XAML and in C#.
1. Understanding the Basics of Grids
The default Windows Phone Page template creates a <Grid> element called
"ContentPanel".
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.
Now, in the default MainPage.xaml page template, between the opening and closing
<Grid></Grid> tags, the Grid appears empty ... it 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".
2. Grid RowDefinitions and ColumnDefinitions and defining sizes
An example of a Grid that actually defines rows is the "LayoutRoot" grid. The Grid
defines two rows:
For now, notice how the StackPanel with the project and page titles places itself inside
the first row ... the StackPanel has an attribute called Grid.Row="0". You reference both
rows and columns using a zero-based numbering scheme.
Notice how the ContentPanel Grid places itself in the second row (by setting the
attribute Grid.Row="1").
The first row has its height set to "Auto". The second row (row 1) is set to "*". 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 is 150
pixels tall, then that's the actual height of the row. If it's only 100 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:
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 (and from our original PetSounds 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" size. This is why the button first occupied the
entire ContentPanel grid, and why I had to specify I wanted the button to be 200 x 200
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:
... 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.
3. 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,
Margin="10,20,30,40"
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.
4. StackPanel basics
The second style of layout is enabled by using the StackPanel element. It will arrange
your controls in a flow from top to bottom.
Back in our PetSounds project, we have the following StackPanel defined to create the
app title and page title at the top of the MainPage.xaml:
Both TextBlock elements take up the entire horizontal width of their parent (the
LayoutRoot Grid) and therefore they are stacked vertically. Let's convert our
ContentPanel:
from a Grid into a StackPanel:
At first, this conversion only moves the Meow Button vertically downward:
However, if I were to remove the Width and HorizontalAlignment attributes from the
Quack button (I also remove the Button.Background property):
And comment out the Width and HorizontalAlignment attributes from the Meow button ...
AND comment out the Margin in my MainPage() constructor:
Then you could see that the two buttons are vertically stacked in the StackPanel:
So, as you can see, you can achieve just about any layout you can imagine by using a
combination of four things:
· Grid layout, including RowDefinitions and ColumnDefinitions
· StackPanel layout
· Margins to move elements away from the left-side, top-side, right-side or bottom-side
· The HorizontalAlignment and VerticalAlignment attributes
Ok, so that's XAML Layout in a nutshell. Let's move on to events.
5. Understanding Events
If you watched the C# Fundamentals series on Channel9, in one of the last videos we
talked about events. In the Windows Phone API, Pages and Control raise events for key
moments in their lifecycle OR for interactions with the end user. In this series, we've
already "wired up" the Click event of our PlayAudioButton 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 PlayAudioButton example:
If you'll recall, we typed:
Click="
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:
NameOfElement_EventName
... so in our case ...
PlayAudioButton_Click
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 PlayAudioButton as opposed to another control on the
page.
(2) Click the lightening 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 (in a sense) ... 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 PlayAudioButton_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, PlayAudioButton_Click,
would execute.
You might wonder: why doesn't that line of code look like this:
myButton.Click += PlayAudioButton_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 PlayAudioButton_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
PlayAudioButton_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 PlayAudioButton_Click method:
private void PlayAudioButton_Click(object sender, RoutedEventArgs e)
{
}
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 of 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, but I don't believe we'll see them used in this series.
Recap
To recap, the big takeaways in this lesson are the ways in which we can influence the
layout of Pages and Controls. 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.
No comments:
Post a Comment