Monday, October 31, 2011

Layout adjustments Snapping and OrientationChanges in Metro Applications

If you have read my first article on Windows 8 metro styled application, you might already know about the working principle of it. I have talked about the capabilities for an application and settings which end user can configure for an application. In this post I will take a look at the basic layout structure that one needs to follow while creating an application in WinRT Metro styles so that it is best suited to perform well.

Layout is the most important part of any application. The best design for an application gets more credit and love from the users than applications that are designed bad. Metro style applications runs in full screen. Your application does not include a  Title Bar, status bar or anything. Microsoft gave us some of the basic layout guidelines that one needs to follow. Lets talk about them here to make you understand how you should layout your application in Metro Applications to utilize maximum flexibility of it.



Lets start defining a layout and explain each section.

<UserControl x:Class="LayoutSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Loaded="UserControl_Loaded"
    d:DesignHeight="768" d:DesignWidth="1366">
    
    <Grid x:Name="LayoutRoot" Background="#FF0C0C0C">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="320"/> 
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Rectangle x:Name="rctSpacerLeft" Width="120" />
        <Border x:Name="brdHeader" Height="140" Grid.ColumnSpan="2" Grid.Column="1">
            <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Text="My Basic Layout"
                       FontSize="40"/>
        </Border>

        <ListBox x:Name="lstBasicItems" Grid.Row="1"
                 Grid.Column="1">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical" >
                        <TextBlock Text="{Binding Name}" HorizontalAlignment="Center"/>
                        <TextBlock Text="{Binding Description}" HorizontalAlignment="Left"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Grid x:Name="grdLayoutRight" Grid.Row="1" Grid.Column="2" DataContext="{Binding ElementName=lstBasicItems, Path=SelectedItem}">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock Text="Name" FontSize="14" />
            <TextBox Text="{Binding Name}" Grid.Column="1" />
            <TextBlock Text="Description" FontSize="14" Grid.Row="1" />
            <TextBox Text="{Binding Description}" Grid.Column="1" Grid.Row="1" />
        </Grid>
    </Grid>
    
</UserControl>

This is a basic layout of an application which shows a list of data in the left hand side in a ListBox and the Right hand side shows few textboxes to edit the data. Following the convension, each application should have a left margin of 120 px. In this layout the First Column of the LayoutRoot grid is specified Auto. Auto indicates that the portion of the screen will be sized to content. In this section I specified a Rectangle with width 120px.
Again the Application needs to have a Heading. I have specified the heading in the First Row of the Grid. The ListBox is taken as 320px, this is because when the application is snapped, it takes 320px of size based on the design resolution. The Grid is *(star) Sized, hence it will adjust the size based on the available space.

Now if you run this code, you will see something like this :


Here the ListBox takes 320 px of size and the Grid takes the rest of the available space. Now if you snap the application with another application, it will not adjust the size automatically. Lets write code so that it does.
public MainPage()
{
    InitializeComponent();
    var currentView = ApplicationLayout.GetForCurrentView();
    currentView.LayoutChanged += new TypedEventHandler<ApplicationLayout, ApplicationLayoutChangedEventArgs>(currentView_LayoutChanged);
}

void currentView_LayoutChanged(ApplicationLayout sender, ApplicationLayoutChangedEventArgs args)
{
    switch (args.Layout)
    {
        case ApplicationLayoutState.Filled:
        case ApplicationLayoutState.FullScreen:
            this.rctSpacerLeft.Visibility = Windows.UI.Xaml.Visibility.Visible;
            this.brdHeader.Visibility = Windows.UI.Xaml.Visibility.Visible;
            break;
        case ApplicationLayoutState.Snapped:
                this.rctSpacerLeft.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                this.brdHeader.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
            break;
    }
}

So here you can see I have made the rctSpacerLeft and brdHeader visible only when the state of the application is Filled (that means when some other application is snapped with the application) or when the application is FullScreen (that means when the whole application is showed up). But these elements need to be Collapsed whenever the application is in Snapped mode(when the current application is snapped).

You should already know, basically applications has 3 states. One when the size of the application is 320px long (called snapped ), when the application is filled most of the space just leaving 320px of the entire screen for some other application or when the application is full screen.


In the above image, I have just snapped the Tweet@rama that comes preinstalled with Windows 8. You can see the Grid is sized smaller than the previous one sharing the the 320 px of the entire screen to the other application.  Here the Margin is kept intact.

But when the current application is snapped state, we need to show the entire thing in 320px. So the application needs to adjust itself to be meaningful to the User.

When I snap the current application with Tweet@rama, you can see, it removes the header, the spacer margin and the Grid that is auto sized.

Responding to Rotation

Responding to Ration of the device is another important consideration that you should always keep in mind while designing a Metro styled application. To know the Native Orientation of the device when the application gets started, you can use the DisplayProperties.CurrentOrientation property.

Lets modify the code to show the Orientation of current device in header. By default the orientation is Landscape.


The above image shows up the current Orientation of the device. Lets edit the xaml a bit to introduce it.

<Border x:Name="brdHeader" Height="140" Grid.ColumnSpan="2" Grid.Column="1">
      <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center"
                 FontSize="40">
          <Run Text="My Basic Layout" />
          <Run x:Name="runOrientation" />
      </TextBlock>
</Border>
I have added a couple of Run statement to allow changing the Orientation text in one particular Run block inside the TextBlock. Now lets handle the OrientationChanged Event of DisplayProperties to get the current Orientation. To do this lets add this code :
public MainPage()
{
    InitializeComponent();
    var currentView = ApplicationLayout.GetForCurrentView();

    currentView.LayoutChanged += new TypedEventHandler<ApplicationLayout, ApplicationLayoutChangedEventArgs>(currentView_LayoutChanged);

    DisplayProperties.OrientationChanged += new DisplayPropertiesEventHandler(DisplayProperties_OrientationChanged);
}

void DisplayProperties_OrientationChanged(object sender)
{
    this.WriteOrientation();
}
void WriteOrientation()
{
    this.runOrientation.Text = string.Format(" ({0})", DisplayProperties.CurrentOrientation);
}
Here we initialize the new event handler for OrientationChanged in constructor of MainPage, which will call the event whenever the orientation of the device is changed. DisplayProperties also lists many other display related properties like ColorProfileChanged, LogicalDpiChanged etc but they are rarely needed. You can read them from here.

Download Sample Project

Conclusion

To write a general purpose application, it is very important that your application maintains proper layout when the device changes its size or shape. The objective of the post is to get you through understanding how you could form a good layout for your application. I hope you have already got the basics of how to layout the application in Metro Environments.

I hope this post helped you.

Thank you for reading.


No comments:

Post a Comment

Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.

Author's new book

Abhishek authored one of the best selling book of .NET. It covers ASP.NET, WPF, Windows 8, Threading, Memory Management, Internals, Visual Studio, HTML5, JQuery and many more...
Grab it now !!!