This chapter
is taken from book "Programming Windows Phone 7" by Charles Petzold published by
Microsoft press.
http://www.charlespetzold.com/phone/index.html
One of the most important classes in all of Silverlight
is
Panel-the class that plays a starring role
in the Silverlight layout system. You might expect such a crucial class to
define many properties and events, but Panel defines only three properties on
its own:
- Background of type Brush
- Children of type UIElementCollection
- IsItemsHost of type bool
The three standard types of panels provided by Silverlight for Windows Phone
are StackPanel (probably the simplest kind of panel), Grid (which is the first
choice for most routine layout), and Canvas, which should be ignored for most
routine layout jobs, but has some special characteristics that make it handy
sometimes.
The Single-Cell Grid
A Grid is generally arranged in rows and columns, you can put multiple
children in a single-cell Grid. Here's a simple example for reference purposes:
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<TextBlock
Text="TextBlock
aligned at right bottom"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
/>
<Image
Source="Images/BuzzAldrinOnTheMoon.png"
/>
<Ellipse
Stroke="{StaticResource
PhoneAccentBrush}"
StrokeThickness="24"
/>
<TextBlock
Text="TextBlock
aligned at left top"
HorizontalAlignment="Left"
VerticalAlignment="Top"
/>
</Grid>
All four elements are given the entire content area in
which to reside:
The StackPanel Stack
Here are the same four elements in a
StackPanel,
which is nested in the content grid:
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<StackPanel
Name="stackPanel"
Orientation="Vertical">
<TextBlock
Text="TextBlock aligned
at right bottom"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
/>
<Image
Source="Images/BuzzAldrinOnTheMoon.png"
/>
<Ellipse
Stroke="{StaticResource
PhoneAccentBrush}"
StrokeThickness="12" />
<TextBlock
Text="TextBlock aligned
at left top"
HorizontalAlignment="Left"
VerticalAlignment="Top"
/>
</StackPanel>
</Grid>
By default, the StackPanel arranges its children in a stack from
top to bottom. The children do not overlap:
The Orientation
property of StackPanel
is set to a member of the
Orientation enumeration, either
Horizontal or
Vertical. The default
is Vertical, but the
StackPanelWithFourElements program toggles the
StackPanel orientation
when you tap the screen. Here's the code to do it:
protected
override void
OnManipulationStarted(ManipulationStartedEventArgs
args)
{
stackPanel.Orientation =
stackPanel.Orientation == System.Windows.Controls.Orientation.Vertical
? System.Windows.Controls.Orientation.Horizontal
: System.Windows.Controls.Orientation.Vertical;
args.Complete();
args.Handled = true;
base.OnManipulationStarted(args);
}
One tap and the elements are arranged from left to right:
Text Concatenation with
StackPanel
A StackPanel
with a horizontal orientation can concatenate text. This is demonstrated
in the TextConcatenation project:
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<StackPanel
Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="{StaticResource
PhoneAccentBrush}">
<TextBlock
Text="Two
" />
<TextBlock
Text="plus
" />
<TextBlock
Text="two
" />
<TextBlock
Text="equals
" />
<TextBlock
Text="four!"
/>
</StackPanel>
</Grid>
Here it is:
An easier solution is to put the
StackPanel in a
Border element, and move all the alignment and
Background settings to
that Border:
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<Border
Background="{StaticResource
PhoneAccentBrush}"
Padding="12"
CornerRadius="24"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="Two
" />
<TextBlock
Text="plus
" />
<TextBlock
Text="two
" />
<TextBlock
Text="equals
" />
<TextBlock
Text="four!"
/>
</StackPanel>
</Border>
</Grid>
Now you get a nice comfortable background with rounded corners:
Nested Panels
It's possible to nest one
StackPanel in another, which makes most sense if
they're of different orientations. Here's a program with two verticals in one
horizontal:
<StackPanel
Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel>
<TextBlock
Text="Panel"
FontWeight="Bold"
TextDecorations="Underline"
/>
<TextBlock
Text="StackPanel"
/>
<TextBlock
Text="Canvas"
/>
<TextBlock
Text="Grid"
/>
</StackPanel>
<StackPanel
Margin="12
0 0 0">
<TextBlock
Text="Properties"
FontWeight="Bold"
TextDecorations="Underline"
/>
<TextBlock
Text="Orientation"
/>
<TextBlock
Text="Left,
Top, ZIndex" />
<TextBlock
Text="RowDefinitions,
ColumnDefinitions, etc" />
</StackPanel>
</StackPanel>
The single Margin
setting serves to separate the two columns just a bit:
Visibility and Layout
The UIElement
class defines a
property named
Visibility that's
handy for temporariliy hiding elements that you don't want to be visible
all the time. The
Visibility property is not a Boolean, however. It's of type Visibility,
an enumeration with two members, Visible and Collapsed.
In the previous program, set the Visibility property on one of the
elements:
<TextBlock Text="Left, Top, ZIndex"
Visibility="Collapsed" />
Two ScrollViewer Applications
Actually, on Windows Phone 7, the scrollbars are more virtual than
real. You don't actually scroll the ScrollViewer with the scrollbars. You use your fingers instead. Still,
it's convenient to refer to scrollbars, so I will continue to do so. By default, the vertical scrollbar is visible and the horizontal scrollbar is
hidden, but you can change that with the VerticalScrollBarVisibility and
HorizontalScrollBarVisibility properties.
I enhanced the customary application title a little bit to put it in a
different color and make it two lines:
<StackPanel
x:Name="TitlePanel"
Grid.Row="0"
Margin="24,24,0,12">
<TextBlock
x:Name="ApplicationTitle"
Style="{StaticResource
PhoneTextNormalStyle}"
TextAlignment="Center"
Foreground="{StaticResource
PhoneAccentBrush}">
"A Telephonic Conversation"<LineBreak
/>by Mark Twain
</TextBlock>
</StackPanel>
Notice the strict division of labor: The TextBlock elements display the text;
the StackPanel provides the stacking; the ScrollViewer provides the scrolling:
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<Grid.Resources>
<Style
x:Key="paragraphStyle"
TargetType="TextBlock">
<Setter
Property="TextWrapping"
Value="Wrap" />
<Setter
Property="Margin"
Value="5" />
<Setter
Property="FontSize"
Value="{StaticResource
PhoneFontSizeSmall}"/>
</Style>
</Grid.Resources>
<ScrollViewer
Padding="5">
<StackPanel>
<TextBlock
Style="{StaticResource
paragraphStyle}">
 I consider that a conversation by telephone — when you are simply
