Have you checked your binding?
Here is a quick test showing the binding working:
1. Model:
public class DataRecord
{
public int ID { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public ObservableCollection<string>? ListItems { get; set; }
}
2. Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
GridData.Add(new()
{
ID = 1,
Name = "Widget 1",
Description = "This is a widget of #1",
ListItems = new() { Tags[0], Tags[1], Tags[2], Tags[3], }
});
GridData.Add(new()
{
ID = 1,
Name = "Widget 2",
Description = "This is a widget of #2",
ListItems = new() { Tags[4], Tags[5], Tags[6], Tags[7], }
});
GridData.Add(new()
{
ID = 1,
Name = "Widget 3",
Description = "This is a widget of #3",
ListItems = new() { Tags[3], Tags[5], Tags[7], Tags[9], }
});
}
public ObservableCollection<string> Tags { get; } = new()
{
"Tag 1", "Tag 2", "Tag 3", "Tag 4", "Tag 5",
"Tag 6", "Tag 7", "Tag 8", "Tag 9", "Tag 10",
};
public ObservableCollection<DataRecord> GridData { get; } = new();
}
3. View:
<Window x:Class="WpfDataGridCellList.MainWindow"
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"
mc:Ignorable="d" x:Name="Window1"
Title="MainWindow" Height="450" Width="800">
<Grid DataContext="{Binding ElementName=Window1}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding GridData}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"
Binding="{Binding ID}" />
<DataGridTextColumn Header="Name"
Binding="{Binding Name}" />
<DataGridTextColumn Header="Description"
Binding="{Binding Description}" />
<DataGridTemplateColumn Header="ListItems">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ItemsControl
ItemsSource="{Binding ListItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel
Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Padding="0 0 10 0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
When run, you can see the
ListItems
for each row.
[UPDATE #1]
As mentioned above, you need to check that you have the collection binding in the
DataGridTemplateColumn
. The above code does just that.
For the
UserControl
, binding to a collection is simple. I'm going for a more
lightweight Custom Control but using a
Label
control for a
view only custom control.:
public class TagItemsControl : Label
{
private static readonly Type ctrlType = typeof(TagItemsControl);
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable),
ctrlType,
new PropertyMetadata(null,
OnItemsSourceChanged));
public IEnumerable? ItemsSource
{
get => GetValue(ItemsSourceProperty) as IEnumerable;
set => SetValue(ItemsSourceProperty, value);
}
private static void OnItemsSourceChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (d.TryCast(out TagItemsControl? ctrl))
{
Binding binding = new Binding(nameof(ItemsSource)) { Source = ctrl };
}
}
}
Any template can be applied to the
TagItemsControl
custom control. I have a simple Template, however requires a
IValueConverter
to join all of the items being viewed:
public class ListToCsvConverter : IValueConverter
{
public object Convert(object? value, Type targetType,
object parameter, CultureInfo culture)
=> value is null
? "[No items]"
: string.Join(", ", (IEnumerable<string>)value);
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
Now we can update the view:
<Window x:Class="WpfDataGridCellList.MainWindow"
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:WpfDataGridCellList"
mc:Ignorable="d" x:Name="Window1"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:ListToCsvConverter x:Key="ListToCsvConverter" />
<ControlTemplate x:Key="TagItemsControlTemplate"
TargetType="{x:Type local:TagItemsControl}">
<Label Content="{TemplateBinding ItemsSource,
Converter={StaticResource ListToCsvConverter}}" />
</ControlTemplate>
</Window.Resources>
<Grid DataContext="{Binding ElementName=Window1}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding GridData}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"
Binding="{Binding ID}" />
<DataGridTextColumn Header="Name"
Binding="{Binding Name}" />
<DataGridTextColumn Header="Description"
Binding="{Binding Description}" />
<DataGridTemplateColumn Header="ListItems">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TagItemsControl
ItemsSource="{Binding ListItems}"
Template="{StaticResource
TagItemsControlTemplate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
When run, you can see the ListItems for each row using the
TagItemsControl custom control.
If you don't like my template using a
Label
control and the
ListToCsvConverter
to format the layout of the
ListItems
, then you can change it - totally customizable!
Hope this helps!
[edit #1]
If required, here is the helper extension:
public static class DependencyObjectExtension
{
public static bool TryCast<TElement>(this DependencyObject dObj,
out TElement? element)
where TElement : UIElement
{
element = dObj as TElement;
return element != null;
}
}
[UPDATE #2]
A
Label
is extends the
ContentControl
. The
UserControl
also extends the
ContentControl
. You can check this using
Peek Definition. I could have used that as my base class for the custom
TagItemsControl
.
The
UserControl
will automatically set the
DataContext
to the parent control's
DataContext
, so the Properties of the Class contained in the row are exposed to the
UserControl
binding. You do not require a Dependancy Property. I used a Dependancy Property in a custom control to show how to implement it.
So, here is a simple
UserControl
version:
<UserControl x:Class="WpfDataGridCellList.TagItemsUserControl"
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:WpfDataGridCellList">
<UserControl.Resources>
<local:ListToCsvConverter x:Key="ListToCsvConverter" />
</UserControl.Resources>
<Label Content="{Binding ListItems,
Converter={StaticResource ListToCsvConverter}}" />
</UserControl>
NOTE: I am not setting the
DataContext
as it is inherited.
You do not require anything in the Code-Behind (see explanation above)
using System.Windows.Controls;
namespace WpfDataGridCellList;
public partial class TagItemsUserControl : UserControl
{
public TagItemsUserControl() => InitializeComponent();
}
And finally using the UserControl:
<DataGrid Grid.Row="1" AutoGenerateColumns="False"
ItemsSource="{Binding GridData}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID"
Binding="{Binding ID}" />
<DataGridTextColumn Header="Name"
Binding="{Binding Name}" />
<DataGridTextColumn Header="Description"
Binding="{Binding Description}" />
<DataGridTemplateColumn Header="ListItems">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:TagItemsUserControl />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have set the
DataGrid
in a second Row, so use this on the
Grid
:
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
With the CustomControl, I only use a Dependancy Property for the binding data source so as to keep the CustomControl generic - ie: can be used with other collections and layout templates. The issue with the UserControl version, is that it is very specific/restrictive, both the binding data source and layout. It is "best practice" to keep it as generic as possible.
Enjoy!