Search code examples
c#.netdatabasedatagridviewcomboboxcolumn

Datagridview with comboboxes referencing to the own dataset mixing rows


I have a DataGridView which is based on a DataSet from a data base referenced by a BindingSource. In the DataSet there is an ID as primary key. Further there is another field in the DataSet containing an BuddyID referencing to another row of the same table. And a field containing a name of the element.

In the DataGridView there is the DataGridViewTextboxColumn with the name and DataGridViewComboboxColumn where you can select the Name of another element to change the BuddyID, reverencing by another BindingSource to the same DataSet. But that don't work like I would have it.

When you have two elements as buddy to each other and you want to set the IDs, then the BuddyID of the other element is changed to the same value too. Although I don't change the other ComboBox the value is changing! Maybe it's a problem of the combo box, but I have no idea about what to do to fix that. Maybe anyone of yours?

Edit: both (buddy) elements have the same name appearing in the combo box

Code generated by the designer - unluckily with name "text" instead of "combo":

    private System.Windows.Forms.DataGridViewComboBoxColumn idBuddyDataGridViewTextBoxColumn;

    private System.Windows.Forms.DataGridViewComboBoxColumn idB uddyDataGridViewTextBoxColumn;

      // 
      // idBuddyDataGridViewTextBoxColumn
      // 
      this.idBuddyDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells;
      this.idBuddyDataGridViewTextBoxColumn.DataPropertyName = "IdBuddy";
      this.idBuddyDataGridViewTextBoxColumn.DataSource = this.komponentenBuddyBindingSource;
      this.idBuddyDataGridViewTextBoxColumn.DisplayMember = "Komponentenname";
      this.idBuddyDataGridViewTextBoxColumn.HeaderText = "Buddy";
      this.idBuddyDataGridViewTextBoxColumn.Name = "idBuddyDataGridViewTextBoxColumn";
      this.idBuddyDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
      this.idBuddyDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
      this.idBuddyDataGridViewTextBoxColumn.ValueMember = "Id";
      this.idBuddyDataGridViewTextBoxColumn.Width = 62;

Code for the DataGridView by designer:

private System.Windows.Forms.DataGridView dgvKomponenten;
  this.dgvKomponenten = new System.Windows.Forms.DataGridView();
  ((System.ComponentModel.ISupportInitialize)(this.dgvKomponenten)).BeginInit();

  // 
  // dgvKomponenten
  // 
  this.dgvKomponenten.AllowUserToDeleteRows = false;
  this.dgvKomponenten.AutoGenerateColumns = false;
  this.dgvKomponenten.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
  this.dgvKomponenten.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
        this.komponentennameDataGridViewTextBoxColumn,
        ... (10 other columns) ...
        this.idBuddyDataGridViewTextBoxColumn});
  this.dgvKomponenten.DataSource = this.komponentenBindingSource;
  this.dgvKomponenten.Dock = System.Windows.Forms.DockStyle.Fill;
  this.dgvKomponenten.Location = new System.Drawing.Point(0, 0);
  this.dgvKomponenten.Name = "dgvKomponenten";
  this.dgvKomponenten.Size = new System.Drawing.Size(452, 612);
  this.dgvKomponenten.TabIndex = 9;
  this.dgvKomponenten.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgvKomponenten_CellValueChanged);
  this.dgvKomponenten.DataError += new System.Windows.Forms.DataGridViewDataErrorEventHandler(this.dgvKomponenten_DataError);
  this.dgvKomponenten.RowEnter += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgvKomponenten_RowEnter);
  ((System.ComponentModel.ISupportInitialize)(this.dgvKomponenten)).EndInit();

And some called code by myself:

private void dgvKomponenten_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
  DataGridView dgvChanged = ((DataGridView)sender);
  FilteredTypeDataGridViewComboBoxCell ftdgvcbcSubtyp;

  if (null != dgvChanged.Columns["idTypDataGridViewTextBoxColumn"])
  {
    if (e.ColumnIndex == dgvChanged.Columns["idTypDataGridViewTextBoxColumn"].Index)
    {
      ftdgvcbcSubtyp = (FilteredTypeDataGridViewComboBoxCell)dgvChanged.Rows[e.RowIndex].Cells["idSubtypDataGridViewTextBoxColumn"];
      ftdgvcbcSubtyp.InitCellFilter(e.RowIndex);
      if (!ftdgvcbcSubtyp.Items.Contains(ftdgvcbcSubtyp.Value))
      {
        ftdgvcbcSubtyp.Value = 0;
      }
    }
  }

}

Solution

  • You're right; it appears the combo used in the DGV has a bug where it case insensitively looks up the chosen item based on the display text.. If you have 5,"John" or even 5,"john" you can never select it, because choosing it always finds/sets the selection to the first John (the one with id 1)

    This is the best workaround I've been able to come up with:

        public class Buddy {
            public string Name { get; set; }
            public int Id { get; set; }
        }
    
        public Form1(string s1 = null)
        {
            InitializeComponent();
    
            dataSet1.People.AddPeopleRow(1, "John", 1);
            dataSet1.People.AddPeopleRow(2, "Mary", 1);
            dataSet1.People.AddPeopleRow(3, "Mark", 1);
            dataSet1.People.AddPeopleRow(4, "Luke", 1);
            dataSet1.People.AddPeopleRow(5, "John", 1);
    
            var b = new BindingList<Buddy>();
            var h = new Dictionary<string, int>();
            foreach (var r in dataSet1.People)
            {
                if (!d.TryGetValue(r.Name.ToLower(), out int x))
                    x = 0;
                b.Add(new Buddy { Name = r.Name + (x > 0 ? new string('\0', x) : ""), Id = r.Id });
                d[r.Name.ToLower()] = x + 1;
            }
    
            buddyBindingSource.DataSource = b;
            peopleBindingSource.DataSource = dataSet1.People;
    
        }
    

    Namely, we zip through the list of people building a new list of name/id pairs to show in our combo. Every time we hit a name we've seen before, we add an increasing number of NUL characters onto the end of the name. They don't show in the combo, but they permit the text to be different, so that selecting the 5th John really does select that one, not the first one.