Showing posts with label Combobox. Show all posts
Showing posts with label Combobox. Show all posts

Sunday, December 16, 2007

Dynamic ComboBox in a DataGridView

And here is the second installment. It is generally convenient to bind the grid column to a single datasource such that all dropdowns have identical items. However, certain applications require that the items in the dropdown depend upon other attributes of the row.

For this example, I will create a three column grid with the first column containing the Employee Name, the second one containing the Department and third one being the dynamic combobox column that lists the positions for the particular department.

Once again, I have created the datasources at runtime to provide a complete example. To add that dynamic feel, I have decided to handle a DataGridView event - CellValueChanged. You can of course set this value after binding the original gridview source as well.

The Setup code that declares two dummy datasources and binds the Employee datasource with the gridview.

    'Create the sample list of names

    Dim dtNames As DataTable = New DataTable

    dtNames.Columns.Add("Name", GetType(String))

    dtNames.Columns.Add("Department", GetType(String))

 

    Dim dr As DataRow

    dr = dtNames.NewRow : dr.Item("Name") = "Alvin Menezes" : dr.Item("Department") = "" : dtNames.Rows.Add(dr)

    dr = dtNames.NewRow : dr.Item("Name") = "Saikumar Chettiar" : dr.Item("Department") = "" : dtNames.Rows.Add(dr)

    dr = dtNames.NewRow : dr.Item("Name") = "Sample Name 1" : dr.Item("Department") = "" : dtNames.Rows.Add(dr)

    dr = dtNames.NewRow : dr.Item("Name") = "Sample Name 2" : dr.Item("Department") = "" : dtNames.Rows.Add(dr)

 

    'create the sample department - position choices

    dtChoices = New DataTable

    dtChoices.Columns.Add("Department", GetType(String))

    dtChoices.Columns.Add("Position", GetType(String))

 

    dr = dtChoices.NewRow : dr.Item("Department") = "IT" : dr.Item("Position") = "Tech Architect" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "IT" : dr.Item("Position") = "Project Manager" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "IT" : dr.Item("Position") = "Project Leader" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "IT" : dr.Item("Position") = "Developer" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "HR" : dr.Item("Position") = "Manager" : dtChoices.Rows.Add(dr)

    dr = dtChoices.NewRow : dr.Item("Department") = "HR" : dr.Item("Position") = "Executive" : dtChoices.Rows.Add(dr)

 

 

    'Bind the datagridview

    dgvSampleEx.AutoGenerateColumns = False

    dgvSampleEx.DataSource = dtNames



The code listing for the dynamic ComboBox rendering

  Private Sub dgvSampleEx_CellValueChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvSampleEx.CellValueChanged

    If e.RowIndex < 0 Then

      Exit Sub

    End If

 

    If e.ColumnIndex = 1 Then

      Dim Department As String = CStr(dgvSampleEx.Rows(e.RowIndex).Cells(1).Value)

      Dim dv As DataView = New DataView(dtChoices, "Department = '" & Department & "'", "", DataViewRowState.CurrentRows)

 

      Dim dgvComboCell As DataGridViewComboBoxCell = CType(dgvSampleEx.Rows(e.RowIndex).Cells(2), DataGridViewComboBoxCell)

      With dgvComboCell

        .DisplayMember = "Position"

        .ValueMember = "Position"

        .DataSource = dv

      End With

    End If

  End Sub


Guess the trick is to know that binding a DataGridView column sets all dropdown elements to be identical whereas binding the DataGridViewCell gives you more control over the exact elements that need to be displayed in the dropdown choices.

And the results for all to see


Note how the dropdown values differ - depending on the Department that has been entered. Ideally, the Department could also be rendered as a combobox. I decided to go with plain text to keep this example simple.

ComboBox in a DataGridView

It is often useful to add a dropdown box in a DataGridView column. I will list a couple of methods that can be used to achieve this. Of course, your real world applications will differ in certain areas related to the source of data. For the sake of this example, I have created these datasources at runtime.
The first example deals with displaying a two column grid with the first column containing the Employee Name and the second column that can be used to collect the Employee Gender
  1. Drag a DataGridView on the form

  2. Expand the DataGridView tasks and add a couple of columns


  3. Set the DataPropertyName for the first column appropriately, Since my data source deals with Employee Names, I have selected 'Name' which corresponds to the column name in my datasource.


  4. Finally, bind the data source with the grid and another datasource with the second column so that the dropdown choices are rendered correctly.

        'Create the sample list of names

        Dim dtNames As DataTable = New DataTable

        dtNames.Columns.Add("Name", GetType(String))

     

        Dim dr As DataRow

        dr = dtNames.NewRow : dr.Item("Name") = "Alvin Menezes" : dtNames.Rows.Add(dr)

        dr = dtNames.NewRow : dr.Item("Name") = "Saikumar Chettiar" : dtNames.Rows.Add(dr)

        dr = dtNames.NewRow : dr.Item("Name") = "Sample Name 1" : dtNames.Rows.Add(dr)

        dr = dtNames.NewRow : dr.Item("Name") = "Sample Name 2" : dtNames.Rows.Add(dr)

     

        'Create the sample list of dropdown choices

        Dim dtGenders As DataTable = New DataTable

        dtGenders.Columns.Add("Gender", GetType(String))

        dtGenders.Columns.Add("GenderId", GetType(Integer))

     

        dr = dtGenders.NewRow : dr.Item("GenderId") = 1 : dr.Item("Gender") = "Male" : dtGenders.Rows.Add(dr)

        dr = dtGenders.NewRow : dr.Item("GenderId") = 2 : dr.Item("Gender") = "Female" : dtGenders.Rows.Add(dr)

     

        'Bind the datagridview

        dgvSample.AutoGenerateColumns = False

        dgvSample.DataSource = dtNames

     

        'Setup the Combobox column

        Dim dgvComboCol As DataGridViewComboBoxColumn = CType(dgvSample.Columns(1), DataGridViewComboBoxColumn)

        With dgvComboCol

          .DisplayMember = "Gender"

          .ValueMember = "GenderId"

          .DataSource = dtGenders

        End With