sitting by and not taking any part in that conversation — is one of the
solemnest curiosities of this modern life. Yesterday I was writing a deep
article on a sublime philosophical subject while such a conversation was going
on in the room. I notice that one can always write best when somebody is talking
through a telephone close by. Well, the thing began in this way. A member of our
household came in and asked me to have our house put into communication with Mr.
Bagley's, down town. I have observed, in many cities, that the sex always shrink
from calling up the central office themselves. I
don't know why, but they do. So I touched the bell, and this talk ensued: —
</TextBlock>
<TextBlock
Style="{StaticResource
paragraphStyle}">
 <Run
FontStyle="Italic">Central
Office.</Run>
[Gruffly.] Hello!
</TextBlock>
<TextBlock
Style="{StaticResource
paragraphStyle}">
 <Run
FontStyle="Italic">I.</Run>
Is it the Central Office?</TextBlock>
…
<TextBlock
Style="{StaticResource
paragraphStyle}"
TextAlignment="Right"><Run
FontStyle="Italic">Atlantic
Monthly</Run>,
June 1880</TextBlock>
</StackPanel>
</ScrollViewer>
</Grid>
By default, ScrollViewer
provides vertical scrolling. The control
responds to touch, so you can easily scroll through and read the whole story.
The PublicClasses program coming up next also has a
ScrollViewer
containing a vertical StackPanel,
but it fills up that StackPanel
entirely in code. Using reflection, the code-behind file
obtains all the public classes exposed by the System.Windows, Microsoft.Phone,
Microsoft.Phone.Controls, and Microsoft.Phone.Controls.Maps assemblies, and
lists them in a class hierarchy.
In preparation for this job, the XAML file contains an
empty StackPanel
identified by name:
<Grid
x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<ScrollViewer
HorizontalScrollBarVisibility="Auto">
<StackPanel
Name="stackPanel" />
</ScrollViewer>
</Grid>
The code-behind file makes use of a separate little
class named ClassAndChildren
to store the tree-structured classes:
using
System;
using
System.Collections.Generic;
namespace
PublicClasses
{
class
ClassAndChildren
{
public ClassAndChildren(Type
parent)
{
Type = parent;
SubClasses =
new List<ClassAndChildren>();
}
public Type
Type { set; get;
}
public
List<ClassAndChildren>
SubClasses { set; get;
}
}
}
The program creates a
ClassAndChildren
object for each class that is displayed in the tree, and each
ClassAndChildren
object contains a List
object with all the classes that derive from that class.
Here's the complete code portion of the
MainPage class. It
needs a using directive for
System.Reflection.
namespace PublicClasses
{
public partial
class MainPage
: PhoneApplicationPage
{
Brush accentBrush;
public MainPage()
{
InitializeComponent();
accentBrush = this.Resources["PhoneAccentBrush"]
as Brush;
// Get all assemblies
List<Assembly>
assemblies = new
List<Assembly>();
assemblies.Add(Assembly.Load("System.Windows"));
assemblies.Add(Assembly.Load("Microsoft.Phone"));
assemblies.Add(Assembly.Load("Microsoft.Phone.Controls"));
assemblies.Add(Assembly.Load("Microsoft.Phone.Controls.Maps"));
// Set root object (use
DependencyObject for shorter list)
Type typeRoot =
typeof(object);
// Assemble total list of public classes
List<Type>
classes = new
List<Type>();
classes.Add(typeRoot);
foreach (Assembly
assembly in assemblies)
foreach (Type
type in assembly.GetTypes())
if (type.IsPublic &&
type.IsSubclassOf(typeRoot))
classes.Add(type);
// Sort those classes
classes.Sort(TypeCompare);
// Now put all those sorted classes
into a tree structure
ClassAndChildren
rootClass = new
ClassAndChildren(typeRoot);
AddToTree(rootClass, classes);
// Display the tree
Display(rootClass, 0);
}
int TypeCompare(Type
t1, Type t2)
{
return
String.Compare(t1.Name, t2.Name);
}
// Recursive method
void AddToTree(ClassAndChildren
parentClass, List<Type>
classes)
{
foreach (Type
type in classes)
{
if (type.BaseType ==
parentClass.Type ||
(type.BaseType != null
&& type.BaseType.IsGenericType && !type.BaseType.IsGenericTypeDefinition &&
type.BaseType.GetGenericTypeDefinition() ==
parentClass.Type))
{
ClassAndChildren
subClass = new
ClassAndChildren(type);
parentClass.SubClasses.Add(subClass);
AddToTree(subClass, classes);
}
}
}
// Recursive method
void Display(ClassAndChildren
parentClass, int indent)
{
string str1 =
String.Format("{0}{1}{2}{3}",
new
string(' ',
indent * 4),
parentClass.Type.Name,
parentClass.Type.IsAbstract ?
" (abstract)" :
"",
parentClass.Type.IsSealed ?
" (sealed)" :
"");
string str2 =
" " + parentClass.Type.Namespace;
TextBlock txtblk =
new TextBlock();
txtblk.Inlines.Add(str1);
txtblk.Inlines.Add(new
Run
{
Text = str2,
Foreground = accentBrush
});
stackPanel.Children.Add(txtblk);
foreach (ClassAndChildren
child in parentClass.SubClasses)
Display(child, indent + 1);
}
}
}
Here's the portion of the class hierarchy showing
Panel and its
derivatives: