none
DataGridView with different ComboBoxCells per row RRS feed

  • Question

  • Please see picture. The DataGridView is in mixed mode. The Animal column is bound but the Class column is not. The Animal column will contain items like Dog, Chicken, Turtle, Bear, etc. The idea is for the user to select the Class each animal belongs to, such as Mammal, Reptile, Bird, Amphibian, etc.

    The combobox will be different in each row, so I can't set it at the column level. According to the documentation, you should handle the CellValueNeeded event, which is called for each unbound cell. And that's where the fun begins.

    The un-commented code in the CellValueNeeded event somehow creates a situation where the CellValueNeeded event is called repeatedly, resulting in a StackOverflowException within seconds. You'll notice a couple of commented lines in this event referring to a text box. As an experiment, I changed the combo box to a text box, and it worked fine, without problems. But nothing I do with the cell as a combo box seems to work. It always blows the stack.

    Now, outside of that event, in the form constructor, that same code works just fine, but I was hoping not to have to do that. In theory, catching the unbound cells as the DataGridView is filled seems pretty elegant.

    I read that the problem may have something to do with the value supplied to the comb box, and that I should handle the DataError event. However, the DataError event never fires. Breakpoints there never hit.

    Any ideas how to do this are welcome!

    using System.Windows.Forms;
    using System.Collections.Generic;
    using System.ComponentModel;
    
    namespace Requester
    {
        public partial class Form1 : Form
        {
            struct DGVRows
            {
                public string Animal { get; set; }
            }
    
            struct DGVCombo
            {
                public int Value { get; set; }
                public string Display { get; set; }
            }
    
            BindingList<DGVRows> RowList = new BindingList<DGVRows>();
    
            public Form1()
            {
                InitializeComponent();
                dataGridView1.AutoGenerateColumns = false;
                var NewRow = new DGVRows();
                NewRow.Animal = "Dog";
                RowList.Add(NewRow);
                NewRow.Animal = "Trout";
                RowList.Add(NewRow);
                NewRow.Animal = "Lizard";
                RowList.Add(NewRow);
                NewRow.Animal = "Chicken";
                RowList.Add(NewRow);
                dataGridView1.DataSource = RowList;
                Animal.DataPropertyName = "Animal";
                //foreach (DataGridViewRow dgvrow in dataGridView1.Rows)
                //{
                //    var Combo = new List<DGVCombo>();
                //    var x = new DGVCombo();
                //    x.Value = 1;
                //    x.Display = "Mammal";
                //    Combo.Add(x);
                //    x.Value = 2;
                //    x.Display = "Reptile";
                //    Combo.Add(x);
                //    x.Value = 3;
                //    x.Display = "Amphibian";
                //    Combo.Add(x);
                //    var cmb = (dgvrow.Cells[1] as DataGridViewComboBoxCell);
                //    cmb.ValueMember = "Value";
                //    cmb.DisplayMember = "Display";
                //    cmb.DataSource = Combo;
                //}
            }
    
            private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
            {
                if (e.ColumnIndex == 1)
                {
                    //var dgvtxt = (dgv.Rows[e.RowIndex].Cells[1] as DataGridViewTextBoxCell);
                    //e.Value = "sf";
                    //var dgv = sender as DataGridView;
                    //var dgvcmb = (dgv.Rows[e.RowIndex].Cells[1] as DataGridViewComboBoxCell);
                    //dgvcmb.Items.AddRange(new string[] { "A", "B", "C" });
                    //dgvcmb.Value = dgvcmb.Items[1];
                    //e.Value = dgvcmb.Value;
                    var Combo = new List<DGVCombo>();
                    var x = new DGVCombo();
                    x.Value = 1;
                    x.Display = "Mammal";
                    Combo.Add(x);
                    x.Value = 2;
                    x.Display = "Reptile";
                    Combo.Add(x);
                    x.Value = 3;
                    x.Display = "Amphibian";
                    Combo.Add(x);
                    var dgv = sender as DataGridView;
                    var cmb = (dgv.Rows[e.RowIndex].Cells[e.ColumnIndex] as DataGridViewComboBoxCell);
                    cmb.ValueMember = "Value";
                    cmb.DisplayMember = "Display";
                    cmb.DataSource = Combo;
                    cmb.Value = "Mammal";
                    e.Value = cmb.Value;
                }
            }
    
            private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
            {
                e.Cancel = true;
            }
        }
    }
    
    Tuesday, September 17, 2019 3:10 AM

