Search code examples
windows-store-appswinrt-xaml

Responsive GridView


I would like to create a responsive GridView for my Windows Universal app. I want basically a GridView which columns horizontally fills whole space.

Responsive GridView wireframe

XAML should be like this, but I don't know which combination of ItemsPanelTemplate I have to use.

<Page>

<Page.Resources>
    <CollectionViewSource x:Name="groupedItemsViewSource" IsSourceGrouped="true"/>
</Page.Resources>

<GridView ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}">
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <!-- ? -->
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
    <GridView.GroupStyle>
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <!-- header -->
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
            <GroupStyle.Panel>
                <ItemsPanelTemplate>
                    <!-- ? -->
                </ItemsPanelTemplate>
            </GroupStyle.Panel>
        </GroupStyle>
    </GridView.GroupStyle>
    <GridView.ItemTemplate>
        <DataTemplate>
            <!-- item -->
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

</Page>

Solution

  • I understand your question. Honestly, you probably won't end up doing it this way. But maybe you are just wanting to learn how to do it. So, for the sake of academic inquiry I am going to answer your question.

    Use this XAML:

    <local:MyGridView>
    
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                </Grid>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="GridViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                <Setter Property="VerticalContentAlignment" Value="Stretch" />
            </Style>
        </ItemsControl.ItemContainerStyle>
    
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid   Background="{Binding Color}"
                    Margin="10"
                    Grid.RowSpan="{Binding RowSpan}" 
                    Grid.ColumnSpan="{Binding ColumnSpan}"
                    Grid.Row="{Binding Row}" 
                    Grid.Column="{Binding Column}">
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    
        <GridView.Items>
            <local:MyItem Color="Gainsboro" Row="0" RowSpan="1" Column="0" ColumnSpan="3" />
            <local:MyItem Color="Red" Row="1" RowSpan="1" Column="0" ColumnSpan="1" />
            <local:MyItem Color="Green" Row="1" RowSpan="1" Column="1" ColumnSpan="1" />
            <local:MyItem Color="Blue" Row="1" RowSpan="1" Column="2" ColumnSpan="1" />
            <local:MyItem Color="Yellow" Row="2" RowSpan="1" Column="0" ColumnSpan="1" />
            <local:MyItem Color="Orange" Row="2" RowSpan="1" Column="1" ColumnSpan="1" />
        </GridView.Items>
    
    </local:MyGridView>
    

    Use this code behind:

    public class MyGridView : GridView
    {
        protected override DependencyObject GetContainerForItemOverride()
        {
            var container = base.GetContainerForItemOverride() as FrameworkElement;
            if (container == null) return container;
    
            var content = ItemTemplate.LoadContent() as FrameworkElement;
            if (content == null) return container;
    
            // sync the container grid dependency properties with the content
            var binding = content.GetBindingExpression(Grid.RowProperty);
            if (binding != null)
                container.SetBinding(Grid.RowProperty, content.GetBindingExpression(Grid.RowProperty).ParentBinding);
            binding = content.GetBindingExpression(Grid.RowSpanProperty);
            if (binding != null)
                container.SetBinding(Grid.RowSpanProperty, binding.ParentBinding);
            binding = content.GetBindingExpression(Grid.ColumnProperty);
            if (binding != null)
                container.SetBinding(Grid.ColumnProperty, binding.ParentBinding);
            binding = content.GetBindingExpression(Grid.ColumnSpanProperty);
            if (binding != null)
                container.SetBinding(Grid.ColumnSpanProperty, binding.ParentBinding);
    
            return container;
        }
    }
    

    This will give you EXACTLY what you asked for in your question.

    Now, let's talk about why you probably won't want this approach. Dynamic size of a cell is not a feature of the WrapGrid, the native panel for GridView. As a result, we have to switch to Grid, which doesn't have dynamic Rows/Columns. Could I update this example to create dynamic Rows/Columns? Of course. But that's not the point.

    Internally, MS Design recommends that your margins change slightly based on the width of your GridView. This, itself, is no small feat since you cannot databind to a margin. But the resulting visual is actually much improved because it gives you more space on the page as the width is decreased WITHOUT messing up the size of your data item. Once you reach a certain narrow width, you let the native WrapGrid remove the column for you, rather than maintaining a fixed number of columns.

    Step back for a minute and consider how you will have to write each data item in the GridView with adaptive logic with your initial approach. That complexity is a needless addition to most apps. That being said, I don't know your app as well as you do. You are the developer and not me. You can decide what's best for your project.

    You might be asking yourself, well how does Microsoft change the margin? The answer is that using VSM we wait for a certain width and when it occurs, we set the margin explicitly. Depending on the type of control you decide to use, you may need to inject the margin somehow, but in general, it's just a VSM action. This is important because it's not a sliding scale. That is to say, it's either the wider margin or the more narrow margin and nothing in between.

    Man, I hope this makes sense.

    Best of luck!