Right. Now that I had some time, I achieved it. Here are the steps:
Firstly, I'm going to define sample data type and some sample data to help visualize things right inside the designer, instead of having to run it every time. This step is not necessary for you since you already have your data types defined. Just take care of one thing though. Our control needs access to a property named RowNumber
to decide the row/col of each element. This should simply be an int
type property showing the row index of the element within the collection.
public class FiveRowData
{
public int RowNumber { get; set; }
public string Column1 { get; set; }
public string Column2 { get; set; }
}
public class FiveRowList : List<FiveRowData>
{
}
Next, add a sample xml data file (Add New Item > Resource Dictionary) to the project. This too is not necessary and is here just for design-time support. Paste the following sample data into the file:
<YourProject:FiveRowList xmlns:YourProject="clr-namespace:YourProject" Capacity="37">
<YourProject:FiveRowData RowNumber="1" Column1="Content 1" Column2="Item 1" />
<YourProject:FiveRowData RowNumber="2" Column1="Content 2" Column2="Item 2" />
<YourProject:FiveRowData RowNumber="3" Column1="Content 3" Column2="Item 3" />
<YourProject:FiveRowData RowNumber="4" Column1="Content 4" Column2="Item 4" />
<YourProject:FiveRowData RowNumber="5" Column1="Content 5" Column2="Item 5" />
<YourProject:FiveRowData RowNumber="6" Column1="Content 6" Column2="Item 6" />
<YourProject:FiveRowData RowNumber="7" Column1="Content 7" Column2="Item 7" />
<YourProject:FiveRowData RowNumber="8" Column1="Content 8" Column2="Item 8" />
<YourProject:FiveRowData RowNumber="9" Column1="Content 9" Column2="Item 9" />
<YourProject:FiveRowData RowNumber="10" Column1="Content 10" Column2="Item 10" />
<YourProject:FiveRowData RowNumber="11" Column1="Content 11" Column2="Item 11" />
<YourProject:FiveRowData RowNumber="12" Column1="Content 12" Column2="Item 12" />
<YourProject:FiveRowData RowNumber="13" Column1="Content 13" Column2="Item 13" />
<YourProject:FiveRowData RowNumber="14" Column1="Content 14" Column2="Item 14" />
<YourProject:FiveRowData RowNumber="15" Column1="Content 15" Column2="Item 15" />
<YourProject:FiveRowData RowNumber="16" Column1="Content 16" Column2="Item 16" />
<YourProject:FiveRowData RowNumber="17" Column1="Content 17" Column2="Item 17" />
</YourProject:FiveRowList>
Now actual work. Add a new UserControl
to your WPF project. Name it FiveRowWrapper
(or whatever you wish, but then take care of renaming things in the following code). Go to XAML and replace existing code with the following:
<ItemsControl x:Class="FiveRowWrapper"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YourProject"
xmlns:syswin="clr-namespace:System.Windows;assembly=PresentationFramework"
mc:Ignorable="d"
d:DesignHeight="180" d:DesignWidth="600"
d:DataContext="{d:DesignData Source=FiveRowListSampleData.xaml}"
ItemsSource="{Binding}">
<ItemsControl.Resources>
<local:ItemsCountToColCountConverter x:Key="ItemsCountToColCountConverter" />
<local:FiveRowToRowNumberConverter x:Key="FiveRowToRowNumberConverter" />
<local:FiveRowToColNumberConverter x:Key="FiveRowToColNumberConverter" />
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:FiveRowData">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Column1}" Margin="10" />
<TextBlock Text="{Binding Column2}" Margin="10" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid local:GridHelpers.RowCount="5"
local:GridHelpers.ColumnCount="{Binding Count, Converter={StaticResource ItemsCountToColCountConverter}}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Grid.Row" Value="{Binding RowNumber, Converter={StaticResource FiveRowToRowNumberConverter}}" />
<Setter Property="Grid.Column" Value="{Binding RowNumber, Converter={StaticResource FiveRowToColNumberConverter}}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
A few points to elaborate. We are using a custom ItemsControl
that uses Grid
as its panel. This will serve as the overall container of our individual items. Those items in turn will use the ItemTemplate
defined above containing a StackPanel
with two TextBlock
s to show the properties of each item. For my sample class, I have used Column1
and Column2
properties, but you can use your properties or even customize the appearance of individual items in any way you want by defining your own ItemTemplate
here. The ItemContainerStyle
property at the bottom is responsible for binding Grid.Row
and Grid.Column
properties of each individual item and thus placing the item into appropriate cell within the main Grid
.
There are two other things in the XAML code above that need explanation. Firstly, we need to decide the number of columns in our Grid
, which can simply be computed by dividing the number of items by 5. The problem however is that due to Grid
's design, we cannot bind the number of columns to a property and will need to handle indirectly through attached properties.
Add a new class to your project and name it GridHelpers
(thanks to Rachel's helpful answer). Replace the contents of this file with the following:
public class GridHelpers
{
public static readonly DependencyProperty ColumnCountProperty = DependencyProperty.RegisterAttached("ColumnCount", typeof(int), typeof(GridHelpers), new PropertyMetadata(-1, ColumnCountChanged));
public static int GetColumnCount(DependencyObject obj)
{
return obj.GetValue(ColumnCountProperty);
}
public static void SetColumnCount(DependencyObject obj, int value)
{
obj.SetValue(ColumnCountProperty, value);
}
public static void ColumnCountChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)obj;
grid.ColumnDefinitions.Clear();
for (int i = 0; i <= (int)e.NewValue - 1; i++) {
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
}
}
}
As you see, this class adds an attached property to Grid
using which we can define the number of columns dynamically. I'm using GridLength.Auto
here, but in case you want equal-sized columns, you can use fixed-size columns too.
Lastly we need the following 3 converters:
- Number of elements to number of columns (divide number of elements by 5, take ceiling)
- RowNumber to Row (modulo 5)
- RowNumber to Column (divide by 5, take floor)
Here's the code for the 3 converters. Define them anywhere in your project:
public class ItemsCountToColCountConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)Math.Ceiling((int)value / 5f);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class FiveRowToRowNumberConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((int)value - 1) % 5f;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class FiveRowToColNumberConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)Math.Floor(((int)value - 1) / 5f);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The final output of the above effort looks like this in my WPF Designer:

Lastly, the control will expand horizontally as you add more items to your collection. You should place this control inside a ScrollViewer
to handle that situation.
I leave background coloring and other smaller activities as exercise for you.