-2

My goal here is to have 3 columns when displaying my data in an ItemsControl. I have found that most of the answers regarding this matter, is to use a UniformGrid. Like so:

<ItemsControl x:Name="Tasks"
                ItemsSource="{Binding Tasks}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid Columns="3"
                            Background="Green" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button  Margin="0,10,0,16"
                        Padding="18,5,18,11"
                        Height="153"
                        Width="192"
                        Background="{StaticResource Card}"
                        Style="{StaticResource PlainButtonTheme}">
                        
                <!--Button Content-->
                
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

My UI looks like this now (I just added the green background so I can see the whole UniformGrid) :

enter image description here


What I want here, is my middle column to take the whole space (kind of like the * behavior of a grid). How can I achieve this?

paraJdox1
  • 805
  • 9
  • 23
  • The whole point of using UniformGrid is in the uniformity of its cells (in size). In particular, UniformGrid does not support addressing cells, but arranges them sequentially in the order from the collection. If you need different cells and / or collection elements need to be set to specific cells, then you need to use Grid. Add Row and Column properties to the elements of the collection to position them in the desired cells, in ItemContainerStyle set the binding to these properties. – EldHasp Jun 27 '21 at 09:29
  • I tried using `Grid` before I used `UniformGrid` but my child controls just get stacked on top on each other, so after rendering I can only see one control. Can you give me some pointers? – paraJdox1 Jun 27 '21 at 09:54
  • Any particular reason you can't use a DataGrid? – Mark Feldman Jun 27 '21 at 11:19

1 Answers1

2

I tried using Grid before I used UniformGrid but my child controls just get stacked on top on each other, so after rendering I can only see one control.

It is necessary to explicitly set the address (Row and Column) of the cell.

Example:

namespace GridItems
{
    public class TileData
    {
        public object Content { get; set; }
        public int Row { get; set; }
        public int Column { get; set; }

        public TileData() { }

        public TileData(object content, int row, int column)
        {
            Content = content;
            Row = row;
            Column = column;
        }
    }
}
namespace GridItems
{
    public class TilesViewModel
    {
        public TileData[] Tiles { get; } =
        {
            new TileData("First", 0, 0),
            new TileData("Second", 0, 1),
            new TileData("Third", 0, 2),
            new TileData("Fourth", 1, 0),
            new TileData("Fifth", 1, 1),
            new TileData("Sixth", 1, 2)
        };
    }
}
<Window x:Class="GridItems.GridItemsWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:GridItems"
        mc:Ignorable="d"
        Title="GridItemsWindow" Height="450" Width="800">
    <FrameworkElement.DataContext>
        <local:TilesViewModel/>
    </FrameworkElement.DataContext>
    <FrameworkElement.Resources>
        <DataTemplate x:Key="TileTemplate" DataType="local:TileData">
            <Viewbox Margin="20">
                <TextBlock Text="{Binding Content}"
                           FontSize="20"
                           Padding="20"
                           Background="Aqua"/>
            </Viewbox>
        </DataTemplate>
        <Style x:Key="ItemStyle" TargetType="ContentPresenter">
            <Setter Property="Grid.Row" Value="{Binding Row}"/>
            <Setter Property="Grid.Column" Value="{Binding Column}"/>
        </Style>
        <ItemsPanelTemplate x:Key="GridTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition />
                    <ColumnDefinition Width="100"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
            </Grid>
        </ItemsPanelTemplate>
    </FrameworkElement.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding Tiles}"
                      ItemTemplate="{DynamicResource TileTemplate}"
                      ItemContainerStyle="{DynamicResource ItemStyle}"
                      ItemsPanel="{DynamicResource GridTemplate}"/>
    </Grid>
</Window>

Supplement: answer to the question about dynamically changing the number of elements in the collection and, accordingly, the Grid rows.

Implementation option using Attached Property.

using System;
using System.Windows;
using System.Windows.Controls;

namespace GridItems
{
    public static class AutoRowsGrid
    {
        /// <summary> Returns the value of the ChildrenCount attached property for <paramref name = "grid" />. </summary>
        /// <param name = "grid"> <see cref = "Grid" /> whose property value will be returned. </param>
        /// <returns> <see cref = "int" /> property value. </returns>
        public static int? GetChildrenCount(Grid grid)
        {
            return (int?)grid.GetValue(ChildrenCountProperty);
        }

        /// <summary> Sets the ChildrenCount attached property for <paramref name = "grid" />. </summary>
        /// <param name = "grid"> <see cref = "Grid" /> whose property value will be returned. </param>
        /// <param name = "value"> <see cref = "int" /> value for the property. </param>
        public static void SetChildrenCount(Grid grid, int value)
        {
            grid.SetValue(ChildrenCountProperty, value);
        }

