Working with Silverlight and XNA windows phone 7 application Orientation


This chapter is taken from book "Programming Windows Phone 7" by Charles Petzold published by Microsoft press. http://www.charlespetzold.com/phone/index.html

When you run the SilverlightHelloPhone program, and you turn the phone or emulator sideways, you'll discover that the display doesn't change to accommodate the new orientation, To fix that problem you have to make change in the root PhoneApplicationPage tag, of MainPage.xaml change the attribute.

SupportedOrientations="Portrait" to: SupportedOrientations="PortraitOrLandscape"

SupportedOrientations is a property of PhoneApplicationPage. It's set to a member of the SupportedPageOrientation enumeration, either Portrait, Landscape, or PortraitOrLandscape. Now when you turn the phone or emulator sideways, the contents of the page shift around accordingly:

1.gif

Two of the most important properties in working with dynamic layout are HorizontalAlignment and VerticalAlignment. on the other hand, if you now needed to stack a bunch of text strings, you would probably find it straightforward in XNA, but not so obvious in Silverlight. Rest assured that there are ways to organize elements in Silverlight. A whole category of elements called panels exist solely for that purpose. You can even position elements based on pixel coordinates, if that's your preference.

Normally a Grid organizes its content into cells identified by row and column, but this program puts nine TextBlock elements in a single-cell Grid to demonstrate the use of HorizontalAlignment and VerticalAlignment in nine different combinations:

Example

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <TextBlock Text="Top-Left"
            VerticalAlignment="Top"
            HorizontalAlignment="Left" />
        <TextBlock Text="Top-Center"
            VerticalAlignment="Top"
            HorizontalAlignment="Center" />
        <TextBlock Text="Top-Right"
            VerticalAlignment="Top"
            HorizontalAlignment="Right" />
        <TextBlock Text="Center-Left"
            VerticalAlignment="Center"
            HorizontalAlignment="Left" />
        <TextBlock Text="Center"
            VerticalAlignment="Center"
            HorizontalAlignment="Center" />
        <TextBlock Text="Center-Right"
            VerticalAlignment="Center"
            HorizontalAlignment="Right" />
        <TextBlock Text="Bottom-Left"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Left" />
        <TextBlock Text="Bottom-Center"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Center" />
        <TextBlock Text="Bottom-Right"
            VerticalAlignment="Bottom"
            HorizontalAlignment="Right" />
</
Grid>

Output

2.gif

Although this screen appears to show all the combinations, the program does not actually show the default settings of the HorizontalAlignment and VerticalAlignment properties. The default settings are enumeration members named Stretch. The HorizontalAlignment and VerticalAlignment properties are very important in the layout system in Silverlight. So is Margin. Try adding a Margin setting to the first TextBlock in this program:

<TextBlock Text="Top-Left"
    VerticalAlignment="Top"
    HorizontalAlignment="Left" 
    Margin="100" />

Now there's a 100-pixel breathing room between the TextBlock and the left and top edges of the client area. The Margin property is of type Thickness, a structure that has four properties named Left, Top, Right, and Bottom. If you specify only one number in XAML, that's used for all four sides.

Padding: is also of type Thickness, and when used with the TextBlock, Padding is visually indistinguishable from Margin. But they are definitely different: Margin is space on the outside of the TextBlock; Padding is space inside the TextBlock not occupied by the text itself.

You're second guessing the size of the TextBlock without knowing as much about the element as the TextBlock itself. In some cases, setting Width and Height is appropriate, but not here. The Width and Height properties are of type double, and the default values are those special floating-point values called Not a Number or NaN.

Some useful events are also available for obtaining information involving element sizes. The Loaded event is fired when visuals are first arranged on the screen; SizeChanged is supported by elements to indicate when they've changed size; LayoutUpdated is useful when you want notification that a layout cycle has occurred, such as occurs when orientation changes.

You can associate a particular event with an event handler right in XAML, but the actual event handler must be implemented in code. When you type an event name in XAML (such as SizeChanged) Visual Studio will offer to create an event handler for you. That's what I did with the SizeChanged event for the content grid:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"
        SizeChanged="ContentPanel_SizeChanged">
        <TextBlock Name="txtblk"
        HorizontalAlignment="Center"
        VerticalAlignment="Center" />
</
Grid>

Assigning names to elements is one of two primary ways in which code and XAML interact. The second way is for the element defined in XAML to fire an event that is handled in code. Here's the handler for the SizeChanged event of the content grid as Visual Studio created it:

Example

private
void ContentPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
    txtblk.Text = String.Format("ContentPanel size: {0}\n" +
    "TitlePanel size: {1}\n" +
    "LayoutRoot size: {2}\n" +
    "MainPage size: {3}\n" +
    "Frame size: {4}",
    e.NewSize,
    new Size(TitlePanel.ActualWidth,
    TitlePanel.ActualHeight),
    new Size(LayoutRoot.ActualWidth,
    LayoutRoot.ActualHeight),
    new Size(this.ActualWidth, this.ActualHeight),
    Application.Current.RootVisual.RenderSize);
}

The first SizeChanged event occurs when the page is created and laid out, that is, when the content grid changes size from 0 to a finite value:

3.gif

Orientation Events: To set SupportedOrientations to PortraitOrLandscape, and try to write orientation-independent applications, Obviously there is more to handling orientation changes than just setting the SupportedOrientations property. If you need to perform any special handling, both PhoneApplicationFrame and PhoneApplicationPage include OrientationChanged events. PhoneApplicationPage supplements that event with a convenient and equivalent protected overridable method called OnOrientationChanged.

