Intricacies of Layout in Windows Phone 7

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.


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:

  1. Background of type Brush
  2. Children of type UIElementCollection
  3. 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:

n1.gif

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:

n2.gif

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:

n3.gif

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:

n4.gif

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:

n5.gif

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:

n6.gif

Visibility and Layout

The UIElementclass defines a property named Visibility that's handy for temporariliy hiding elements that you don't want to be visible all the time. TheVisibility 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}"> &#x2003;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}"> &#x2003;<Run FontStyle="Italic">Central Office.</Run> [Gruffly.] Hello!
                       
</TextBlock>
                        <TextBlock Style="{StaticResource paragraphStyle}"> &#x2003;<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.

n7.gif

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:

n8.gif