Search code examples
c#wpfcomboboxdatagriddatagridtemplatecolumn

Selection change in DataGrid ComboBox template column clears values in other rows


In my project I have a DataGrid with two template columns that each contain a ComboBox. When I change the selection of one of the ComboBox in any of the rows, the change is reflected in all rows.

I've tried setting IsSynchronizedWithCurrentItem="False", but this did not help.

Can any one offer me a way to fix this? Here is my code.

Xaml

<DataGrid 
        x:Name="dtg"
        CanUserAddRows="False"
        AutoGenerateColumns="False"
        IsSynchronizedWithCurrentItem="False"
        ItemsSource="{Binding Pdts, Mode=TwoWay}">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Type" Width="*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox
                            IsEditable="True"
                            ItemsSource="{Binding DataContext.TypeCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                            DisplayMemberPath="TypeName"
                            SelectedItem="{Binding Type}"
                            SelectedValuePath="Id"
                            SelectionChanged="ComboBox_SelectionChanged_1"
                            SelectedValue="{Binding TypeId, Mode= TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>

            <DataGridTemplateColumn Header="Name" Width="*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox
                            IsEditable="True"
                            KeyUp="ComboBox_KeyUp_1"
                            IsSynchronizedWithCurrentItem="False"
                            ItemsSource="{Binding DataContext.PdtCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                            DisplayMemberPath="PdtName"
                            SelectedItem="{Binding Pdt}"
                            SelectedValuePath="Id"
                            SelectedValue="{Binding Id, Mode= TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

Viewmodel

public class MainWindowViewModel : Base {
    public ObservableCollection<Type> TypeCollection { get; set; }
    public ObservableCollection<Pdt> PdtCollection { get; set; }
    public ObservableCollection<PdtAndType> Pdts { get; set; }

    public MainWindowViewModel() {
        TypeCollection = new ObservableCollection<Type>();
        PdtCollection = new ObservableCollection<Pdt>();
        Pdts = new ObservableCollection<PdtAndType>();

        TypeCollection.Add(new Type {Id = 1, TypeName = "one"});
        TypeCollection.Add(new Type {Id = 2, TypeName = "two"});
        TypeCollection.Add(new Type {Id = 3, TypeName = "three"});
        TypeCollection.Add(new Type {Id = 4, TypeName = "four"});
        TypeCollection.Add(new Type {Id = 5, TypeName = "five"});

        addPdt();
    }

    public void add(int typeId = 1) {
        Random r = new Random();
        int x = r.Next(10);
        PdtCollection.Clear();
        for (int i = 0; i < x; i++) {
            PdtCollection.Add(new Pdt {Id = 1, PdtName = "P" + i + 1});
        }
    }

    public void addPdt() {
        Pdts.Add(new PdtAndType());
    }
}

Model class Pdt

public class Pdt : Base {
    private int _ID;

    public int Id {
        get { return _ID; }
        set {
            if (_ID != value) {
                _ID = value;
                onPropertyChanged("Id");
            }
        }
    }

    private String _PdtName;

    public String PdtName {
        get { return _PdtName; }
        set {
            if (_PdtName != value) {
                _PdtName = value;
                onPropertyChanged("PdtName");
            }
        }
    }

    private int _typeId;

    public int TypeId {
        get { return _typeId; }
        set {
            if (_typeId != value) {
                _typeId = value;
                onPropertyChanged("TypeId");
            }
        }
    }
}

Model class Type

public class Type : Base {
    private int _ID;

    public int Id {
        get { return _ID; }
        set {
            if (_ID != value) {
                _ID = value;
                onPropertyChanged("Id");
            }
        }
    }

    private String _typeName;

    public String TypeName {
        get { return _typeName; }
        set {
            if (_typeName != value) {
                _typeName = value;
                onPropertyChanged("TypeName");
            }
        }
    }
}

model class PdtAndtype

 public class PdtAndType : Base {
    private int _typeId;

    public int TypeId {
        get { return _typeId; }
        set {
            if (_typeId == value) return;
            _typeId = value;
            onPropertyChanged(nameof(TypeId));
        }
    }

    private int _pdtId;

    public int PdtId {
        get { return _pdtId; }
        set {
            if (_pdtId == value) {
                return;
            }
            _pdtId = value;
            onPropertyChanged(nameof(PdtId));
        }
    }

    private Type _type;

    public Type Type {
        get { return _type; }
        set {
            if (_type == value) {
                return;
            }
            _type = value;
            onPropertyChanged(nameof(Type));
        }
    }

    private Pdt _pdt;

    public Pdt Pdt {
        get { return _pdt; }
        set {
            if (_pdt == value) {
                return;
            }
            _pdt = value;
            onPropertyChanged(nameof(Pdt));
        }
    }
}

Solution

  • It seems like you are binding the SelectedItem to the same object on the ViewModel.

    I'm guessing that you didn't post the whole ViewModel, but that the only conclusion I can make.

    I suggest you make model for each row, with a SelectedType and a SelectedPdt properties in it.

    I would be happy to elaborate more if you post more details about the ViewModel and the models.


    Edit

    Ok, I can see the problem now.

    You are using only 1 ViewModel for your combo boxes. On your KeyUp event handler, you replace the items in your PdtCollection.

    This collection is single, and all the combo boxes are bound to this single collection. If I understand your goal, you want to make a different collection for each row.

    Since a row in your DataGrid is represented by the Pdts collection, I suggest you put the PdtCollection inside your Pdt object. That way, you can bind the combo box of each row to the relevant collection inside the row model.

    To make things simpler, You Pdt should look something like this:

    class Pdt : Base
    {
        /* 
            Other properties
        */
    
        private ObservableCollection<Pdt> _pdts;
        public ObservableCollection<Pdt> Pdts
        {
            get { return _pdts; }
            set { _pdts = value; }
        }
    }
    

    After that, you could bind your column instead on the single collection in your Window's DataContext, to the ROW data context. This should look something like this:

    <DataGridTemplateColumn Header="Name" Width="*">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <ComboBox IsEditable="True"
                          KeyUp="ComboBox_KeyUp_1"
                          IsSynchronizedWithCurrentItem="False"
                          ItemsSource="{Binding PdtCollection}"
                          DisplayMemberPath="PdtName"
                          SelectedItem="{Binding Pdt}"
                          SelectedValuePath="Id"
                          SelectedValue="{Binding Id, Mode= TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    

    I think you'll need to adjust a bit the code (The selection and the displayed members and such, and of course to initialize the inner PdtCollection to whatever values you want), but the overall idea is this one.

    I hoped I was clear enough. Happy coding. :)