Answers

  • Hi Robert,

    About using event "CellValueNeeded", here is a simple demo you can refer to.

        private void Form1_Load(object sender, EventArgs e)
        {
            dataGridView1.VirtualMode = true;
            dataGridView1.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridView1_CellValueNeeded);
            DataGridViewTextBoxColumn first = new DataGridViewTextBoxColumn();
            first.HeaderText = "first";
            first.Name = "first";
            DataGridViewComboBoxColumn second = new DataGridViewComboBoxColumn();
            second.HeaderText = "second";
            second.Name = "second";
            dataGridView1.Columns.Add(first);
            dataGridView1.Columns.Add(second);
            dataGridView1.RowCount = 4;
            dataGridView1.AllowUserToAddRows = false;
        }
    
        List<List<string>> sourcelist = new List<List<string>> {
            new List<string> { "A", "B", "C" },
            new List<string> { "1", "2", "3" },
            new List<string> { "!", "@", "#" } };
    
        void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
        {
            if (e.RowIndex == this.dataGridView1.RowCount) return;
            switch (this.dataGridView1.Columns[e.ColumnIndex].Name)
            {
                case "first":
                    e.Value = "first" + e.RowIndex.ToString();
                    break;
                case "second":
                    DataGridViewComboBoxCell cbc = (DataGridViewComboBoxCell)dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];
                    cbc.DataSource = sourcelist[e.RowIndex];
                    break;
                default:
                    break;
            }
        }

    Besides, About "CellValueNeeded", you can refer to these documents.

    DataGridViewComboBoxColumn Class

    Implementing Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control

    Walkthrough: Implementing Virtual Mode in the Windows Forms DataGridView Control

    Regards,

    Kyle


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Marked as answer by Robert in SF Saturday, September 21, 2019 1:59 AM
    • Unmarked as answer by Robert in SF Saturday, September 21, 2019 6:48 PM
    • Marked as answer by Robert in SF Saturday, September 21, 2019 7:02 PM
    Wednesday, September 18, 2019 5:28 AM
    Moderator

All replies

  • Hi,

    Do you want to set different combobox for each row? Have you try to add the DataGridViewComboBoxCell to DataGridView directly?

    The following is the demo code.

        // The list for test
        List<AnimalClass> animalClasses = new List<AnimalClass> {
            new AnimalClass { animal = "A", aclass = new List<string> { "C1" } },
            new AnimalClass { animal = "B", aclass = new List<string> { "C2", "C4" } },
            new AnimalClass { animal = "C", aclass = new List<string> { "C3", "C5","C8" } },
            new AnimalClass { animal = "D", aclass = new List<string> { "C2", "C6" } }
        };
    
        private void Form1_Load(object sender, EventArgs e)
        {
            foreach (var item in animalClasses)
            {
                DataGridViewRow newrow = new DataGridViewRow();
                DataGridViewTextBoxCell animalcell = new DataGridViewTextBoxCell
                {
                    Value = item.animal
                };
                newrow.Cells.Add(animalcell);
    
                // Add ComboBoxCell
                DataGridViewComboBoxCell course = new DataGridViewComboBoxCell();
                course.DataSource = item.aclass;
                newrow.Cells.Add(course);
    
                dataGridView1.Rows.Add(newrow);
            }
        }
    
        // The test class to store data
        class AnimalClass
        {
            public string animal { get; set; }
            public List<string> aclass { get; set; }
        }

    Result,

    Hope this can help you.

    Regards,

    Kyle


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Tuesday, September 17, 2019 5:32 AM
    Moderator
  • Thanks for your reply. I did try loading the combo box cell in the form constructor, and that worked. However, that is only good for demo purposes. The constructor and the Load event only happen once, but rows can be added to the DataGridView later.

    The documentation says the purpose of CellValueNeeded is to handle unbound cells when the DataGridView is in virtual mode. Perhaps I am not using the CellValueNeeded event correctly?

    Tuesday, September 17, 2019 3:18 PM
  • Hi Robert,

    About using event "CellValueNeeded", here is a simple demo you can refer to.

        private void Form1_Load(object sender, EventArgs e)
        {
            dataGridView1.VirtualMode = true;
            dataGridView1.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridView1_CellValueNeeded);
            DataGridViewTextBoxColumn first = new DataGridViewTextBoxColumn();
            first.HeaderText = "first";
            first.Name = "first";
            DataGridViewComboBoxColumn second = new DataGridViewComboBoxColumn();
            second.HeaderText = "second";
            second.Name = "second";
            dataGridView1.Columns.Add(first);
            dataGridView1.Columns.Add(second);
            dataGridView1.RowCount = 4;
            dataGridView1.AllowUserToAddRows = false;
        }
    
        List<List<string>> sourcelist = new List<List<string>> {
            new List<string> { "A", "B", "C" },
            new List<string> { "1", "2", "3" },
            new List<string> { "!", "@", "#" } };
    
        void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
        {
            if (e.RowIndex == this.dataGridView1.RowCount) return;
            switch (this.dataGridView1.Columns[e.ColumnIndex].Name)
            {
                case "first":
                    e.Value = "first" + e.RowIndex.ToString();
                    break;
                case "second":
                    DataGridViewComboBoxCell cbc = (DataGridViewComboBoxCell)dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];
                    cbc.DataSource = sourcelist[e.RowIndex];
                    break;
                default:
                    break;
            }
        }

    Besides, About "CellValueNeeded", you can refer to these documents.

    DataGridViewComboBoxColumn Class

    Implementing Virtual Mode with Just-In-Time Data Loading in the Windows Forms DataGridView Control

    Walkthrough: Implementing Virtual Mode in the Windows Forms DataGridView Control

    Regards,

    Kyle


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Marked as answer by Robert in SF Saturday, September 21, 2019 1:59 AM
    • Unmarked as answer by Robert in SF Saturday, September 21, 2019 6:48 PM
    • Marked as answer by Robert in SF Saturday, September 21, 2019 7:02 PM
    Wednesday, September 18, 2019 5:28 AM
    Moderator
  • Thank you, Kyle. That worked!

    • Edited by Robert in SF Saturday, September 21, 2019 11:58 PM
    Saturday, September 21, 2019 2:03 AM