And that's it, we are good to go

Friday, December 14, 2007

Multi Colored ComboboxListItems

Requirement

Consider this, a list of Students and their overall performance. For the sake of this example, I will simply rate the performance at two levels
  • 80% and above
  • below 80%
To easily discern between students at these performance levels, we can display the student listitems in different colors using the Combobox control.
A little about the Combobox Item rendering
Listitems in a combobox are rendered in one of two ways - an unselected listitem and a selected listitem.

Since we would like to render the Student Listitems based on the performance factor at two levels, we will require a total of four color combinations -
  • Students whose overall performance meets or exceeds 80%


    Normal unselected  
    Selected

  • Students whose overall performance is below 80%


    Normal unselected  
    Selected

Lets dig in

Having defined the colors that we can use for rendering the listitems, here are the steps that are required
  1. Change the DrawMode property of the Combobox to OwnerDrawVariable. The default value is Normal.


  2. Handle the MeasureItem event of the Combobox. This allows for manipulating the size of each Listitem. In this current example, I have simply set a fixed height and width, however based on the value of e.Index and its corresponding Listitem element, you can change these values if you wish to render the Listitems differently.

    Private Sub cmbStudents_MeasureItem(ByVal sender As Object, ByVal e As System.Windows.Forms.MeasureItemEventArgs) _
     Handles cmbStudents.MeasureItem

      e.ItemHeight = 18 'Pick this based on desired height of the listitem
      e.ItemWidth = cmbStudents.Width
    End Sub


  3. Handle the DrawItem event of the Combobox. Like the event name suggests, this event allows you to perform a custom draw of the Listitem every time the Listitem is displayed. For our example, it is sufficient to know that this event is fired each time a user expands the Combobox to view a list of items as well runs his mouse over the expanded list resulting in a hover selection of that listitem.

    Private Sub cmbStudents_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) _
     Handles cmbStudents.DrawItem
      If e.Index < 0 Then
        Exit Sub
      End If

      'Get the Student object that needs to be rendered using the e.Index
      Dim oStudent As Student = CType(cmbStudents.Items(e.Index), Student)
      Dim DisplayStr As String = oStudent.Name

      Dim r As RectangleF = New RectangleF(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height)

      If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
        If oStudent.OverallPerformanceScore > 80 Then
          e.Graphics.FillRectangle(Brushes.Lavender, r)
          e.Graphics.DrawString(DisplayStr, e.Font, System.Drawing.Brushes.Blue, r)
        Else
          e.Graphics.FillRectangle(Brushes.AntiqueWhite, r)
          e.Graphics.DrawString(DisplayStr, e.Font, System.Drawing.Brushes.Red, r)
        End If
      Else
        If oStudent.OverallPerformanceScore > 80 Then
          e.Graphics.FillRectangle(Brushes.GhostWhite, r)
          e.Graphics.DrawString(DisplayStr, e.Font, System.Drawing.Brushes.Blue, r)
        Else
          e.Graphics.FillRectangle(Brushes.SeaShell, r)
          e.Graphics.DrawString(DisplayStr, e.Font, System.Drawing.Brushes.Red, r)
        End If
      End If

      ' Draw the focus rectangle if mouse hovers over the item.
      e.DrawFocusRectangle()
    End Sub


The Result




Student Class Listing


Private Class Student
  Private _Name As String
  Private _OverallPerformanceScore As Double

  Public Sub New(ByVal Name As String, ByVal OverallPerformanceScore As Double)
    _Name = Name
    _OverallPerformanceScore = OverallPerformanceScore
  End Sub

  Public ReadOnly Property Name() As String
    Get
      Return _Name
    End Get
  End Property

  Public ReadOnly Property OverallPerformanceScore() As Double
    Get
      Return _OverallPerformanceScore
    End Get
  End Property

  Public Overrides Function ToString() As String
    Return Name
  End Function
End Class