Skip to content

Enable Grid to dynamically switch layouts #232

@nandin-borjigin

Description

@nandin-borjigin

Discussed in https://github.com/CommunityToolkit/WindowsCommunityToolkit/discussions/4470

Originally posted by nandin-borjigin January 30, 2022

Usecase

Imagine we want to show a list of items and each list item is a 3-column grid.

+---+-------------+-------------------------------+
| 1 | Lorem Ipsum | Lorem ipsum dolor sit amet... |
+---+-------------+-------------------------------+

Now, we want to be responsive to the window size changes and decide to collapse this list item into 2 rows when the window width is narrow.

+-------+-----------------------+
|   1   |      Lorem Ipsum      |
+-------+-----------------------+
| Lorem ipsum dolor sit amet... |
+-------------------------------+

Potential approach

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="Auto" x:Name="SecondColumn"/>
        <ColumnDefinition Width="*" x:Name="ThirdColumn"/>
    </Grid.ColumnDefinitions>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="NarrowState">
                <VisualState.StateTriggers .../> <!-- Trigger implementation omitted -->
                <VisualState.Setters>
                    <Setter Target="Description.(Grid.Row)" Value="1"/>
                    <Setter Target="Description.(Grid.Column)" Value="0"/>
                    <Setter Target="Description.(Grid.ColumnSpan)" Value="2"/>
                    <Setter Target="ThridColumn.Width" Value="0"/>
                    <Setter Target="ThridColumn.Width" Value="*"/>
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <TextBlock x:Name="Number">1</TextBlock>
    <TextBlock x:Name="Title">Lorem Ipsum</TextBlock>
    <TextBlock x:Name="Description">Lorem ipsum dolor sit amet...</TextBlock>
</Grid>

Problem

It's not really intuitive to declare a 2 x 3 grid layout when what we really need is either 1 x 3 or 2 x 2, depending on the window width. In above approach, we're imperatively dictating how the narrow layout would be instead of declaratively describing it and let the library handles the dirty work.
Further, another problem here is the visual state setter would quickly become maintenance hell when the layout becomes slightly complex. Just imagine what if we want to add column spacing to the grid.

Normal
+---+:+-------------+:+-------------------------------+
| 1 |:| Lorem Ipsum |:| Lorem ipsum dolor sit amet... |
+---+:+-------------+:+-------------------------------+

Narrow
+-----+:+-----------------------+:+
|  1  |:|     Lorem Ipsum       |:|
+-----+:+-----------------------+:+
| Lorem ipsum dolor sit amet... |:|
+-------------------------------+:+

This is what we would get if we simply add ColumnSpacing="10" on the Grid element. The gutter between 2nd and 3rd column is still there since the 3rd column is effectively there even if it's of 0 width, so the spacing exists. To better workaround this, we need to modify the visual state setters. We need to make the Title element span 2nd and 3rd columns and make the Description element span 3 columns. And this is only for a minor change like adding a column spacing.

Proposal

Provide Grid with a set of attached properties to enable programmers to declaratively describe the possible layouts and easily switch between them.

<Grid x:Name="RootGrid" GirdExentions.ActiveLayout="Normal">
    <!-- Declaratively define the possible layouts. -->
    <!-- GridExtensions.Layouts is a dictionary of GridLayoutDefinition -->
    <GridExtensions.Layouts>
        <GridLayoutDefinition x:Key="Normal">
            <!-- A GridLayoutDefinition consists of -->
            <!-- row definitions, column definitions and an area definition -->
            <GridLayoutDefinition.RowDefinitions>
                <RowDefinition Height="Auto"/>
            </GridLayoutDefinition.RowDefinitions>
            <GridLayoutDefinition.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </GridLayoutDefinition.ColumnDefinitions>
            <!-- Area definition just simply puts down -->
            <!-- children names in desired order -->
            Number Title Description
        </GridLayoutDefinition>
        <GridLayoutDefinition x:Key="Narrow">
            <GridLayoutDefinition.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </GridLayoutDefinition.RowDefinitions>
            <GridLayoutDefinition.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </GridLayoutDefinition.ColumnDefinitions>
            Number      Title; <!-- semicolon is used to separate differnt rows -->
            Description Description <!-- row/column span is expressed by repeating the elment name -->
        </GridLayoutDefinition>
    </GridExtensionstensions.Layouts>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="NarrowState">
                <VisualState.StateTriggers .../> <!-- Trigger implementation omitted -->
                <VisualState.Setters>
                    <!-- Only ActiveLayout property on the root grid needs to change according to the visual state -->
                    <Setter Target="RootGrid.(GridExtensions.ActiveLayout)" Value="Narrow">
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <TextBlock x:Name="Number">1</TextBlock>
    <TextBlock x:Name="Title">Lorem Ipsum</TextBlock>
    <TextBlock x:Name="Description">Lorem ipsum dolor sit amet...</TextBlock>
</Grid>
```</div>

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions