Click here to Skip to main content
15,878,852 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I have an an DataGrid in which I load Data from an ObservableCollection and each row has an CheckBox so that I can have a list of checked Rows. Now I wanted to add an new Column to the DataGrid which is an ComboBox and in the ComboBox is a list with Checkboxen. I want for each row that when I check one Row and then when I check other Items from the ComboBox that I can combine the selected row and the selected values from my ComboBox.

The problem is that if I check one Row of the Combobox item it will check for every other ComboBox that appears in the DataGrid.

I want only to check the value of the combobox for one row and combine it with the Value of the checked row of the datagrid.

Here is my Code of my DataGrid:
<DataGrid  Style="{DynamicResource DataGridStyle}" 
                       Margin="10"  SelectionUnit="CellOrRowHeader"  AutoGenerateColumns="False" 
                       CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Single"  VerticalAlignment="Stretch"
                       x:Name="SpaltenGrid" ItemsSource="{Binding bischenanders, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                       GridLinesVisibility="None"  ScrollViewer.CanContentScroll="True" 
                       ScrollViewer.VerticalScrollBarVisibility="Auto" 
                       ScrollViewer.HorizontalScrollBarVisibility="Auto">
                        <DataGrid.Columns >
                            <DataGridCheckBoxColumn x:Name="selectedField" Binding="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Auswahl" CanUserSort="False">
                                <DataGridCheckBoxColumn.ElementStyle>
                                    <Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}" />
                                </DataGridCheckBoxColumn.ElementStyle>
                            </DataGridCheckBoxColumn>
                            
                            <DataGridTextColumn  Header="Spaltenname"  Binding="{Binding Spaltenname}" Width="1.5*"/>
                            
                            <DataGridComboBoxColumn x:Name="cmbAdditionColume"   Header="AdditionsSpalten" DisplayMemberPath="Spaltenname"
                                                    CanUserSort="False" Width="*">
                            
                                <DataGridComboBoxColumn.ElementStyle>
                                    
                                    <Style TargetType="ComboBox"  BasedOn="{StaticResource ComboBoxStyle1 }"  />
                                </DataGridComboBoxColumn.ElementStyle>

                            </DataGridComboBoxColumn>

                            <!--<DataGridTemplateColumn Header="Week Days" Width="*" x:Name="cmbTemplateColumn">
                                <DataGridTemplateColumn.CellTemplate>
                                    <DataTemplate>
                                        <ComboBox x:Name="cmbAdditionColume" Foreground="Black"
                                                  DisplayMemberPath="Spaltenname" Style="{StaticResource ComboBoxStyle1}"/>
                                    </DataTemplate>
                                </DataGridTemplateColumn.CellTemplate>
                            </DataGridTemplateColumn>-->

                        </DataGrid.Columns>

                        <DataGrid.Resources>
                            <!-- Select everything with orange... -->
                            <SolidColorBrush 
    Color="Transparent" 
    x:Key="{x:Static SystemColors.HighlightBrushKey}" />
                            <!-- ...all the time -->
                            <SolidColorBrush 
    Color="Transparent" 
    x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" />
                            <!--Design kopfzeile-->
                            <Style TargetType="{x:Type DataGridColumnHeader}" x:Name="test2" >
                                <Setter Property="Background" Value="Red"/>
                                <Setter Property="Foreground" Value="Black"/>
                                <Setter Property="FontWeight" Value="SemiBold"/>
                                <Setter Property="Height" Value="30"/>
                                <Setter Property="FontSize" Value="15"/>
                                <Setter Property="BorderThickness" Value="0" />
                                <Setter Property="BorderBrush" Value="Green"/>
                                <Setter Property="Padding" Value="10 0 0 0"/>
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
                                            <Grid x:Name="insideHeader" Background="Transparent">
                                                <Border x:Name="borderHeader" BorderThickness="0"
                                        CornerRadius="10" 
                                        Background="White"
                                        Padding="10,0,0,0"
                                        >
                                                    <ContentPresenter />
                                                </Border>
                                                <!--<Thumb HorizontalAlignment="Right"
                           Grid.Column="1"
                           Name="PART_HeaderGripper"
                           Margin="0,4,0,4"
                           Width="2"/>-->
                                            </Grid>

                                            <ControlTemplate.Triggers>
                                                <Trigger Property="IsMouseOver" Value="True">
                                                    <Setter  Property="Foreground" Value="{DynamicResource PrimaryBlueColor}"/>
                                                </Trigger>
                                                <Trigger Property="IsPressed" Value="True">
                                                    <Setter TargetName="borderHeader" Property="Background" Value="{DynamicResource SecundaryGrayColor}"/>
                                                </Trigger>
                                            </ControlTemplate.Triggers>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>

                            <Style TargetType="{x:Type DataGridRowHeader}">
                                <Setter Property="Content" Value=">" />
                                <Setter Property="Foreground" Value="{DynamicResource PrimaryBlueColor}" />
                                <Setter Property="FontWeight" Value="Bold"/>
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="{x:Type DataGridRowHeader}">
                                            <Grid x:Name="insideHeader" Background="Transparent">
                                                <Border x:Name="borderHeader" BorderThickness="0"
                                        CornerRadius="2"
                                        Background="White"
                                        Padding="0,10,0,0"
                                        >
                                                    <TextBlock Text=">" FontSize="12" VerticalAlignment="Top" Margin="0 0 0 0" TextAlignment="Center" FontWeight="Bold" Width="20" Foreground="{DynamicResource PrimaryBlueColor}" />

                                                </Border>
                                            </Grid>

                                            <ControlTemplate.Triggers>
                                                <Trigger Property="IsMouseOver" Value="True" >
                                                    <Setter TargetName="borderHeader" Property="Background" Value="{DynamicResource PrimaryGrayColor}"/>
                                                </Trigger>
                                                <Trigger Property="IsPressed" Value="True">
                                                    <Setter TargetName="borderHeader" Property="Background" Value="{DynamicResource SecundaryGrayColor}"/>
                                                </Trigger>
                                            </ControlTemplate.Triggers>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>

                            </Style>

                            <!--Deaktivieren Des rowheader
                    <Style TargetType="{x:Type DataGridRowHeader}">
                        <Setter Property="Background" Value="Transparent"/>
                    </Style>-->

                            <!--Cellen Design-->
                            <Style TargetType="{x:Type DataGridCell}">
                                <Setter Property="Background" Value="Transparent"/>
                                <Setter Property="Foreground" Value="Black"/>
                                <Setter Property="FontFamily" Value="sans-serif"/>
                                <Setter Property="BorderThickness" Value="0" />
                                <Setter Property="BorderBrush" Value="#333333"/>
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="{x:Type DataGridCell}">
                                            <Border x:Name="insideBorder" Background="Transparent" BorderThickness="0 2 0 1" BorderBrush="#dddddd">
                                                <Border x:Name="BorderCell" BorderThickness="0"
                                        CornerRadius="6"
                                        Background="White"
                                        Padding="8,5,0,5"
                                        Margin="2">
                                                    <ContentPresenter/>
                                                </Border>
                                            </Border>
                                            <ControlTemplate.Triggers>
                                                <Trigger Property="IsMouseOver" Value="True">
                                                    <Setter  Property="Foreground" Value="{DynamicResource PrimaryBlueColor}"/>
                                                </Trigger>
                                            </ControlTemplate.Triggers>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </DataGrid.Resources>



                    </DataGrid>


my itemtemplate of my ComboBox:

<Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding Path=cmbIsChecked, Mode=TwoWay}" Width="20"/>
                        <TextBlock Text="{Binding Path=Spaltenname}" VerticalAlignment="Center" />
                    </StackPanel>
                </DataTemplate>
            </Setter.Value>
        </Setter>


my "bischenanders" collection (sry for that bad name I will fix that in the future)

my outputColumns class:

public class OutputColumns
{
    public string Spaltenname{ get; set; }
    public bool IsChecked { get; set; }

    public bool cmbIsChecked { get; set; }


}


thats the way I read the selected Items for the rows and for the Combobox rows

public List<OutputColumns> GetSelectedItems()
    {
        var selectedItems = new List<OutputColumns>();

        foreach (OutputColumns item in View.bischenanders)
        {
            try
            {
                if (item.IsChecked)
                {
                    selectedItems.Add(item);
                }
            }
            catch
            {

            }
        }
        return selectedItems;
    }

    public List<OutputColumns> cmbGetSelectedItems()
    {

        var selectedItems = new List<OutputColumns>();

        foreach (OutputColumns item in View.bischenanders)
        {
            try
            {
                if (item.cmbIsChecked)
                {
                    selectedItems.Add(item);
                }
            }
            catch
            {

            }
        }
        return selectedItems;
    }


So that If this row is checked:

Picture1

and in the combobox that 2 rows are checked:

Picture2

then I want that these 3 values are getting Combined

but when I check in one CombobBox every combobox in the DataGrid has the same checked Values

It looks like that every time I open a new ComboBox he getting the data from the class and then there will the programm find a version where it is checked, because ComboBoxs I already have open get there own checked values, only the new ones getting the checked one that are checked in the previous checkbox.

Thanks for help!

What I have tried:

I tried to made an new Collection and adding it like this:

<DataGridTemplateColumn Header="AdditionsSpalten"  Width="*" x:Name="cmbTemplateColumn">
                                   <DataGridTemplateColumn.CellTemplate>
                                       <DataTemplate>
                                           <ComboBox ItemsSource="{Binding ocColumns}" SelectedItem="{Binding cmbIsChecked}" DisplayMemberPath="Spaltenname" Style="{StaticResource ComboBoxStyle1}">
                                               <ComboBox.ItemTemplate>
                                                   <DataTemplate>
                                                       <StackPanel Orientation="Horizontal">
                                                           <CheckBox IsChecked="{Binding cmbIsChecked}" Width="20" />
                                                           <TextBlock Text="{Binding Spaltenname}" Width="100" />
                                                       </StackPanel>
                                                   </DataTemplate>
                                               </ComboBox.ItemTemplate>
                                           </ComboBox>
                                       </DataTemplate>
                                   </DataGridTemplateColumn.CellTemplate>
                               </DataGridTemplateColumn>


and in my OutputColumns Class:

private ObservableCollection<OutputColumns> _outputs;
        public ObservableCollection<OutputColumns> outputs
        {
            get
            {
                return _outputs;
            }

            set
            {
                _outputs = value;
                NotifyPropertyChanged(nameof(outputs));

            }
        }


and here for filling the Data:

foreach(OutputColumns columns in View.bischenanders)
            {
                columns.outputs = View.ocColumns;
            }
Posted
Updated 4-Jan-23 2:50am
v3

1 solution

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.
C#
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>:
C#
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:
C#
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:
C#
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:
C#
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:
XML
<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!
 
Share this answer
 
v3
Comments
EstKells 4-Jan-23 9:04am    
how would I need to perform it to have an collection to track it?
Graeme_Grant 4-Jan-23 19:18pm    
I had a closer look at it and it needed a little bit more than I suggest. Create a new project, drop in the code, and run to see how it works. Enjoy!
EstKells 5-Jan-23 3:41am    
Thank you very much for you Solution and the Time It took you to make this, but saddly if I make It into an new Project the Datagrid is empty I just have the DataGrid Headers
Graeme_Grant 5-Jan-23 3:56am    
Here is a downloadable version: WpfDataGridComboBoxColumn.zip - Google Drive[^]
EstKells 5-Jan-23 4:18am    
does it work for .Net 4.8 ?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900