Question

I am making a Win Store App, and facing the issue to handle orientation.

There is a grid, I want to modify the design of it when the orientation is changed. The stack Panels in both the sides (in Landscape View) should goto Top and Bottom in Portrait View.

Currently I have created two grids with all the controls and adjust them according to the orientation, and switching the visibility property, using VisualStateManager.

But I want to achieve this with a single grid, any suggestion will be a great help for me.

The below image is the concept design of the app: enter image description here

Was it helpful?

Solution

Doing this with a single Grid is difficult, but possible.

Firstly, what you probably want to do is worry less about orientation and more about horizontal width. That way, if the user snaps the app in such a way that there isn't much horizontal space (similar to portrait mode), you can display a similar experience.

Now, onto the Grid.

The key here is that you are moving from a Grid with, in essence, 3 Columns to one with 1 Column. As such, you will need to modify the ColumnSpan Property of your cells.

What you have drawn so far has 3 Columns, each with a Width of GridLength *. Start there.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>    
</Grid>

Next, we want to set up the Rows. It's better to make the number of Rows the largest that you'll need, which is by my count 6. If you set them all to Auto, then the empty Rows (in the Landscape orientation) will disappear.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>    
    <Grid.RowDefinitions>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>    
</Grid>

Now, to create some basic contents. I'm just going to create a bunch of different StackPanels and name them what you have contained in them.

I am setting the default Grid/Row values to that of Landscape. I am also using two rows for the 'Second Row' (containing StackPanels 1 and 2). Both StackPanels will have a RowSpan of 2 to compensate.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>    
    <Grid.RowDefinitions>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
        <RowDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>    
    <StackPanel Name="HeaderRow" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"/>
    <StackPanel Name="StackPanel1" Grid.Row="1" Grid.Column="0" Grid.RowSpan="2"/>
    <StackPanel Name="Content" Grid.Row="1" Grid.Column="1"/>
    <StackPanel Name="BlueSection" Grid.Row="2" Grid.Column="1"/>
    <StackPanel Name="StackPanel2" Grid.Row="1" Grid.Column="2" Grid.RowSpan="2"/>
    <StackPanel Name="FooterRow" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3"/>
</Grid>

Now we have a nice definition for your Landscape orientation. It will automatically size vertically based on the content within it as well, though you are free to do whatever minimum sizing you want.

Now that we've done this, we just need to create some VisualStates for our different sizes. In one VisualState we will be defining exactly what we've already done. In the other, we will be creating a new one. I am naming them 'Landscape' and 'Portrait' for now, but I suggest doing something like 'Mid' and 'Wide', and then defining which is used based on the Bounds given. I have omitted the previous definitions for the sake of space.

Technically, we don't need to include the Landscape definition, but I'm including it as an additional example.

Note: In the second picture, you have StackPanel2 below Footer. I have reflected this below. If you need that changed, just change the row definitions of StackPanel2 to 4 and of Footer to 5.

<Grid>
    ...
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="OrientationStates">
            <!-- What we've already defined -->
            <VisualState x:Name="Landscape">
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.RowSpan)" Storyboard.TargetName="StackPanel1">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.ColumnSpan)" Storyboard.TargetName="StackPanel1">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>

                <!-- Stack Panel 2 -->
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.RowSpan)" Storyboard.TargetName="StackPanel2">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.ColumnSpan)" Storyboard.TargetName="StackPanel2">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="StackPanel2">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Column)" Storyboard.TargetName="StackPanel2">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
                 </ObjectAnimationUsingKeyFrames>

                <!-- Content -->
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="Content">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Column)" Storyboard.TargetName="Content">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.ColumnSpan)" Storyboard.TargetName="Content">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>

                <! -- Blue Section -->
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="BlueSection">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Column)" Storyboard.TargetName="BlueSection">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.ColumnSpan)" Storyboard.TargetName="BlueSection">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>

                <!-- Footer -->
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="Footer">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="3"/>
                 </ObjectAnimationUsingKeyFrames>
            </VisualState>

            <!-- The New Section -->
            <VisualState x:Name="Portrait">
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.RowSpan)" Storyboard.TargetName="StackPanel1">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.ColumnSpan)" Storyboard.TargetName="StackPanel1">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="3"/>
                 </ObjectAnimationUsingKeyFrames>

                <!-- Stack Panel 2 -->
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.RowSpan)" Storyboard.TargetName="StackPanel2">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.ColumnSpan)" Storyboard.TargetName="StackPanel2">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="3"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="StackPanel2">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="5"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Column)" Storyboard.TargetName="StackPanel2">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                 </ObjectAnimationUsingKeyFrames>

                <!-- Content -->
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="Content">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Column)" Storyboard.TargetName="Content">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.ColumnSpan)" Storyboard.TargetName="Content">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="3"/>
                 </ObjectAnimationUsingKeyFrames>

                <! -- Blue Section -->
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="BlueSection">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="3"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Column)" Storyboard.TargetName="BlueSection">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
                 </ObjectAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.ColumnSpan)" Storyboard.TargetName="BlueSection">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="3"/>
                 </ObjectAnimationUsingKeyFrames>

                <!-- Footer -->
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Grid.Row)" Storyboard.TargetName="Footer">
                     <DiscreteObjectKeyFrame KeyTime="0" Value="4"/>
                 </ObjectAnimationUsingKeyFrames>
            </VisualState>
        </VisualStateGroup>
    <VisualStateManager.VisualStateGroups>
</Grid>

You're almost done. All you need left is for your Page to listen for WindowSizeChanged and respond to it! It needs to get a reference to your Grid. As such, I suggest adding x:Name="ContentGrid" or something similar to the base Grid definition. Such as:

<Grid x:Name="ContentGrid">

My example for WindowSizeChanged will assume you have done this.

private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs windowSizeChangedEventArgs)
{
    if (windowSizeChangedEventArgs.Size.Width < 800)
    {
        VisualStateManager.GoToState(ContentGrid, "Portrait", true);
    }
    else
    {
        VisualStateManager.GoToState(ContentGrid, "Landscape", true);
    }
}

Then, in your Page's OnNavigatedTo and OnNavigatedFrom...

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    Window.Current.SizeChanged += WindowSizeChanged;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    Window.Current.SizeChanged -= WindowSizeChanged;
}

Make sure you remove the SizeChanged event handler on navigating from the page, otherwise your app will retain a copy of your page in memory indefinitely. This is how to create a fun memory leak.

And that's it! On size changed, your main page will notify your Grid that it needs to reconfigure itself and it will do so in the way described. Obviously you won't be able to directly paste this in, but you should be able to use a lot of it. Make sure to change the TargetNames to your named controls.

Hope this helps and happy coding!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top