Using a Grid ItemsPanel with a DataTemplateSelector

The other week, I was working on an app with a requirement to show a collection of items in a grid formation spanning the entire view. First we looked at the feasibility of using an out of the box solution such as GridView, but this wasn’t flexible enough for our requirements. I decided that a good approach would be to use an ItemsControl with a Grid used as the ItemsPanelTemplate.

In theory, this approach worked great, as the data we received came in the form of a multidimensional array, therefore we could directly map each item's column and row. As with all great plans, there was a floor, the Gird.Row/Column attached properties on the item’s DataTemplate, didn’t seem to work. After some quick investigation using XamlSpy I could see that each item’s DataTemplate was being encapsulated in a ContentPresenter, preventing Grid.Row/Column from applying to the ItemPanel.

After a quick Google, I came across this post by Rob Garfoot, who had a solution. It was exactly what I was after, so I implemented the custom GridAwareItemsControl and added the attached properties and I was good to go.

As with all projects, a requirement was added afterwards to use a DataTemplateSelector to choose different templates, not anticipating any issues, I created a custom Selector and dropped it in my code. I was wrong, the view appeared completely wrong and was a mess. After another quick inspection in XamlSpy I found that now there was a second ContentPresenter being added to each Item. After a couple of hours of trying to refactor the GridAwareItemsControl provided by Rob, I stumbled across the solution.

The Grid’s attached properties are accessible from the DataTemplateSelector. Therefore, you can set the Row/Column properties from here by passing in the container object and casting it to a FrameworkElement.

protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
    ...

    Grid.SetColumn(uiElement, listItem.GridColumn);
    Grid.SetRow(uiElement, listItem.GridRow);
    Grid.SetColumnSpan(uiElement, listItem.GridColumnSpan);
    Grid.SetRowSpan(uiElement, listItem.GridRowSpan);

    ...

    return base.SelectTemplateCore(item, container);
}

If you want to see it in action, you can grab a demo here.