none
Placing a ComboBox onto a DataGridView Cell. RRS feed

  • Question

  • I'm working on an application where users can enter data via a datagridview.
    Some of the columns need to display a combobox so the user can select from particular values.
    I've got this working, to a fashion, but there are things that bug me.
    The application works by placing a combobox over the datagridview cell when the focus gets to a particular row, so that the user can select from the drop down list. After a value is selected it copies that to the datagrid cell.
    The application works fine if I only use the mouse for selecting cell. If I use the TAB key I have to tab twice on some columns. If I don't move off the cell that is being edited, then the value is not saved, (I guess because endedit has not been called).
    Anyway the code is a tyranny of fudges. It must be way more complicated than it needs to be.

    So, my question. Does anyone know of a working example of this sort of application in vb.net or even c#?
    I’ve searched high and low trying to find something.
    I’ve found bits of stuff like using ProtectedOverides etc but no full implementation, of what seems to me to be a pretty fundamental requirement. Many professional applications seem to take this approach.
    I didn't want to use a combobox column as I don't think it looks very good. 
    To be honest, I last worked on this three years ago and gave up. On looking at it again I was surprised how well it worked, but it's not right.
    Tuesday, April 2, 2019 11:57 AM

All replies

  • Hello,

    Is there a reason for not using a DataGridViewComboBox column ? I have an example found in the MSDN code sample.

    For the code sample see this project.


    Please remember to mark the replies as answers if they help and unmarked them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.

    NuGet BaseConnectionLibrary for database connections.

    StackOverFlow
    profile for Karen Payne on Stack Exchange

    Tuesday, April 2, 2019 12:31 PM
    Moderator
  • Hi

    No idea if this will be helpful. This is a stand alone example nand needs nthe controls added in the Designer as listed at top of code.

    This uses both a ComboBox and a DateTimePicker as 'in cell' items and they do what you describe.

    ' CB IN DGV
    ' CB DATASOURCE
    ' DATETIMEPICKER IN DGV
    ' DGV DATASOURCE
    ' SUM DT COLUMN
    ' ID NUMBER IN ROW HEAD£R
    ' DGV ADD/REMOVE ROW
    ' DGV MOVE ROW UP/DOWN
    
    ' Needs: DataGridView named DGV,
    ' ComboBox named cb
    ' DateTimePicker named Picker
    ' Buttons 1 - 4
    ' Labels 1 - 2
    Option Strict On
    Option Explicit On
    Public Class Form1
      Dim dt As New DataTable("myPayments")
      Dim path As String = Application.StartupPath & "\Data\Data.xml"
      Dim RowAdd As Boolean = False
      Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        If Not IO.Directory.Exists(IO.Path.GetDirectoryName(path)) Then
          IO.Directory.CreateDirectory(IO.Path.GetDirectoryName(path))
        End If
        dt.WriteXml(path)
        My.Settings.Sz = Size
        My.Settings.Loc = Location
      End Sub
      Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Location = My.Settings.Loc
        Size = My.Settings.Sz
        MinimumSize = New Size(600, 300)
        Text = "MyPayments"
        With dt
          .Columns.Add("CC", GetType(Boolean))
          .Columns("CC").DefaultValue = False
          .Columns.Add("Date", GetType(DateTime))
          .Columns.Add("Where", GetType(String))
          .Columns.Add("Amount", GetType(Decimal))
          .Columns.Add("Notes", GetType(String))
    
          If IO.File.Exists(path) Then
            .ReadXml(path)
          End If
    
          ' uncomment to set uo dummy data
          ' .Rows.Clear
          'For i As Integer = 1 To 4
          '  .Rows.Add(True, Now.AddDays(-11), "EXAMPLE", i * 3.33, "Example notes")
          'Next
    
        End With
        With DGV
          .DataSource = dt
          .AllowUserToAddRows = False
          .AllowUserToDeleteRows = False
          .MultiSelect = False
          .SelectionMode = DataGridViewSelectionMode.CellSelect
          .Columns("Notes").AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
          .Columns("Date").DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
          .Columns("Date").DefaultCellStyle.Format = "dd MMM yyyy"
          .Columns("Amount").DefaultCellStyle.Format = "£0.00"
          .Columns("Amount").DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
          .Columns("Notes").DefaultCellStyle.WrapMode = DataGridViewTriState.False
          .AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells
          .EnableHeadersVisualStyles = False
          .RowHeadersDefaultCellStyle.Font = DGV.DefaultCellStyle.Font
          .RowHeadersDefaultCellStyle.ForeColor = Color.Blue
          .RowHeadersDefaultCellStyle.BackColor = Color.FromArgb(255, 255, 255, 192)
          .RowHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.TopRight
          .RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.EnableResizing
          .RowHeadersWidth = 80
          .ColumnHeadersDefaultCellStyle.Font = New Font("Arial", 12, FontStyle.Bold)
          .Columns("Amount").HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight
          .Columns("Date").HeaderCell.Style.Alignment = DataGridViewContentAlignment.TopCenter
          With .TopLeftHeaderCell
            .Value = "ID "
            .Style.Alignment = DataGridViewContentAlignment.TopRight
            .Style.Font = DGV.DefaultCellStyle.Font
          End With
        End With
        With Picker
          .Font = DGV.DefaultCellStyle.Font
          .Format = DateTimePickerFormat.Custom
          .CustomFormat = "dd MMM yyyy"
          .BringToFront()
          .Visible = False
          .TabStop = False
          .ShowUpDown = False
          .Value = Now
        End With
        With CB
          .Visible = False
          .TabStop = False
          .BringToFront()
          .Font = DGV.DefaultCellStyle.Font
          .DataSource = GetCB()
          .AutoCompleteSource = AutoCompleteSource.ListItems
          .AutoCompleteMode = AutoCompleteMode.SuggestAppend
        End With
      End Sub
      Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
        If DGV.RowCount < 1 Then Exit Sub
        DGV("Amount", DGV.RowCount - 1).Selected = True
        Calc()
        Renumber()
      End Sub
      Private Sub DGV_CellEnter(sender As Object, e As DataGridViewCellEventArgs) Handles DGV.CellEnter
        CBPickOFF()
        Select Case e.ColumnIndex
          Case DGV.Columns("Date").Index
            SetPicker(e.ColumnIndex, e.RowIndex)
          Case DGV.Columns("Where").Index
            SetCB(e.ColumnIndex, e.RowIndex)
            Calc()
        End Select
      End Sub
      Sub SetCB(col As Integer, row As Integer)
        Dim r As Rectangle = DGV.GetCellDisplayRectangle(col, row, False)
        r.X += 2
        r.Y += 3 + DGV.CurrentCell.Size.Height \ 2 - CB.Height \ 2
        CB.Width = r.Width + 18
        CB.Location = r.Location
        CB.DataSource = GetCB()
        CB.SelectedItem = DGV("Where", DGV.CurrentRow.Index).Value.ToString
        CB.Visible = True
        CB.Select()
      End Sub
      Sub SetPicker(col As Integer, row As Integer)
        Picker.Visible = True
        Picker.Select()
        Try
          Picker.Value = CDate(DGV.CurrentCell.Value)
        Catch ex As InvalidCastException
          Picker.Value = Now
        Catch ex As Exception
          MessageBox.Show("Sorry, ERROR assigning value to DateTimePicker!")
        End Try
        Dim r As Rectangle = DGV.GetCellDisplayRectangle(col, row, False)
        r.X += 2
        r.Y += 3 + DGV.CurrentCell.Size.Height \ 2 - Picker.Height \ 2
        Picker.Width = r.Width + 17
        Picker.Location = r.Location
      End Sub
      Private Sub Picker_Validated(sender As Object, e As EventArgs) Handles Picker.Validated
        DGV("Date", DGV.CurrentRow.Index).Value = Picker.Value.ToString("dd MMM yyyy")
        Picker.Visible = False
      End Sub
      Private Sub CB_Validated(sender As Object, e As EventArgs) Handles CB.Validated
        DGV("Where", DGV.CurrentRow.Index).Value = CB.Text.ToUpper
        CB.Visible = False
      End Sub
      Function GetCB() As List(Of String)
        Dim d As New List(Of String)
        For Each r As DataRow In dt.Rows
          r.BeginEdit()
          Dim c As String = Nothing
          c = r("Where").ToString.ToUpper
          If c.Length > 0 AndAlso Not d.Contains(c) Then
            d.Add(c)
          End If
        Next
        Return d
      End Function
      Sub Renumber()
        Dim num As Integer = 1
        Dim textSize As New Size(0, 0)
        Dim g As Graphics = DGV.CreateGraphics
        For Each r As DataGridViewRow In DGV.Rows
          textSize = TextRenderer.MeasureText(g, num.ToString, DGV.DefaultCellStyle.Font, textSize, TextFormatFlags.NoPadding)
          r.HeaderCell.Value = num.ToString
          num += 1
        Next
        DGV.RowHeadersWidth = 38 + textSize.Width
      End Sub
      Sub Calc()
        Label2.Text = dt.AsEnumerable().Where(Function(row) row.Field(Of Boolean)("CC") = True).Sum(Function(row) row.Field(Of Decimal)("Amount")).ToString("0.00")
      End Sub
      Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' MOVE UP
        If DGV.RowCount < 2 Then Exit Sub
        CBPickOFF()
        Dim r As Integer = DGV.CurrentRow.Index
        If r < 1 Then Exit Sub
        Dim c As Integer = DGV.CurrentCell.ColumnIndex
        Dim hr As DataRow = dt.Rows(r)
        Dim nr As DataRow = dt.NewRow
        nr.ItemArray = hr.ItemArray
        dt.Rows.Remove(hr)
        dt.Rows.InsertAt(nr, r - 1)
        Renumber()
        DGV(c, r - 1).Selected = True
      End Sub
      Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        ' MOVE DOWN
        If DGV.RowCount < 2 Then Exit Sub
        CBPickOFF()
        Dim r As Integer = DGV.CurrentRow.Index
        If r = DGV.RowCount - 1 Then Exit Sub
        Dim c As Integer = DGV.CurrentCell.ColumnIndex
        Dim hr As DataRow = dt.Rows(r)
        Dim nr As DataRow = dt.NewRow
        nr.ItemArray = hr.ItemArray
        dt.Rows.Remove(hr)
        dt.Rows.InsertAt(nr, r + 1)
        Renumber()
        DGV(c, r + 1).Selected = True
      End Sub
      Private Sub DGV_SizeChanged(sender As Object, e As EventArgs) Handles DGV.SizeChanged
        CBPickOFF()
      End Sub
      Private Sub DGV_Scroll(sender As Object, e As ScrollEventArgs) Handles DGV.Scroll
        CBPickOFF()
      End Sub
      Private Sub DGV_EndEditMode(sender As Object, e As EventArgs) Handles DGV.CurrentCellDirtyStateChanged
        Select Case DGV.CurrentCell.ColumnIndex
          Case DGV.Columns("CC").Index
            If DGV.IsCurrentCellDirty Then
              DGV.CommitEdit(DataGridViewDataErrorContexts.Commit)
              Calc()
            End If
          Case DGV.Columns("Amount").Index
            Calc()
        End Select
      End Sub
      Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        ' ADD NEW ROW
        CBPickOFF()
        dt.Rows.Add(False, Now, Nothing, 0, Nothing)
        Renumber()
        DGV("Amount", DGV.RowCount - 1).Selected = True
        DGV.BeginEdit(True)
      End Sub
      Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
        ' REMOVE ROW
        If DGV.RowCount < 1 Then Exit Sub
        CBPickOFF()
        DGV.Rows.Remove(DGV.CurrentRow)
        Calc()
        Renumber()
        If DGV.RowCount < 1 Then Exit Sub
        DGV("Amount", DGV.CurrentRow.Index).Selected = True
        DGV.BeginEdit(True)
      End Sub
      Sub CBPickOFF()
        Picker.Visible = False
        CB.Visible = False
      End Sub
      Private Sub DGV_Sorted(sender As Object, e As EventArgs) Handles DGV.Sorted
        Renumber()
      End Sub
    End Class
    


    Regards Les, Livingston, Scotland

    Tuesday, April 2, 2019 12:48 PM
  • Hi,
    Thanks for the code samples.
    Les. I tried your demo and it is similar to what I'm trying to do, however there are a lot of things it doesn't implement eg:
    TAB Key If I try to tab from the combobox control moves to the buttons rather than the next cell in the grid.
    When I highlight the cell that displays the combobox then whatever happens to be in the combobox gets copied to the cell rather than taking whatever is in the cell and copying that to the combobox.
    Not setup to handle the enter key.
    The re-ordering is very useful.

    I've got these things working ok, but it's just issues like having to hit tab twice from the combobox to get to the next cell (Strangely not always, something to do with what's actually got focus.) and other irritations.

    Karen,
    I downloaded your sample, got it into the ide and ran the script succesfully but I don't know how to make it run the VB version, can you let  me know how to do that?

    Tuesday, April 2, 2019 5:12 PM
  • Hi Andy,

    Sorry for the delay. There are two project, one for VB and one for C as per below, ignore the C project. In DataOperations.vb change the Data Source to your server name or if SQL-Server Express to .\SQLEXPRESS

    Private ConnectionString As String = "Data Source=KARENS-PC;Initial Catalog=ExampleDataGridViewComboBox_1;Integrated Security=True" 


    Please remember to mark the replies as answers if they help and unmarked them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.

    NuGet BaseConnectionLibrary for database connections.

    StackOverFlow
    profile for Karen Payne on Stack Exchange

    • Proposed as answer by Alex Li-MSFT Thursday, April 11, 2019 3:17 AM
    Tuesday, April 2, 2019 10:59 PM
    Moderator