I wrote a little post previously about the idea of a Windows app needing to be responsive in both a couple of senses;
- a static sense – the app may end up being run on a wide set of devices with differently sized displays of varied resolutions.
- a dynamic sense – while the app is running, the user can re-size its window and cause the app to need to lay itself out differently. Also, the user could potentially drag the app to another monitor with different display settings or just change the display settings of the monitor that the app is on.
The usual way of doing this is to make use of grids and define some rows/columns of the grid to be proportionately sized and some rows/columns of the grid to size to their content or be given fixed, pixel sizes.
For the areas of the screen that are proportionately sized, you typically then use controls in those areas that can do something with the increased/reduced screen size that is being given to them. Controls like GridViews and ListViews and so on that can display more/less content on larger/smaller screens.
As an example, here’s my “Expense MX” app which I built to get around the building up spreadsheets for the old Microsoft expenses system. Here’s a landscape view;
and in a minimal view ( essentially snapped view ) the GridView control gets hidden, a ListView control gets unhidden – it responds;
Regardless…
This is all done with Visual State Management because it’s a XAML app and that’s the technique I’d usually use – define states for the different layouts that you want and then define animations between those states. What can be “tricky” with that approach is that there are some things (e.g. the number of rows/columns in a Grid) that you can’t really animate.
Another aspect that gets tricky is you often end up defining one set of rows/columns for a Grid that represents the superset of all the rows/columns that you’ll ever need and that can get complicated as you try and define animations that target the Row/Column/RowSpan/ColumnSpan values of individual elements/sub-panels to move them into the right places for the right layouts.
One way around that is to have completely different pages for the separate layouts and to secretly navigate the parent Frame between those pages hoping that the user doesn’t spot the seams caused by that navigation. Like most things, that approach has its pros/cons too.
One thing I’ve been playing around with in my head is the idea that the Grid control might be able to take on some of the work of the Visual State Manager and let you define more than one set of rows/columns for a Grid and then have the Grid switch between those sets of rows/columns.
For example, let’s imagine I’ve got this simple layout of an image and a lot of text.
and then I drag this into a portrait layout and it displays;
Nothing so radical there and the XAML that is generating this is as below;
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <TextBlock Text="the picture" Style="{StaticResource HeaderTextBlockStyle}"></TextBlock> <Image Grid.Row="1" Margin="0,20,0,0" Source="Assets/winter.jpg" Stretch="UniformToFill" /> </Grid> <Grid Grid.Column="1" x:Name="gridText"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <TextBlock Text="the text" Style="{StaticResource HeaderTextBlockStyle}"></TextBlock> <TextBlock Margin="20,20,0,0" FontSize="24" Grid.Row="1" TextWrapping="WrapWholeWords" TextTrimming="WordEllipsis"> Lorem ipsum... </TextBlock> </Grid> </Grid>
But the sharp-eyed among you would notice that the root Grid here does not have any rows/columns defined for it which means that it can’t possibly be producing the layout that I showed in the 2 pictures. What I’ve done with that root Grid as an experiment is to add my own attached property which allows me to define multiple sets of rows/columns for the grid depending on whether it thinks that it is in Landscape/Portrait/Minimal layout mode. That extra piece of XAML looks a little as below where the mt: namespace is mapped to my own code.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <!-- Multiple layouts for the grid --> <mt:FlexGrid.Layout> <!-- So far, I've failed to find an API that tells me whether this app is using 500px or 320px as its minimum size so hence the property here --> <mt:FlexGridLayout MinimalLayoutSize="500"> <!-- the layout for landscape mode --> <mt:FlexGridLayout.LandscapeLayout> <mt:FlexGridLayoutDefinition> <!-- which elements do we move around going in to landscape mode? --> <!-- note that there is also a Type="Toggle" which toggles an element's visibility as we go into this particular mode. Note also that using the ElementName could be bettered by allowing Element binding --> <mt:FlexGridLayoutDefinition.Changes> <mt:FlexGridLayoutPositionChange ElementName="gridText" Type="Move" Row="0" Column="1" /> </mt:FlexGridLayoutDefinition.Changes> <!-- what rows do we want in the grid in landscape mode? --> <mt:FlexGridLayoutDefinition.RowDefinitions> <RowDefinition /> </mt:FlexGridLayoutDefinition.RowDefinitions> <!-- what columns do we want in the grid in landscape mode? --> <mt:FlexGridLayoutDefinition.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </mt:FlexGridLayoutDefinition.ColumnDefinitions> </mt:FlexGridLayoutDefinition> </mt:FlexGridLayout.LandscapeLayout> <!-- and then the portrait definition --> <mt:FlexGridLayout.PortraitLayout> <mt:FlexGridLayoutDefinition> <mt:FlexGridLayoutDefinition.Changes> <mt:FlexGridLayoutPositionChange ElementName="gridText" Type="Move" Row="1" Column="0" /> </mt:FlexGridLayoutDefinition.Changes> <mt:FlexGridLayoutDefinition.RowDefinitions> <RowDefinition /> <RowDefinition /> </mt:FlexGridLayoutDefinition.RowDefinitions> <mt:FlexGridLayoutDefinition.ColumnDefinitions> <ColumnDefinition /> </mt:FlexGridLayoutDefinition.ColumnDefinitions> </mt:FlexGridLayoutDefinition> </mt:FlexGridLayout.PortraitLayout> </mt:FlexGridLayout> </mt:FlexGrid.Layout> <!-- Now comes the content which I've already included so won't repeat here --> </Grid>
At the time of writing, this brings in a bunch of classes that I really just threw together as a bit of a sketch so there’s lots of things to fix and there’s lots of things that could be improved.
It’s more classes than I’d like. They look like this on a diagram;
and I’ll bundle the source with the app such that you can poke around in it ( albeit, it’s very rough and ready ).
One of the downsides of this approach is that you have to play around a little bit with the XAML designer to generate those rows/columns because the designer is not really lined up with the idea that you want to design a Grid with multiple definitions of what its rows/columns look like but it’s kind of do-able.
As another, more simple example let’s just say that I want to display a red rectangle in the centre of the screen in portrait mode and a red rectangle above a blue rectangle in landscape mode. Something like;
and that’s done with a lump of XAML;
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <mt:FlexGrid.Layout> <mt:FlexGridLayout> <mt:FlexGridLayout.LandscapeLayout> <mt:FlexGridLayoutDefinition> <mt:FlexGridLayoutDefinition.Changes> <mt:FlexGridLayoutChange Type="Toggle" ElementName="blue" /> </mt:FlexGridLayoutDefinition.Changes> <mt:FlexGridLayoutDefinition.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </mt:FlexGridLayoutDefinition.RowDefinitions> <mt:FlexGridLayoutDefinition.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </mt:FlexGridLayoutDefinition.ColumnDefinitions> </mt:FlexGridLayoutDefinition> </mt:FlexGridLayout.LandscapeLayout> <mt:FlexGridLayout.PortraitLayout> <mt:FlexGridLayoutDefinition> <mt:FlexGridLayoutDefinition.Changes> <mt:FlexGridLayoutChange Type="Toggle" ElementName="blue" /> </mt:FlexGridLayoutDefinition.Changes> <mt:FlexGridLayoutDefinition.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </mt:FlexGridLayoutDefinition.RowDefinitions> <mt:FlexGridLayoutDefinition.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </mt:FlexGridLayoutDefinition.ColumnDefinitions> </mt:FlexGridLayoutDefinition> </mt:FlexGridLayout.PortraitLayout> </mt:FlexGridLayout> </mt:FlexGrid.Layout> <Rectangle Width="192" Height="192" Fill="Red" Grid.Row="1" Grid.Column="1"/> <Rectangle x:Name="blue" Width="192" Height="192" Fill="Blue" Grid.Row="2" Grid.Column="1" Visibility="Collapsed"/> </Grid>
although at this point I stared hard that XAML and started to think that there’s quite a lot of duplication in there so perhaps this is not really improving much on the VSM approach
So…perhaps best left to the cupboard marked “experiments” but I enjoyed playing around with it a little and it was good to explore whether there’s value in this kind of approach versus working with the VSM and defining more complex Grids which represent the union of all possible layouts and then moving elements around within those grids by animating their Row, Column, RowSpan, ColumnSpan values.
Here’s the source for the simple example above. Very rough and ready as I made it in around 45 minutes (it’s a Windows 8.1 project).