        /// <summary><see cref="DependencyProperty"/> for methods <see cref="GetChildrenCount(Grid)"/> и <see cref="SetChildrenCount(Grid, int)"/>.</summary>
        public static readonly DependencyProperty ChildrenCountProperty =
            DependencyProperty.RegisterAttached(nameof(GetChildrenCount).Substring(3), typeof(int?), typeof(AutoRowsGrid), new PropertyMetadata(null, CountChanged));

        private static void CountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Grid grid = (Grid)d;
            double count = (int)e.NewValue;

            int columns = grid.ColumnDefinitions.Count;
            if (columns < 1)
                columns = 1;

            int newRows = (int)Math.Ceiling(count / columns);

            int rows = grid.RowDefinitions.Count;

            if (newRows != rows)
            {
                if (newRows > rows)
                {
                    for (; newRows > rows; rows++)
                    {
                        grid.RowDefinitions.Add(new RowDefinition());
                    }
                }
                else
                {
                    for (rows--; newRows <= rows; rows--)
                    {
                        grid.RowDefinitions.RemoveAt(rows);
                    }
                }
            }
        }
    }
}
<Window x:Class="GridItems.GridItemsWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:GridItems"
        mc:Ignorable="d"
        Title="GridItemsWindow" Height="450" Width="800">
    <FrameworkElement.DataContext>
        <local:TilesViewModel/>
    </FrameworkElement.DataContext>
    <FrameworkElement.Resources>
        <DataTemplate x:Key="TileTemplate" DataType="local:TileData">
            <Viewbox Margin="20">
                <TextBlock Text="{Binding Content}"
                           FontSize="20"
                           Padding="20"
                           Background="Aqua"/>
            </Viewbox>
        </DataTemplate>
        <Style x:Key="ItemStyle" TargetType="ContentPresenter">
            <Setter Property="Grid.Row" Value="{Binding Row}"/>
            <Setter Property="Grid.Column" Value="{Binding Column}"/>
        </Style>
        <ItemsPanelTemplate x:Key="GridTemplate">
            <Grid local:AutoRowsGrid.ChildrenCount="{Binding Items.Count, ElementName=listBox}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition />
                    <ColumnDefinition Width="100"/>
                </Grid.ColumnDefinitions>
            </Grid>
        </ItemsPanelTemplate>
    </FrameworkElement.Resources>
    <Grid>
        <ItemsControl x:Name="listBox"
                      ItemsSource="{Binding Tiles}"
                      ItemTemplate="{DynamicResource TileTemplate}"
                      ItemContainerStyle="{DynamicResource ItemStyle}"
                      ItemsPanel="{DynamicResource GridTemplate}"/>
    </Grid>
</Window>

But if you do it "quite rightly", then in your collection there is an obvious heterogeneity of its elements.
Items for the left, middle, and right columns require different presentation.
Therefore, the most correct, based on the principles of OOP, was to create a type containing all the elements of one row and then fill the collection with rows, and not separate cells.

EldHasp
  • 6,079
  • 2
  • 9
  • 24
  • I really got this to work, but... if I just set e.g. two in my View, then I can't see all my records, I can't even scroll through all the records. Is there a way ot make the RowDefinition "dynamic" depending on the items that are retrieved? – paraJdox1 Jun 28 '21 at 07:22
  • You want to set a certain logic of behavior - this is possible. But you need to clearly algorithmize your ideas about this logic. This logic can be implemented later in the ViewModel and/or Attached Property. – EldHasp Jun 28 '21 at 07:33
  • what do I need to achieve this? I am not familiar with a "behavior" btw... – paraJdox1 Jun 28 '21 at 07:34
  • For me, let's say, it's completely incomprehensible what kind of presentation you want to get for your collection. You originally only asked a question about how to make the middle column auto-width. Now you need to change the number of columns. How do you want to change them? Dynamically from ViewMode? Or "hands in XAML"? If dynamically, then you need to set the appropriate properties in the VM, and in the View - set the behavior for changing these properties. – EldHasp Jun 28 '21 at 07:41
  • yeah I asked about how to make the middle column auto-width, but with this solution, I can't display all my rows. – paraJdox1 Jun 28 '21 at 08:10
  • Now I understand what you need. Now I'll think about how best to solve it. – EldHasp Jun 28 '21 at 08:29
  • 1
    Read the supplement to my answer. – EldHasp Jun 28 '21 at 09:16
  • Thank you @EldHasp. This is what I was looking for! :)) – paraJdox1 Jun 30 '21 at 11:19