0

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;
      }
    }
  }

}
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
UHM
  • 303
  • 1
  • 7
  • Show the setup of your DGVCombo. What you've described isn't how i'd have done it but it *should* work.. – Caius Jard Sep 17 '21 at 10:02
  • I updated the question with the code generated by the designer. – UHM Sep 17 '21 at 11:47
  • Can you show the same for the datagridview itself? – Caius Jard Sep 17 '21 at 11:52
  • I updated further stuff – UHM Sep 17 '21 at 12:04
  • I had a play with a demo project and, other than a slight oddity in a grid DataError that is thrown when you make someone their own buddy, I couldn't find any problem with the way you have it structured. I noted also that the error on self-friending went away if the bindingsources were bound to the datatable direct, rather than being bound to the dataset, with a datamember of the table name. Do you still get issues if you remove the DGV event handlers for those 3 various things? – Caius Jard Sep 17 '21 at 20:19
  • When I remove the 3 event handlers I get many system argument exceptions at the InitCellFilter above for another column. For that is the DataError event handler to catch them. The problem described above doesn't change through this. – UHM Sep 18 '21 at 08:06
  • How did you refer to the DataTable? If I change it to "// // komponentenBuddyBindingSource // // this.komponentenBuddyBindingSource.DataMember = "Komponenten"; this.komponentenBuddyBindingSource.DataSource = this.rvmConfigDataSet.Tables["Komponenten"]; it doesn't change behaviour. – UHM Sep 18 '21 at 08:20
  • I'd remove the cell filter too; start simple – Caius Jard Sep 18 '21 at 12:55
  • I was just using a strongly typed DataSet and a single table in it.. I'll post the project later.. or you can follow: make new DataSet file, open in designer, add one table with 3 columns (id, name, buddyid), save and switch to forms designer, open data sources window, drop table onto form, copypaste bindingsource, change buddy Id to combo column (set up as you have there - DataSource is bindingsource 2, DisplayMember=Name, ValueMember=id), then just set both bindingsources DataSource property to be = thedatasetname.TheTableName in constructor (it is strongly typed so is avail as a property) – Caius Jard Sep 18 '21 at 12:56
  • Right Click Save As [this gif](https://i.stack.imgur.com/a3Ycq.gif), then rename it to .zip and open it with winrar or winzip (but 7 zip and windows compressed folders don't like it when we hide zips inside gifs). It's the demo project i did to check on your issue. It seems not to present for for me there, so I think some other code youre doing has some interference; the cell filtering sounds a bit odd - what are you trying to do with it (we might be able to do it anothjer way) – Caius Jard Sep 18 '21 at 20:35
  • Oh, I think there is a misunderstanding. The problem is, that I have two rows which have the same name and I want to reference them to the other one of them. In your demo you have each name only once. If I add e.g. dataSet1.People.AddPeopleRow(5, "John", 1); to your project I get the same effect with mixing the Johns. – UHM Sep 20 '21 at 07:43
  • But everything is ID based, so I don't see how.. You should see two John in the dropdown.. – Caius Jard Sep 20 '21 at 11:29
  • Yes. There is an upper John (1) and a lower John (2) in the list. If I mark in the combobox of first row the John(2) and then in the last row the John(1) it starts mixing. If you open combobox of first row then John(1) is marked. If you switch it back to John(2) and you open the combobox of last row there is John(1) marked. You can do that forever without reaching the goal that in in first row John(2) and in last row John(1) is marked. – UHM Sep 20 '21 at 11:57
  • Whereby if you give up and look at the data in the database there are exactly the BuddyIDs of the Johns how they appearing if you open the comboboxes. – UHM Sep 20 '21 at 12:02

1 Answers1

1

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.

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • Sorry, that's not the point. I never had a problem to set the value of a combo box to the 5th John. That always worked fine. The problem is, that it doesn't keep the value if you later set another Buddy-combobox in another row to another John. Then it automatically changes it's value to to the new value of the other combo box. And that's really crazy. Unluckily this crazy effect is with your solution still there. – UHM Sep 21 '21 at 07:20
  • I don't understand the description of the problem. Can you explain step by step what to do to reproduce it? – Caius Jard Sep 21 '21 at 07:26
  • I gave a try at what I think you mean: https://i.stack.imgur.com/zbceq.gif - but all those rows where I chose some other value (and e.g. at one point there are 3 Mark rows and I change 1 to Luke, but the others don't change from Mark -> Luke (which is what I think you're saying). I also put another column showing the raw Id value that the combo is setting, and they seem to change sensibly.. Let me know how to repro your problematic one – Caius Jard Sep 21 '21 at 08:31
  • No, it changes only this fields which have already that string inside to which you set another field I thought. But by trying that yet I got one more situation. So step by step: 1) Start your program and check all Buddy-Johns. All rows have John 1 selected. 2) Change second Row to Mark. If you like you can check there are still all John-Buddys in the combo boxes at John 1. 3) Switch the John of row 4 (Luke) to John 5 and check all Buddy-Johns. Now all rows except row 2 are set to John 5. 4) Switch the John of row 3 (Mark) to Mary and check all Buddy-Johns. Now all switched back to John 1. – UHM Sep 21 '21 at 09:15
  • That means, although you just used the combobox of one single row all other rows changed the value too! If you switch to a John all other rows with John switch to the same John too. And if you switch an other Buddy of an other row to another name all other rows with John switch to John 1, although you didn't touch them. – UHM Sep 21 '21 at 09:15
  • I think I see what you mean.. You mean that after choosing `John\0` (the bottom one in the combo) in step 3), if you open e.g. the combo on row id 3 (Mark) who we never touched, then the dropdown under the combo has the bottom John highlighted in blue, not the top one (even though we never touched it). Yeah, that looks like it's just the combo being stupid. The data in the table is correct though - see how I added another textbox column to show what BuddyId really is in the table (as a number) - that is correct, even though the dropdown highlights th wrong thing – Caius Jard Sep 21 '21 at 11:40
  • Really I think you're going to have to put something in the combo Other than just "John" otherwise how will the user know which one to pick? This bug has probably existed for years because noone in their right mind would show the user a combo with multiple identical elements in and it be absolutely vital that they choose the right one even though they can't tell the difference between them... If you modify the code so `b.Add(new Buddy { Name = r.Name + (x > 0 ? x.ToString() : ""), Id = r.Id });` it comes right. Put the last name in? Put the ID in? – Caius Jard Sep 21 '21 at 11:45
  • In general there are some new aspects which were new to me, especially the BindingList, and it looks as a good way. I tried to move this way into my software, but unluckily now I get an InvalidCastException, that the object of type "Buddy" can't be converted into "System.Data.DataRowView". And I haven't an idea what's causing that. – UHM Sep 22 '21 at 12:18
  • Can you show the code that inits the bindinglist and binds it to the binding source? (It could also be a normal list too btw) – Caius Jard Sep 22 '21 at 12:23
  • At the moment it looks like that: `this.komponentenTableAdapter.Fill(this.rvmConfigDataSet.Komponenten); foreach (RvmConfigDataSet.KomponentenRow row in rvmConfigDataSet.Komponenten) { String strDisplay; strDisplay = row.Komponentenname; if (!dictBuddy.TryGetValue(strDisplay.ToLower(), out int x)) x = 0; blBuddy.Add(new Buddy { Name = strDisplay + (x > 0 ? new string('\0', x) : ""), Id = row.Id }); dictBuddy[strDisplay.ToLower()] = x + 1; } komponentenBuddyBindingSource.DataSource = blBuddy;` – UHM Sep 22 '21 at 12:34
  • Problem was `komponentenBuddyBindingSource.DataSource = blBuddy;` - because the BindingList can't be set to the BindigSource. But it can be set to the DataSource of the DataColumn directly. So correct is `this.idBuddyDataGridViewTextBoxColumn.DataSource = blBuddy;` – UHM Sep 30 '21 at 09:32
  • In general that is a good extension for me also for combining two fields into one display. So also the problem with two equal texts in different rows is not anymore through adding an additional text of another column. But because all texts are edited by the users of the program you never know what they will enter in the future and if then there are equal texts again. But one more problem I have: there are rows which don't have a buddy at all, therefore I would like to add a row in the combo box with DBNull as index. I will search for it ... – UHM Sep 30 '21 at 09:39
  • If you want to combine two columns to one for display it's easier to use an Expression column - add a datacolumn, set its .Expression property to e.g. `[FirstName] + ' ' + [LastName]` and then bind that. It's not editable, for obvious reasons, but it does well for display/dynamic update if either firstname/lastname changes – Caius Jard Sep 30 '21 at 11:16
  • Ok. But then I can't add a extra row for no Buddy and I have the problem above again when two combinations come to the same text. – UHM Sep 30 '21 at 14:20
  • I think we've been over the "same text" problem and have found a reasonable solution: ensure the text is not the same, otherwise the combo becomes useless because the user can't tell the entries apart anyway. If I were struggling to get "no buddy" chosen with things as they were I think I'd just define a special buddy ID like -1 to mean "no buddy" and process it into null later if it had to be null – Caius Jard Sep 30 '21 at 14:30