Search code examples
c#winformsdatagridviewdatagridviewcomboboxcolumn

How to populate ComboBox DataGridView depending on Row ID


I use DataGridViewComboBoxColumn in DataGridView. What I need is to filter ComboBox values depending on each Row ID.

Si it should look like

-----------------------------
Row ID       ComboBox
              Values
            ID    Name
-----------------------------
1            1     A
             2     B
-----------------------------               
2            3     C
             4     D
-----------------------------

So what I do is creating DataGridViewComboBoxColumn with empty DataSource like it has been shown in code below

var d = new DataGridViewComboBoxColumn()
{
 HeaderText = columnHeader,
 Width = 50,
 DataPropertyName = "D" + i.ToString(),
 DataSource = new BindingSource(){ DataSource = new List<WorkTypeItem>() },
 ValueMember = "ID",
 DisplayMember = "Name",
 Name = "D" + i.ToString()
};
dgvCalendar.Columns.Add(d);

And I assume to populate Row ComboBox for each cell in the CellFormatting event.

 private void DgvCalendar_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{

  // Get object bounded with Row  
  CalendarDetailView row = dgvCalendar.Rows[e.RowIndex].DataBoundItem as CalendarDetailView;

  // By row ID  filter list for cell ComboBox
  var workItems = MyWorkItems.Where(x => x.ID == row.ID); 

  // So how we can to populate cell ComboBox now?
  dgvCalendar.Rows[e.RowIndex].Cells[1] ..... ??? = workItems ;


}

I do this because I cannot display all possible values in the cell ComboBox because they are not suite for the each row. So the idea is not display 1000 values in the ComboBox but only values referenced by Row ID.

My objective is the each cell has its own list of the values but from the same List().

Any clue how to do it?


UPDATE #1

I just get working the code thank to Marco Guignard! I am just update it for C# version.

private void DgvCalendar_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
   if (e.Control is ComboBox)
   {
        int currentKey = (int)dgvCalendar.CurrentRow.Cells[dgvCalendar.CurrentCell.ColumnIndex].Value;

        ComboBox editor = (ComboBox)e.Control;
        CalendarDetailView row = dgvCalendar.CurrentRow.DataBoundItem as CalendarDetailView;

       var filteredItems = WorkTypes.Where(x =>x.ID = row.ID).ToList();

       editor.DataSource = filteredItems;
        editor.SelectedValue = currentKey;
   }           
}

UPDATE #2

Acctually I've finished up it with a similar approach but using a different event.

So initially on column creation logic I use all possible records from the database's table to initialize DataGridView with values and when DataGridView appears we see it.

And when we have to edit combobox cell we just apply objects allowed value list.

private void DgvCalendar_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
    {
        if (dgvCalendar.Columns[e.ColumnIndex].Name.StartsWith("D"))
        {
           var isComboBox = dgvCalendar.CurrentCell is DataGridViewComboBoxCell;
           if (isComboBox)
           {
               DataGridViewComboBoxCell dgViewComboBoxCell = dgvCalendar.CurrentCell as DataGridViewComboBoxCell;

               CalendarDetailView row = dgvCalendar.Rows[e.RowIndex].DataBoundItem as CalendarDetailView;

               dgViewComboBoxCell.DataSource = new BindingSource()
               {
                 DataSource = row.WorkTypes
               };
          }
      }
    }

Solution

  • You have to handle the EditingControlShowing Event.

    First you have to check the CurrentCell Column of your datagridview is corresponding to the combobox column. Then you can change the combobox editor datasource as you wish.

    Don't forget to keep a global list as datasource for the column, as it will still be used for the display of the cells !

    Here is a sample in VB.NET (the FixupDropDownWidth is not needed, it's just a bonus for a better UI display).

            Private Sub DataGridView_EditingControlShowing(sender As System.Object, e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles DataGridView.EditingControlShowing
                    'Route the event depending the cells being edited
                    Try
                        If Me.DataGridView.CurrentCell.OwningColumn Is Me.DataGridView.Columns("comboboxcolumnname") Then
                            comboboxcolumnname_EditingControlShowing(sender, e)
                        End If
                    Catch ex As Exception
        'Exception handling...
                    End Try
                End Sub
    
        Private Sub comboboxcolumnname_EditingControlShowing(sender As System.Object, e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs)
            Dim editor As ComboBox = CType(e.Control, ComboBox)
            Dim currentKey As Short = Me.DataGridView.CurrentRow.Cells("comboboxcolumnname").Value
    
            'Filter editor datasource, remove inactives items except current
            Dim listObject As New List(Of Object)
            listObject.AddRange(GlobalList.Where(Function(c) c.FilterCondition = True OrElse c.ID = currentKey).OrderBy(Function(c) c.Name))
            editor.DataSource = listObject
    
            editor.SelectedValue = currentKey
    
            'Adapt the width of the DropDown
            CType(Me.DataGridView.Columns("comboboxcolumnname"), DataGridViewComboBoxColumn).FixupDropDownWidth(listObject)
        End Sub
    
     <Extension()>
        Public Sub FixupDropDownWidth(column As DataGridViewComboBoxColumn, items As IEnumerable)
            Dim width As Integer = column.DropDownWidth
    
            Dim vertScrollBarWidth As Integer = 0
            If column.Items.Count > column.MaxDropDownItems Then vertScrollBarWidth = SystemInformation.VerticalScrollBarWidth
    
            Dim g As Graphics = column.DataGridView.CreateGraphics()
            Dim font As Font
    
            font = column.DefaultCellStyle.Font
            If font Is Nothing Then font = column.DataGridView.Font
    
            Dim maxWidth As Integer = 0
            For Each item In items
                Dim stringValue As String
                If item.GetType.GetProperty(column.DisplayMember) IsNot Nothing Then
                    stringValue = CStr(item.GetType().GetProperty(column.DisplayMember).GetValue(item, Nothing))
                Else
                    stringValue = item.ToString
                End If
                maxWidth = g.MeasureString(CStr(stringValue), font).Width + vertScrollBarWidth
                If width < maxWidth Then width = maxWidth
            Next
    
            column.DropDownWidth = width
        End Sub