The MainPage class in the SilverlightOrientationDisplay project shows how to override OnOrientationChanged, but what it does with this information is merely to display the current orientation. The content grid in this project contains a simple TextBlock:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <TextBlock Name="txtblk"
        HorizontalAlignment="Center"
        VerticalAlignment="Center" />
</
Grid>

Here's the complete code-behind file. The constructor initializes the TextBlock text with the current value of the Orientation property, which is a member of the PageOrientation enumeration:

using
System.Windows.Controls;
using Microsoft.Phone.Controls;
namespace SilverlightOrientationDisplay
{
    public partial class MainPage : PhoneApplicationPage
   
{
        public MainPage()
        {
            InitializeComponent();
            txtblk.Text = Orientation.ToString();
        }
        protected override void OnOrientationChanged(OrientationChangedEventArgs args)
        {
            txtblk.Text = args.Orientation.ToString();
            base.OnOrientationChanged(args);
        }
    }
}

XNA Orientation

By default, XNA for Windows Phone is set up for a landscape orientation, perhaps to be compatible with other screens on which games are played. If you prefer designing your game for a portrait display, it's easy to do that. In the constructor of the Game1 class of XnaHelloPhone, try inserting the following statements:

graphics.PreferredBackBufferWidth = 320;
graphics.PreferredBackBufferHeight = 480;

It's also possible to have your XNA games respond to orientation changes, but they'll definitely have to be restructured a bit. The simplest type of restructuring to accommodate orientation changes is demonstrated in the XnaOrientableHelloPhone project. The fields now include a textSize variable:

Example

public
class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    string text = "Hello, Windows Phone 7!";
    SpriteFont segoe14;
    Vector2 textSize;
    Vector2 textPosition;
    … 
}

The Game1 constructor includes a statement that sets the SupportedOrientations property of the graphics field:

public
Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
    // Allow portrait mode as well
    graphics.SupportedOrientations = DisplayOrientation.Portrait |
    DisplayOrientation.LandscapeLeft |
    DisplayOrientation.LandscapeRight;
    // Frame rate is 30 fps by default for Windows Phone.
    TargetElapsedTime = TimeSpan.FromTicks(333333);
}

4.gif

Simple Clocks (Very Simple Clocks)

you can attach handlers to events entirely in code as well. One handy class for Silverlight programs is DispatcherTimer, which periodically nudges the program with a Tick event and lets the program do some work. A timer is essential for a clock program, for example.

The content grid of the SilverlightSimpleClock project contains just a centered TextBlock:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <TextBlock Name="txtblk"
        HorizontalAlignment="Center"
        VerticalAlignment="Center" />
</
Grid>

Here's the entire code-behind file. Notice the using directive for the System.Windows.Threading namespace, which isn't included by default. That's the namespace where DispatcherTimer resides:

using System;
using System.Windows.Threading;
using Microsoft.Phone.Controls;
namespace SilverlightSimpleClock
{
    public partial class MainPage : PhoneApplicationPage
   
{
        public MainPage()
        {
            InitializeComponent();
            DispatcherTimer tmr = new DispatcherTimer();
            tmr.Interval = TimeSpan.FromSeconds(1);
            tmr.Tick += OnTimerTick;
            tmr.Start();
        }
        void OnTimerTick(object sender, EventArgs args)
        {
            txtblk.Text = DateTime.Now.ToString();
        }
    }
}

The constructor initializes the DispatcherTimer, instructing it to call OnTimerTick once every second. The event handler simply converts the current time to a string to set it to the TextBlock.

5.gif

An XNA clock program doesn't need a timer because a timer is effectively built into the normal game loop. However, the clock I want to code here won't display milliseconds so the display only needs to be updated every second. For that reason it uses the SuppressDraw method to inhibit superfluous Draw calls.

Here are the XnaSimpleClock fields:

Example

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont segoe14;
    Viewport viewport;
    Vector2 textPosition;
    StringBuilder text = new StringBuilder();
    DateTime lastDateTime;
    …
}

The LoadContent method gets the font and the viewport of the display:

protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            segoe14 = this.Content.Load<SpriteFont>("Segoe14");
            viewport = this.GraphicsDevice.Viewport;
        }

The logic to compare two DateTime values to see if the time has changed is just a little tricky because DateTime objects obtained during two consecutive Update calls will always be different because they have will have different Millisecond fields. For this reason, a new DateTime is calculated based on the current time obtained from DateTime.Now, but subtracting the milliseconds:

protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();
            // Get DateTime with no milliseconds
            DateTime dateTime = DateTime.Now;
            dateTime = dateTime - new TimeSpan(0, 0, 0, 0, dateTime.Millisecond);
            if (dateTime != lastDateTime)
            {
                text.Remove(0, text.Length);
                text.Append(dateTime);
                Vector2 textSize = segoe14.MeasureString(text); textPosition = new Vector2((viewport.Width - textSize.X) / 2, (viewport.Height - textSize.Y) / 2);
                lastDateTime = dateTime;
            }
            else
            {
                SuppressDraw();
            }
            base.Update(gameTime);
        }

If the time has not changed, SuppressDraw is called. The result: Draw is called only once per second.

DrawString also has an overload for StringBuilder:

protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Navy);
            spriteBatch.Begin();
            spriteBatch.DrawString(segoe14, text, textPosition, Color.White);
            spriteBatch.End();
            base.Draw(gameTime);
        }

Output Result

6.gif

Conclusion

I hope this article help you to understand how to managed the orientation in silverlight and xna windows phone 7 application.

Next Recommended Readings