Windows 10 introduces a new layout control called RelativePanel
, which sounds a lot like the Android relative layout system, of which dequehead
approves. He checks it out...
As Laurent demonstrates this thing can be used to persuade certain types of UI to behave in a more portrait-y or more landscape-y fashion by using AdaptiveTrigger
to change a .LeftOf
to a .Below
, for example, and allowing the layout system to re-flow the UI accordingly. This is all very nice.
However, what I would really like is something that works for larger-scale structural changes to the UI. As things stand this generally requires manipulating Grid
's attached properties such as Row
, ColumnSpan
etc. This works very well for small mobile UI designs because the column and row definitions can take proportional dimensions such as *
and 2*
, so they can arrange nicely across a range of window sizes and everything remains visible on the screen. Combined with AdaptiveTrigger
we can make a nice UI that adjusts fluidly to window size and orientation changes, without anything hard-wired. Here is an example of how you might do it (to try it out, just create a blank UWP app and replace the empty Grid
with this):
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<!-- dq: here we set the layout defaults for PortraitState -->
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftColumn" Width="*"/>
<ColumnDefinition x:Name="RightColumn" Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="TopRow" Height="*"/>
<RowDefinition x:Name="BottomRow" Height="2*"/>
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutStateGroup">
<VisualState x:Name="LandscapeState">
<VisualState.StateTriggers>
<!-- dq: landscape applies until width <= 500 effective pixels -->
<AdaptiveTrigger MinWindowWidth="501"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="BigContent.(Grid.Column)" Value="1"/>
<Setter Target="BigContent.(Grid.Row)" Value="0"/>
<Setter Target="RightColumn.Width" Value="2*"/>
<Setter Target="BottomRow.Height" Value="0"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PortraitState"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="SmallContent" Grid.Column="0" Grid.Row="0" Background="Red"/>
<Grid x:Name="BigContent" Grid.Column="0" Grid.Row="1" Background="Blue"/>
</Grid>
The big problem with this approach is that the layout is tightly coupled to the contents - I have to know all about the Row
s and Column
s that are available (not too Bad..) and also manipulate their dimensions (..Bad). It becomes very fragile once your UI gets a bit more complex, and before you know it you are giving up with the declarative approach and it all moves into C# code in the code-behind (woe is me) or ViewModel (oh not, not again!).
No good, dequehead
wants to do it all in the lovely new Blend that he has been blessed with.
So at first sight it seemed that RelativePanel
was going to solve this problem. Perhaps like this:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="LayoutStateGroup">
<VisualState x:Name="LandscapeState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="501"/>
</VisualState.StateTriggers>
<VisualState.Setters>
<!-- dq: move to the right in landscape -->
<Setter Target="BigContent.(RelativePanel.RightOf)" Value="SmallContent"/>
<Setter Target="BigContent.(RelativePanel.Below)" Value=""/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PortraitState"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<RelativePanel>
<!-- dq: dock this one to the top-left -->
<Grid x:Name="SmallContent"
RelativePanel.AlignTopWithPanel="True"
RelativePanel.AlignLeftWithPanel="True"
Background="Red" Width="300" Height="300"/>
<!-- dq: park this below in portrait -->
<Grid x:Name="BigContent"
RelativePanel.Below="SmallContent"
Background="Blue" Width="300" Height="300"/>
</RelativePanel>
</Grid>
This sort of works in the sense that the panels change their relative positions, but I have had to hard-wire the dimensions. Without further mechanics (read complexity) it is rubbish (try it). The Grid
approach, for all its faults, provides a much better solution for this use-case. What I'm after is the proportional layout capability of Grid
combined with the intuitive dynamic nature of what RelativePanel
pretends to be (I know, that's a bit unfair..). Perhaps a couple more attached properties WidthWeighting
and HeightWeighting
, like this:
<RelativePanel>
<Grid x:Name="SmallContent"
RelativePanel.WidthWeighting="*"
RelativePanel.HeightWeighting="*"
RelativePanel.AlignTopWithPanel="True"
RelativePanel.AlignLeftWithPanel="True"
Background="Red"/>
<Grid x:Name="BigContent"
RelativePanel.WidthWeighting="2*"
RelativePanel.HeightWeighting="2*"
RelativePanel.Below="SmallContent"
Background="Blue"/>
</RelativePanel>
This might be interpreted as "if BigContent
is involved in a Width
calculation, give it a weighting of 2*
", and similar for Height
(...along similar lines to android:layout_weight
).
Rather more thought required, but maybe it could be made to work. Any ideas, please leave a comment below.