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.