Your
outputColumns
class does not have a collection to track the selections in the ComboBox (for each row), rather you only have a global collection (for all rows) on the ViewModel. This is why all rows show the same selections in the ComboBoxes.
UPDATE
I have had another look at this. My understanding is that you want to get the Checked items in ComboBox for each row and show the checked items in another row + the IsChecked in another column.
To do this, I needed to use an
IMultiValueConverter
.
public class SummaryConverter : IMultiValueConverter
{
public object? Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (values.Length < 2)
return null;
if (values
.Any(x => x == DependencyProperty.UnsetValue))
return null;
bool isChecked = (bool)values[0];
IEnumerable<Tag> TagItems = (IList<Tag>)values[1];
List<string> tags = new()
{ isChecked
? "checked "
: "unchecked " };
tags.AddRange(TagItems
.Where(tag => tag.IsChecked)
.Select(tag => tag.Name));
return string.Join(", ", tags);
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
CultureInfo culture)
=> throw new NotImplementedException();
}
As we are binding to an
ObservableCollection<T>
in the
OutputColumn
, I needed to extend the
ObservableCollection<T>
with a custom property as the
IMultiValueConverter
does not see the
INotifyPropertChanged
events for the items in the code>ObservableCollection<t>:
public class ObservableCollectionEx<T>
: ObservableCollection<T>
where T : INotifyPropertyChanged
{
private int itemChangeCount;
public int ItemChangeCount
{
get => itemChangeCount;
set
{
itemChangeCount = value;
OnPropertyChanged(
new PropertyChangedEventArgs(nameof(ItemChangeCount)));
}
}
protected override void OnCollectionChanged(
NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (T item in e.NewItems!.Cast<T>())
{
item.PropertyChanged += Item_PropertyChanged;
}
}
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (T item in e.NewItems!.Cast<T>())
{
item.PropertyChanged -= Item_PropertyChanged;
}
}
}
private void Item_PropertyChanged(
object? sender,
PropertyChangedEventArgs e)
{
ItemChangeCount += 1;
}
}
Wrapper for the
INotifyPropertyChanged
event:
public abstract class ObservableObject
: INotifyPropertyChanged
{
public void Set<TValue>(
ref TValue field,
TValue newValue,
[CallerMemberName] string propertyName = "")
{
if (!EqualityComparer<TValue>.Default.Equals(field, default)
&& field!.Equals(newValue))
return;
field = newValue;
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler? PropertyChanged;
}
Now the Models:
public class OutputColumn : ObservableObject
{
private string spaltenname;
private bool isChecked;
public string Spaltenname
{
get => spaltenname;
set => Set(ref spaltenname, value);
}
public bool IsChecked
{
get => isChecked;
set => Set(ref isChecked, value);
}
public ObservableCollectionEx<Tag> Tags { get; set; }
}
public class Tag : ObservableObject
{
private string name;
private bool isChecked;
public string Name
{
get => name;
set => Set(ref name, value);
}
public bool IsChecked
{
get => isChecked;
set => Set(ref isChecked, value);
}
}
Now we can do the code behind to initialize the data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitData();
}
private Random random = new();
public ObservableCollection<OutputColumn> OutputColumns { get; set; }
= new();
void InitData()
{
for (int i = 0; i < 10; i++)
{
OutputColumn item = new()
{
Spaltenname = $"Item {i + 1}",
IsChecked = random.Next(0, 2) == 1,
Tags = new()
{
new()
{
Name = "Asset Type",
IsChecked = random.Next(0, 2) == 1
},
new()
{
Name = "Service Tag",
IsChecked = random.Next(0, 2) == 1
},
new()
{
Name = "Item Number",
IsChecked = random.Next(0, 2) == 1
},
new()
{
Name = "System Brand",
IsChecked = random.Next(0, 2) == 1
},
new()
{
Name = "Description",
IsChecked = random.Next(0, 2) == 1
},
}
};
OutputColumns.Add(item);
}
}
}
Now we can wire up the view/UI:
<Window x:Class="WpfDataGridComboBoxColumn.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:WpfDataGridComboBoxColumn"
mc:Ignorable="d"
x:Name="Window"
Title="MainWindow" Height="450" Width="800">
<DataGrid ItemsSource="{Binding ElementName=Window, Path=OutputColumns}"
AutoGenerateColumns="False">
<DataGrid.Resources>
<local:SummaryConverter x:Key="StaticSummaryConverter" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridCheckBoxColumn
Binding="{
Binding IsChecked,
UpdateSourceTrigger=PropertyChanged}"
Header="IsChecked"/>
<DataGridTextColumn Binding="{Binding Spaltenname}"
Header="Name"/>
<DataGridTemplateColumn Header="AdditionsSpalten">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=Tags}"
DisplayMemberPath="Name">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox
IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Summary">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding
Converter="{StaticResource
StaticSummaryConverter}">
<Binding Path="IsChecked" />
<Binding Path="Tags"/>
<Binding Path="Tags.ItemChangeCount"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Window>
This is a "create a new project and drop the code in" to see how it works. The last column dynamically changes depending on the selections made in the Checkbox and ComboBox columns
NOTES:
1. DataGridColumns may not be the same, however, the purpose of the following code is to answer the questions on the issue that you are having. You will need to adjust the code for what you are doing.
2. The custom
ObservableCollectionEx<T>
class is used in the
OutputColumn
model class
3. The
MultiBinding
on the Summary
DataGridTemplateColumn
column uses the new
ItemChangeCount
property of the
ObservableCollectionEx<T>
to trigger the updating of the binding, but is and used for anything else.
4.
DataGridCheckBoxColumn
& the
CheckBox.Item
bindings required the type of event trigger to be set:
Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}"
for the event to be fired on selection.
Enjoy!