Search code examples
c#datagridviewbindingsource

Each row (object) with own binding source in datagridview?


I'm writing a small application in C# (.NET 4.0). I have a datagridview, each row represents one object. I want a combobox column that allows to choose a specific property of that object.

Example:

public class Car
{
   public String Make {get; set;}
   public BindingList<String> AllColors {get; set;}
   public int SelectedColorIndex {get; set;}
}

Each row represents one Car object. Each (different) car object has it's own selection of possible colors (AllColors). I want to have a column where you can set SelectedColorIndex by choosing one of the colors from AllColors (AllColors is specific to each Car object).

Note: I made up this example but it describes what I want to accomplish.

How can I accomplish this? The only solution that I found was to have a specific combobox outside of the datagridview using which you can change the selected row. And in row enter event I changed the datasource of the bindingsource to the current "AllColors".

Thank you for your time and answers.


Solution

  • Here is the code behind of a working example where the colors available are filtered down to the colors provided in the bound object's AllColors list.

    The magic happens in the CellBeginEdit and CellEndEdit event handlers - there we provide each combo box cell with the list from the bound row and then reset it on exit.

    One thing this relies upon is having a master list which contains all colours - there is no way around this.

    Also I've added handling for the case where a new row needs default values. All I do is set the selected index by default to one. Of course this wouldn't work in the real world, you would need something a bit smarter! The DefaultValuedNeeded event is describe here on MSDN.

    public partial class Form1 : Form
    {
    
        private BindingSource cars;
        private BindingSource masterColors;
    
        public Form1()
        {
            InitializeComponent();
    
            masterColors = new BindingSource();
            masterColors.Add(new CarColor{Name = "Blue", Index = 1});
            masterColors.Add(new CarColor{Name = "Red", Index = 2});
            masterColors.Add(new CarColor { Name = "Green", Index = 3 });
            masterColors.Add(new CarColor { Name = "White", Index = 4 });
    
            BindingList<CarColor> fordColors = new BindingList<CarColor>();
            fordColors.Add(new CarColor{Name = "Blue", Index = 1});
            fordColors.Add(new CarColor{Name = "Red", Index = 2});
    
            BindingList<CarColor> toyotaColors = new BindingList<CarColor>();
            toyotaColors.Add(new CarColor { Name = "Green", Index = 3 });
            toyotaColors.Add(new CarColor { Name = "White", Index = 4 });
    
            cars = new BindingSource();
            cars.Add(new Car { Make = "Ford", SelectedColorIndex = 1, AllColors = fordColors });
            cars.Add(new Car { Make = "Toyota", SelectedColorIndex = 3, AllColors = toyotaColors });
    
            dataGridView1.DataSource = cars;
            dataGridView1.Columns["SelectedColorIndex"].Visible = false;
            //dataGridView1.Columns["AllColors"].Visible = false;
    
            DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
            col.Name = "AvailableColors";
            col.DataSource = masterColors;
            col.DisplayMember = "Name";
            col.DataPropertyName = "SelectedColorIndex";
            col.ValueMember = "Index";
            dataGridView1.Columns.Add(col);
    
            dataGridView1.CellBeginEdit += new DataGridViewCellCancelEventHandler(dataGridView1_CellBeginEdit);
            dataGridView1.CellEndEdit += new DataGridViewCellEventHandler(dataGridView1_CellEndEdit);
            dataGridView1.DefaultValuesNeeded += new DataGridViewRowEventHandler(dataGridView1_DefaultValuesNeeded);
        }
    
        void dataGridView1_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
        {
            if (e.ColumnIndex == dataGridView1.Columns["AvailableColors"].Index)
            {
                if (e.RowIndex != dataGridView1.NewRowIndex)
                {
                    // Set the combobox cell datasource to the filtered BindingSource
                    DataGridViewComboBoxCell dgcb = (DataGridViewComboBoxCell)dataGridView1
                                    [e.ColumnIndex, e.RowIndex];
                    Car rowCar = dataGridView1.Rows[e.RowIndex].DataBoundItem as Car;
                    dgcb.DataSource = rowCar.AllColors;
                }
    
            }
        }
    
        private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == dataGridView1.Columns["AvailableColors"].Index)
            {
                // Reset combobox cell to the unfiltered BindingSource
                DataGridViewComboBoxCell dgcb = (DataGridViewComboBoxCell)dataGridView1
                                [e.ColumnIndex, e.RowIndex];
                dgcb.DataSource = masterColors; //unfiltered
            }
        }
    
        void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
        {
            e.Row.Cells["SelectedColorIndex"].Value = 1;
        }
    
    }
    
    public class Car
    {
        public String Make { get; set; }
        public BindingList<CarColor> AllColors { get; set; }
        public int SelectedColorIndex { get; set; }
    }
    
    public class CarColor
    {
        public String Name { get; set; }
        public int Index { get; set; }
    }
    

    1 I first learned how to do this from the DataGridView FAQ, a great resource written by Mark Rideout, the program manager at the time for the DataGridView at Microsoft. The example in the FAQ filters based upon another combobox, and uses DataTables but the principle is the same.