Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / Visual Basic
Article

Flat-MultiColumn Combobox with Autocomplete

,
Rate me:
Please Sign up or sign in to vote.
4.86/5 (87 votes)
8 Sep 20079 min read 1.1M   29.1K   191   403
MTGCCombobox: a .NET Combobox that is flat, multicolumn and with Autocomplete feature
Image 1

Contents

Introduction

Once I had to develop a program using VBA with Access, and I had the great pleasure to use the combobox supplied with it: awesome! Multicolumn, Databound, Autocompleting and really fast. Since VB5 and VB6, I was searching for something similar I could use in my applications, but no luck! Nor has the situation changed with VS.NET, and the combobox control remains the same with its limitations and bugs. After some experience in building controls, like new textboxes and folderbrowser, it's time for our multicolumn combobox (written in VB.NET).

Features

Here is a brief description of the main features of our control:

  • Multiple Column (for now, max 4) with configurable widths
  • Flat look similar to the XP combobox
  • Highlight combobox border and arrow on MouseEnter and GotFocus events
  • Autocomplete text written in the text area, based on first column values
  • Overridden DropDownStyle property to manage Autocomplete feature with DropDownList style
  • Loading the combobox via MTGCComboboxItem or through a DataTable
  • Custom colors for border (highlighted and not), arrow, dropdownlist grid, etc.
  • Custom designer used to disable/enable properties at design time, managing verbs etc.

Public Properties

NameDescription
DropDownListThis property is just present in the "parent" combobox, but in this one is overridden and now has 2 possible values:
  • DropDown (default one)
  • DropDownList
ManagingFastMouseMovingIf true, a timer is used to manage the combobox repaint in case of fast mouse moving inside and outside the combo
ManagingFastMouseMovingIntervalTimer interval (in ms) used when ManagingFastMouseMoving is set to true
NormalBorderColorThis is the Border Color of the Combo when not Highlighted
ArrowColor (NEW)Color of the Arrow in the Control Box
DisabledArrowColor (NEW)Color of the Arrow in the Control Box when Enabled=False
ArrowBoxColor (NEW)Background Color of the Arrow Control Box
DisabledArrowBoxColor (NEW)Background Color of the Arrow Control Box when Enabled=False
DisabledBorderColor (NEW)Border Color of the Combo when Enabled=False
DropDownForeColorText Color of the item selected in the DropDownList
DropDownBackColorBackground Color of the item selected in the DropDownList
DropDownArrowBackColorBackground Color of the Arrow box when DropDownList is open
ColumnNumNumber of Columns shown in the DropDownList (max 4)
ColumnWidthSize of columns in pixel, split by ;
GridLineVerticalIf true, there will be a vertical line dividing every column in the DropDownList
GridLineHorizontalIf true, there will be a horizontal line dividing every rows in the DropDownList
CharacterCasingUse this if you want normal, lower or upper casing text (just like in TextBoxes)
BorderStyleSet this to FlatXP if you want the Flat look, or to Fixed3D for the usual look
LoadingTypeThere are two ways to load our combobox
  • ComboboxItem: this is a custom item derived from ListViewItem (see the Code part below to know more about it)
  • DataTable: you can pass a DataTable to the combobox and it will get the first ColumnNum columns (or the ones specified with the SourceDataString property) and show them
HighLightBorderColorColor of the combobox border when highlighted (when the mouse is over or it has the focus)
HighLightBorderOnMouseEventsAre you tired of the highlighting feature? Set this property to false and you will disable it
SourceDataTableDataTable used as source when the LoadingType property is set to DataTable
SourceDataStringString Array used to specify which columns of the DataTable have to be shown (and in which order)
SelectedItem (NEW)This is the Selected Item in the combobox
SelectedValue (NEW)This is the Selected Value in the combobox

Using the code

Flat Border

To reach the Flat Border look, we have to repaint the whole border and the arrow box, so we manage the WndProc event:

VB.NET
Protected Overrides Sub WndProc(ByRef m As Message)
 MyBase.WndProc(m)

 If Me.BorderStyle = TipiBordi.FlatXP Then
  Select Case m.Msg
   Case &HF, &H133
    'WM_PAINT

    'We have to find if the Mouse is Over the combo
    Dim mouseIsOver As Boolean
    Dim mousePosition As Point = Control.MousePosition
    mousePosition = PointToClient(mousePosition)
    mouseIsOver = ClientRectangle.Contains(mousePosition)

    If Me.HighlightBorderOnMouseEvents AndAlso (
     mouseIsOver OrElse Me.Focused) Then
     Dim g As Graphics = Graphics.FromHwnd(Me.Handle)

     DrawBorder(g, Me.HighlightBorderColor)
     DrawHighlightedArrow(g, False)
    Else
     Dim g As Graphics = Graphics.FromHwnd(Me.Handle)

     DrawBorder(g, m_NormalBorderColor)
     DrawNormalArrow(g, True)
    End If

   Case &H2A3
    'WM_MOUSELEAVE
    If Me.Focused Then Exit Sub
    If currentColor.Equals(m_HighlightBorderColor) Then
     Dim mouseIsOver As Boolean
     Dim mousePosition As Point = Control.MousePosition
     mousePosition = PointToClient(mousePosition)
     mouseIsOver = ClientRectangle.Contains(mousePosition)

     If Not mouseIsOver Then
      Dim g As Graphics = Graphics.FromHwnd(Me.Handle)
      DrawBorder(g, m_NormalBorderColor)
      DrawNormalArrow(g, True)
      g.Dispose()
     End If
    End If
   Case &H200
    'WM_MOUSEMOVE
    If Me.HighlightBorderOnMouseEvents = True AndAlso Not Highlighted Then
     currentColor = Me.HighlightBorderColor
     Dim g As Graphics = Graphics.FromHwnd(Me.Handle)
     DrawBorder(g, currentColor)
     DrawHighlightedArrow(g, False)
     g.Dispose()
    End If
   Case &H46
    'WM_WINDOWPOSCHANGING
    If Me.BorderStyle = TipiBordi.FlatXP Then
     'Repaint the arrow when pressed
     If Me.HighlightBorderOnMouseEvents Then
      Dim g As Graphics = Graphics.FromHwnd(Me.Handle)
      Dim pressedColorBrush As Brush = New SolidBrush(m_DropDownBackColor)
      Dim Larghezza As Integer =
      SystemInformation.VerticalScrollBarWidth - arrowWidth
      g.FillRectangle(pressedColorBrush, New Rectangle((Left - Larghezza),
      Top - 1, SystemInformation.VerticalScrollBarWidth + 1, Height + 2))
      Dim p As Pen = New Pen(HighlightBorderColor)
      g.DrawRectangle(p, (Left - Larghezza) - 1, Top - 2,
      SystemInformation.VerticalScrollBarWidth + 2, Height + 4)
      DrawArrow(g, False)
      g.Dispose()
      Me.Invalidate()
     End If
    End If
   Case Else
    Exit Select

  End Select
 End If
End Sub

First thing to notice, this part of code is used only if the BorderStyle property is set to FlatXp.

The HighLight border is drawn when 3 conditions are true:

  • The HighlightBorderOnMouseEvents property is set to true AND
  • The mouse is over the control OR
  • The control has the focus

We draw the combobox in two steps: first the border (DrawBorder) and then the arrowbox (DrawNormalArrow and DrawHighlightedArrow). Here is the code of these procedures:

VB.NET
   'Calculate the location of the Arrow Box
Private Sub ArrowBoxPosition(ByRef left As Integer,
ByRef top As Integer, ByRef width As Integer, ByRef height As Integer)
 Dim rc As Rectangle = ClientRectangle
 width = arrowWidth
 left = rc.Right - width - 2
 top = rc.Top + 2
 height = rc.Height - 4
End Sub

'Draw the Flat Arrow Box when not highlighted
Private Sub DrawNormalArrow(ByRef g As Graphics, ByVal disable As Boolean)
 If Me.BorderStyle = TipiBordi.FlatXP Then
  Dim left, top, arrowWidth, height As Integer
  ArrowBoxPosition(left, top, arrowWidth, height)

  Dim stripeColorBrush As Brush = New SolidBrush(SystemColors.Control)
  Dim Larghezza As Integer = SystemInformation.VerticalScrollBarWidth _
                                                               - arrowWidth
  If (Me.Enabled) Then
   Dim b As Brush = New SolidBrush(SystemColors.Control)
   g.FillRectangle(b, New Rectangle(left - Larghezza, top - 2,
    SystemInformation.VerticalScrollBarWidth, height + 4))
  End If

  If Me.Enabled Then
   Dim p As Pen = New Pen(m_NormalBorderColor)
   g.DrawLine(p, New Point(ClientRectangle.Right -
   SystemInformation.VerticalScrollBarWidth - 2, ClientRectangle.Top),
   New Point(ClientRectangle.Right, ClientRectangle.Top))
   g.DrawLine(p, New Point(ClientRectangle.Right -
   SystemInformation.VerticalScrollBarWidth - 2, ClientRectangle.Bottom - 1),
    New Point(ClientRectangle.Right, ClientRectangle.Bottom - 1))

   If Not disable Then
    DrawHighlightedArrow(g, True)
    g.FillRectangle(stripeColorBrush, left, top - 1,
    arrowWidth + 1, height + 2)
   Else
    g.FillRectangle(stripeColorBrush, left - 5, top - 1,
    arrowWidth + 6, height + 2)
   End If

   DrawArrow(g, False)
  Else
   Dim p As Pen = New Pen(SystemColors.InactiveBorder)
   g.DrawLine(p, New Point(ClientRectangle.Right -
   SystemInformation.VerticalScrollBarWidth - 2, ClientRectangle.Top),
   New Point(ClientRectangle.Right, ClientRectangle.Top))
   g.DrawLine(p, New Point(ClientRectangle.Right -
   SystemInformation.VerticalScrollBarWidth - 2, ClientRectangle.Bottom - 1),
   New Point(ClientRectangle.Right, ClientRectangle.Bottom - 1))

   ' Now draw the unselected background
   g.FillRectangle(stripeColorBrush, left - 5, top - 1,
   arrowWidth + 6, height + 2)

   DrawArrow(g, True)
  End If
  Highlighted = False
 End If
End Sub

'Draw the Flat Arrow Box when highlighted
Private Sub DrawHighlightedArrow(ByRef g As Graphics, ByVal Delete As Boolean)
 If Me.BorderStyle = TipiBordi.FlatXP Then
  Dim left, top, arrowWidth, height As Integer
  ArrowBoxPosition(left, top, arrowWidth, height)

  If (Me.Enabled) Then
   Dim comboTextWidth As Integer =
    SystemInformation.VerticalScrollBarWidth - arrowWidth
   If (comboTextWidth < 0) Then comboTextWidth = 1
   Dim b As Brush = New SolidBrush(HighlightBorderColor)
  End If

  If Not Delete Then
   If (DroppedDown) Then
    Dim cbg As Graphics = CreateGraphics()
    Dim pressedColorBrush As Brush =
    New SolidBrush(m_DropDownArrowBackColor)
    Dim Larghezza As Integer =
    SystemInformation.VerticalScrollBarWidth - arrowWidth
    cbg.FillRectangle(pressedColorBrush, New Rectangle(
    (left - Larghezza), top - 1, SystemInformation.VerticalScrollBarWidth + 1,
     height + 2))
    Dim p As Pen = New Pen(HighlightBorderColor)
    cbg.DrawRectangle(p, (left - Larghezza) - 1, top - 2,
    SystemInformation.VerticalScrollBarWidth + 2, height + 4)
    DrawArrow(cbg, False)
    cbg.Dispose()
    Exit Sub
   Else
    If Enabled Then
     Dim b As Brush = New SolidBrush(m_DropDownBackColor)
     Dim Larghezza As Integer =
     SystemInformation.VerticalScrollBarWidth - arrowWidth
     g.FillRectangle(b, New Rectangle((left - Larghezza), top - 1,
     SystemInformation.VerticalScrollBarWidth + 1, height + 2))

     Dim pencolor As Color = customBorderColor
     If (pencolor.Equals(Color.Empty)) Then
      pencolor = BackColor
     End If
    End If
   End If
  Else
   Dim b As Brush = New SolidBrush(BackColor)
   g.FillRectangle(b, left - 1, top - 1, arrowWidth + 2, height + 2)
  End If
  If Me.Enabled Then DrawArrow(g, False)
  Highlighted = True
 End If
End Sub

Private Sub DrawArrow(ByVal g As Graphics, ByVal Disable As Boolean)
 If Me.BorderStyle = TipiBordi.FlatXP Then
  Dim left, top, arrowWidth, height As Integer
  ArrowBoxPosition(left, top, arrowWidth, height)

  Dim extra As Integer = 1
  If (bUsingLargeFont) Then extra = 2

  'triangle vertex of the arrow
  Dim pts(2) As Point
  pts(0) = New Point(left + arrowWidth / 2 - 2 - extra - 2,
  top + height / 2 - 1)
  pts(1) = New Point(left + arrowWidth / 2 + 3 + extra - 1,
  top + height / 2 - 1)
  pts(2) = New Point(left + arrowWidth / 2 - 1,
  (top + height / 2 - 1) + 3 + extra)

  'draw the arrow as a polygon
  If (Disable) Then
   Dim b As Brush = New SolidBrush(arrowDisableColor)
   g.FillPolygon(b, pts)
  Else
   Dim b As Brush = New SolidBrush(arrowColor)
   g.FillPolygon(b, pts)
  End If
 End If
End Sub

Private Sub DrawBorder(ByVal g As Graphics, ByVal DrawColor As Color)
 If Me.BorderStyle = TipiBordi.FlatXP Then
  g.DrawRectangle(New pen(Me.BackColor, 1), ClientRectangle.Left + 1,
  ClientRectangle.Top + 1, ClientRectangle.Width - 1,
  ClientRectangle.Height - 3)

  'Draw the Border
  If Me.Enabled = False Then 'combo disabilitato
   DrawColor = SystemColors.InactiveBorder
  End If

  Dim pen As pen = New pen(DrawColor, 1)
  'Border Rectangle
  g.DrawRectangle(pen, ClientRectangle.Left, ClientRectangle.Top,
  ClientRectangle.Width - 1, ClientRectangle.Height - 1)
  'Button Rectangle
  g.DrawRectangle(pen, ClientRectangle.Left, ClientRectangle.Top,
  ClientRectangle.Width - SystemInformation.VerticalScrollBarWidth - 3,
   ClientRectangle.Height - 1)
 End If
End Sub

I think there is nothing tricky about this code, just some GDI+ work and a lot of time to reach the desired result!

Autocomplete feature (UPDATED)

This feature is based on the original "AutoComplete ComboBox in VB.NET" code by Daryl, published here on The Code Project. Thank's to Daryl!

I think the Autocomplete feature is a useful one, especially when you have a combobox filled with a lot of items (in my case, all Italian cities, it means more than 10000 items!) and you want to help the user when he is typing in it. To manage the Autocomplete, we have to catch all the events regarding keys, so OnKeyPress, OnKeyDown and OnKeyUp.

VB.NET
  Protected Overrides Sub OnKeyPress_
       (ByVal e As System.Windows.Forms.KeyPressEventArgs)
     'AUTOCOMPLETE: we have to know when a key has been really pressed

     If Me.DropDownStyle = CustomDropDownStyle.DropDown Then
         PressedKey = True
         If Asc(e.KeyChar) = 8 Then
             If Me.SelectedText = Me.Text Then
                 Me.SelectedIndex = -1
             End If
             If Asc(e.KeyChar) = 13 Then e.Handled = True _
       'This is used to suppress the "Beep" when Enter key is pressed
         End If
     Else
         'ReadOnly AutoComplete Management
         Dim sTypedText As String
         Dim iFoundIndex As Integer
         Dim currentText As String
         Dim Start, selLength As Integer

         If Asc(e.KeyChar) = 8 Then
             If Me.SelectedText = Me.Text Then
                 PressedKey = True
                 Me.SelectedIndex = -1
                 Exit Sub
             End If
         End If
         If Me.SelectionLength > 0 Then
             Start = Me.SelectionStart
             selLength = Me.SelectionLength

             'This is equivalent to Me.Text, but sometimes using Me.Text
             'it doesn't work
             currentText = Me.AccessibilityObject.Value
             Dim posizione As Long
             Dim testingString As String
             posizione = InStr(Me.Text, "&")
             If posizione > 0 Then
                 'The "&" character is contained in Me.Text
                 testingString = Microsoft.VisualBasic.Left(Me.Text, _
               posizione - 1) & Microsoft.VisualBasic.Right_
               (Me.Text, Len(Me.Text) - posizione)
             Else
                 testingString = Me.Text
             End If
             If UCase(testingString) = UCase(Me.AccessibilityObject.Value) _
           Then
                 currentText = Me.Text
             End If

             currentText = currentText.Remove(Start, selLength)
             currentText = currentText.Insert(Start, e.KeyChar)
             sTypedText = currentText
         Else
             Start = Me.SelectionStart
             sTypedText = Me.Text.Insert(Start, e.KeyChar)
         End If
         iFoundIndex = Me.FindString(sTypedText)
         If (iFoundIndex >= 0) Then
             PressedKey = True
         Else
             e.Handled = True
         End If
     End If

     MyBase.OnKeyPress(e)
 End Sub

Protected Overrides Sub OnKeyDown(ByVal e As _
               System.Windows.Forms.KeyEventArgs)
   Debug.WriteLine("OnKeyDown " & Me.Text)
   If Me.DropDownStyle = CustomDropDownStyle.DropDownList _
               AndAlso e.KeyCode = Keys.Delete Then
       If Me.Text <> Me.SelectedText Then
           e.Handled = True
       Else
           Me.SelectedIndex = -1
       End If
   End If
   If Me.DropDownStyle = CustomDropDownStyle.DropDown _
               AndAlso e.KeyCode = Keys.Delete Then
       If Me.Text = Me.SelectedText Then
           Me.SelectedIndex = -1
       End If
   End If

   MyBase.OnKeyDown(e)
End Sub

Protected Overrides Sub OnKeyUp(ByVal e As System.Windows.Forms.KeyEventArgs)
   'AUTOCOMPLETING

   'WARNING: With VB.NET 2003 there is a strange behaviour.
   'This event is raised not just when any key is pressed
   'but also when the Me.Text property changes. Particularly,
   'it happens when you write in a fast way (for example
   'you press 2 keys and the event is raised 3 times).
   'To manage this we have added a boolean variable PressedKey that
   'is set to true in the OnKeyPress Event

   Dim sTypedText As String
   Dim iFoundIndex As Integer
   Dim oFoundItem As Object
   Dim sFoundText As String
   Dim sAppendText As String

   Debug.WriteLine("OnKeyUp " & Me.Text)
   If PressedKey Then
       'Ignoring alphanumeric chars
       Select Case e.KeyCode
           Case Keys.Left, Keys.Right, Keys.Up, Keys.Delete, _
               Keys.Down, Keys.End, Keys.Home
               Return
       End Select

       'Get the Typed Text and Find it in the list
       sTypedText = Me.Text
       If e.KeyCode <> Keys.Back Then
           iFoundIndex = Me.FindString(sTypedText)
       Else
           iFoundIndex = Me.FindStringExact(sTypedText)
       End If

       'If we found the Typed Text in the list then Autocomplete
       If iFoundIndex >= 0 AndAlso Me.Text <> "" Then

           'Get the Item from the list (Return Type depends if
           'Datasource was bound or List Created)
           oFoundItem = Me.Items(iFoundIndex)

           'Use the ListControl.GetItemText to resolve the Name
           'in case the Combo was Data bound
           sFoundText = Me.GetItemText(oFoundItem)

           'Append then found text to the typed text to preserve case
           sAppendText = sFoundText.Substring(sTypedText.Length)
           Me.Text = sTypedText & sAppendText

           'Select the Appended Text
           Me.SelectionStart = sTypedText.Length
           Me.SelectionLength = sAppendText.Length

           If e.KeyCode = Keys.Enter Then
               iFoundIndex = Me.FindStringExact(Me.Text)
               Me.SelectedIndex = iFoundIndex
               SendKeys.Send(vbTab)
               e.Handled = True
           End If
       Else
           'Forcing SelectedItem to Nothing if we can't Autocomplete
           Me.SelectedIndex = -1
           Me.SelectedItem = Nothing
       End If

   End If
   PressedKey = False
End Sub

Protected Overrides Sub OnLeave(ByVal e As System.EventArgs)
    'Selecting the item whose text is shown in the text area of the ComboBox
    Dim iFoundIndex As Integer
    'The Me.AccessibilityObject.Value is used instead of Me.Text to manage
    'the event when you write in the combobox text and the DropDownList
    'is open. In this case, if you click outside the combo, Me.Text maintains
    'the old value and not the current one
    Dim currentText As String
    currentText = Me.AccessibilityObject.Value
    Dim testingString As String
    Dim posizione As Long
    posizione = InStr(Me.Text, "&")
    If posizione > 0 Then
        'The "&" character is contained in Me.Text
        testingString = Microsoft.VisualBasic.Left(Me.Text, posizione - 1) _
       & Microsoft.VisualBasic.Right(Me.Text, Len(Me.Text) - posizione)
    Else
        testingString = Me.Text
    End If
    If UCase(testingString) = UCase(Me.AccessibilityObject.Value) Then
        currentText = Me.Text
    End If

    iFoundIndex = Me.FindStringExact(currentText)
    Me.SelectedIndex = iFoundIndex
    If iFoundIndex = -1 Then
        Me.SelectedItem = Nothing
    End If
    MyBase.OnLeave(e)
End Sub

The OnKeyPress event is used only when the DropDownlist property is set to DropDownlist. In this case, the user can't type anything that is not present in the first column of the combobox: a sort of ReadOnly property. The whole Autocomplete process is based on FindString and FindStringExact methods, which return the first item in the combobox (actually the first column of the combo in our case) matching the specified string. When such an item is found, the Text value is completed with the remaining part of the string.

Multicolumn items

To have more columns in the DropDownList is reached using a custom item to load data (MTGCComboBoxItem) and then overriding the DrawItem event to show all items in the correct way.

VB.NET
Public Class MTGCComboBoxItem
 'Since all we need is a "Text" property for this example we can
 'Subclass by inheriting any object desired.
 'For this example, we'll use the ListViewItem
 Inherits ListViewItem
 Implements IComparable

 'each of the below public declarations will be "visible" to the outside
 'You may add as many of these declarations using whatever types you desire
 Public Col1 As String
 Public Col2 As String
 Public Col3 As String
 Public Col4 As String

 'every value of MyInfo you want to store, get's added to the NEW declaration
 Sub New(ByVal C1 As String, Optional ByVal C2 As String = "",
  Optional ByVal C3 As String = "", Optional ByVal C4 As String = "")
  MyBase.New()
  'transfer all incoming parameters to your local storage
  Col1 = C1
  Col2 = C2
  Col3 = C3
  Col4 = C4
  'and finally, pass back the Text property
  Me.Text = C1
 End Sub

 'Function used to sort the items on first element Col1
 Private Function CompareTo(ByVal obj As Object)
 As Integer Implements IComparable.CompareTo
  'every not nothing object is greater than nothing
  If obj Is Nothing Then Return 1

  'this is used to take care of late binding
  Dim other As MTGCComboBoxItem = CType(obj, MTGCComboBoxItem)

  'comparing strings
  Return StrComp(Col1, other.Col1, CompareMethod.Text)
 End Function

End Class

Every MTGCComboBoxItem has four sub-elements, Col1 Col2 Col3 and Col4, each one representing one of the possible columns of that row in the combobox. The function CompareTo is used to sort the items based on the value of first Column Col1.

In this way the user can load data in multiple columns, but they have to be shown when the arrow is clicked and the DropDownList appears. So the DrawItem event had to be rewritten. Here, I show just a part of it, because it's a very long procedure (refer to full source code for the full procedure):

VB.NET
'......
 'item selected
 e.Graphics.FillRectangle(New SolidBrush(DropDownBackColor), r)
 Select Case Me.ColumnNum
  Case 1
   If wcol1 > 0 Then
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(e.Index)
     (Indice(0))).ToString, Me.Font, New SolidBrush(DropDownForeColor),
     rd.X, rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col1.ToString,
     Me.Font, New SolidBrush(DropDownForeColor), rd.X, rd.Y, sf)
    End If
    If Me.m_GridLineHorizontal Then
     e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X, rd.Y +
     rd.Height - 1, rd.X + Me.DropDownWidth, rd.Y + rd.Height - 1)
    End If
   End If
  Case 2
   If wcol1 > 0 Then
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(
     e.Index)(Indice(0))).ToString, Me.Font, New SolidBrush(
     DropDownForeColor), rd.X, rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col1.ToString,
      Me.Font, New SolidBrush(DropDownForeColor), rd.X, rd.Y, sf)
    End If
   End If
   If wcol2 > 0 Then
    If Me.m_GridLineVertical Then
     e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X +
     CInt(wcol1) - 2, rd.Y, rd.X + CInt(wcol1) - 2, rd.Y + 15)
    End If
    e.Graphics.FillRectangle(New SolidBrush(DropDownBackColor),
    rd.X + CInt(wcol1) - 1, rd.Y, r.Width - CInt(wcol1) + 1, r.Height)
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(e.Index)
     (Indice(1))).ToString, Me.Font, New SolidBrush(DropDownForeColor),
     rd.X + CInt(wcol1), rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col2.ToString,
     Me.Font, New SolidBrush(DropDownForeColor), rd.X + CInt(wcol1), rd.Y, sf)
    End If
   End If
   If Me.m_GridLineHorizontal Then
    e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X, rd.Y +
    rd.Height - 1, rd.X + Me.DropDownWidth, rd.Y + rd.Height - 1)
   End If
  Case 3
   If wcol1 > 0 Then
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(e.Index)
     (Indice(0))).ToString, Me.Font, New SolidBrush(DropDownForeColor),
      rd.X, rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col1.ToString,
     Me.Font, New SolidBrush(DropDownForeColor), rd.X, rd.Y, sf)
    End If
   End If
   If wcol2 > 0 Then
    If Me.m_GridLineVertical Then
     e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X + CInt(wcol1)
     - 2, rd.Y, rd.X + CInt(wcol1) - 2, rd.Y + 15)
    End If
    e.Graphics.FillRectangle(New SolidBrush(DropDownBackColor), rd.X +
     CInt(wcol1) - 1, rd.Y, r.Width - CInt(wcol1) + 1, r.Height)
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(e.Index)
     (Indice(1))).ToString, Me.Font, New SolidBrush(DropDownForeColor),
     rd.X + CInt(wcol1), rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col2.ToString,
      Me.Font, New SolidBrush(DropDownForeColor), rd.X + CInt(wcol1), rd.Y, sf)
    End If
   End If
   If wcol3 > 0 Then
    If Me.m_GridLineVertical Then
     e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X + CInt(wcol1)
     + CInt(wcol2) - 2, rd.Y, rd.X + CInt(wcol1) + CInt(wcol2) - 2,
      rd.Y + 15)
    End If
    e.Graphics.FillRectangle(New SolidBrush(DropDownBackColor), rd.X
     + CInt(wcol1) + CInt(wcol2) - 1, rd.Y, r.Width - CInt(wcol1) -
      CInt(wcol2) + 1, r.Height)
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(e.Index)
     (Indice(2))).ToString, Me.Font, New SolidBrush(DropDownForeColor),
     rd.X + CInt(wcol1) + CInt(wcol2), rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col3.ToString, Me.Font,
     New SolidBrush(DropDownForeColor), rd.X + CInt(wcol1)
     + CInt(wcol2), rd.Y, sf)
    End If
   End If
   If Me.m_GridLineHorizontal Then
    e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X, rd.Y
    + rd.Height - 1, rd.X + Me.DropDownWidth, rd.Y + rd.Height - 1)
   End If
  Case 4
   If wcol1 > 0 Then
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(
     e.Index)(Indice(0))).ToString, Me.Font, New SolidBrush(
     DropDownForeColor), rd.X, rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col1.ToString,
      Me.Font, New SolidBrush(DropDownForeColor), rd.X, rd.Y, sf)
    End If
   End If
   If wcol2 > 0 Then
    If Me.m_GridLineVertical Then
     e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X +
     CInt(wcol1) - 2, rd.Y, rd.X + CInt(wcol1) - 2, rd.Y + 15)
    End If
    e.Graphics.FillRectangle(New SolidBrush(DropDownBackColor),
     rd.X + CInt(wcol1) - 1, rd.Y, r.Width - CInt(wcol1) +
     1, r.Height)
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(e.Index)(Indice(1)
     )).ToString, Me.Font, New SolidBrush(DropDownForeColor), rd.X +
     CInt(wcol1), rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col2.ToString,
     Me.Font, New SolidBrush(DropDownForeColor), rd.X + CInt(wcol1), rd.Y, sf)
    End If
   End If
   If wcol3 > 0 Then
    If Me.m_GridLineVertical Then
     e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X + CInt(wcol1) +
      CInt(wcol2) - 2, rd.Y, rd.X + CInt(wcol1) + CInt(wcol2) - 2,
       rd.Y + 15)
    End If
    e.Graphics.FillRectangle(New SolidBrush(DropDownBackColor),
     rd.X + CInt(wcol1) + CInt(wcol2) - 1, rd.Y, r.Width - CInt(wcol1)
     - CInt(wcol2) + 1, r.Height)
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(e.Index)(
     Indice(2))).ToString, Me.Font, New SolidBrush(DropDownForeColor),
     rd.X + CInt(wcol1) + CInt(wcol2), rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col3.ToString, Me.Font,
     New SolidBrush(DropDownForeColor), rd.X + CInt(wcol1)
     + CInt(wcol2), rd.Y, sf)
    End If
   End If
   If wcol4 > 0 Then
    If Me.m_GridLineVertical Then
     e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X + CInt(wcol1)
     + CInt(wcol2) + CInt(wcol3) - 2, rd.Y, rd.X + CInt(wcol1) +
     CInt(wcol2) + CInt(wcol3) - 2, rd.Y + 15)
    End If
    e.Graphics.FillRectangle(New SolidBrush(DropDownBackColor), rd.X +
     CInt(wcol1) + CInt(wcol2) + CInt(wcol3) - 1, rd.Y, r.Width -
     CInt(wcol1) - CInt(wcol2) - CInt(wcol3) + 1, r.Height)
    If Me.LoadingType = CaricamentoCombo.DataTable Then
     e.Graphics.DrawString(Assegna(m_DataTable.Rows(e.Index)(
     Indice(3))).ToString, Me.Font, New SolidBrush(DropDownForeColor),
     rd.X + CInt(wcol1) + CInt(wcol2) + CInt(wcol3), rd.Y, sf)
    ElseIf Me.LoadingType = CaricamentoCombo.ComboBoxItem Then
     e.Graphics.DrawString(Me.Items.Item(e.Index).Col4.ToString,
     Me.Font, New SolidBrush(DropDownForeColor), rd.X + CInt(wcol1)
      + CInt(wcol2) + CInt(wcol3), rd.Y, sf)
    End If
   End If
   If Me.m_GridLineHorizontal Then
    e.Graphics.DrawLine(New Pen(GridLineColor, 1), rd.X, rd.Y +
    rd.Height - 1, rd.X + Me.DropDownWidth, rd.Y + rd.Height - 1)
   End If
 End Select
 If Me.BorderStyle = TipiBordi.FlatXP Then
  'Use the border color to highlight the selected item
  If Me.GridLineHorizontal Then
   e.Graphics.DrawRectangle(New Pen(Me.HighlightBorderColor, 1),
    r.X, r.Y, r.Width - 1, r.Height - 2)
  Else
   e.Graphics.DrawRectangle(New Pen(Me.HighlightBorderColor, 1),
   r.X, r.Y, r.Width - 1, r.Height - 1)
  End If
 End If
 e.DrawFocusRectangle()
 '.......
 'Here is the code for the unselected items
 '.......

This piece of code represents the drawing of the selected item. Depending on how many columns have to be shown (the ColumnNum property), each of the four possible values is drawn through the e.Graphics.DrawString method with ascending x position, but only if the corresponding column width is greater than 0. Data is taken from the DataTable or a MTGCComboboxItem array depending on the LoadingType property. The horizontal and vertical gridlines for each element are drawn as well.

Using the control

Ok, I guess you want to know how to use this control! Add it to your VS ToolBox referencing the MTGCComboBox.dll file, then drag and drop the control to your form. First thing to notice: the text control is empty! How many times we had to manually empty it, especially with TextBoxes? Only with VS 2005 this dream will come true....

Loading Items

Let's go on! Now you can set its properties in the way you want in design mode, and after that write the code to load the combo. For example, suppose you want to load the combo with information about our five continents: name, extension and population. You will have to do something like this:

VB.NET
comboContinent.BorderStyle = MTGCComboBox.TipiBordi.FlatXP
comboContinent.LoadingType = MTGCComboBox.CaricamentoCombo.ComboBoxItem
comboContinent.ColumnNum = 3
comboContinent.ColumnWidth = "80;120;100"

comboContinent.Items.Add(New MTGCComboBoxItem("Africa",
 "30,065,000 sq km", "807,419,000"))
comboContinent.Items.Add(New MTGCComboBoxItem("America",
"42,293,000 sq km", "830,722,000"))
comboContinent.Items.Add(New MTGCComboBoxItem("Asia",
 "44,579,000 sq km", "3,701,000,000"))
comboContinent.Items.Add(New MTGCComboBoxItem("Europe",
"9,938,000 sq km", "730,916,000 "))
comboContinent.Items.Add(New MTGCComboBoxItem("Oceania",
"8,112,000 sq km", "31,090,000"))

The ColumnNum is set to 3 and the widths are 80 pixels for the name, 120 for extension and 100 for population. Remember, if you don't want to show a column value just set is width to 0.

ATTENTION: the ColumnWidth property code procedure automatically adds 20 pixel to DropDownWith property value if the columns' width sum is greater then DropDownWith value, to take care of possibly vertical scrollbar shown in the DropDownList!

In this case, we add each MTGCComboBoxItem with the correct values. A faster way to load the items is creating an array of MTGCComboBoxItems and then using the Combobox.Items.AddRange method to load data. Here is the code:

VB.NET
Dim continentItems(4) As MTGCComboBoxItem

continentItems(0) = New MTGCComboBoxItem("Africa",
"30,065,000 sq km", "807,419,000")
continentItems(1) = New MTGCComboBoxItem("America",
 "42,293,000 sq km", "830,722,000")
continentItems(2) = New MTGCComboBoxItem("Asia",
"44,579,000 sq km", "3,701,000,000")
continentItems(3) = New MTGCComboBoxItem("Europe",
 "9,938,000 sq km", "730,916,000 ")
continentItems(4) = New MTGCComboBoxItem("Oceania",
"8,112,000 sq km", "31,090,000")

comboContinent.Items.AddRange(continentItems)

The other way to load the combo is through a dataTable. In this case, we can create a datatable with our information and then pass it as the SourceDataTable property:

VB.NET
Dim dtContinents As New DataTable("ContinentInfo")
dtContinents.Columns.Add("Name", System.Type.GetType("System.String"))
dtContinents.Columns.Add("Extension",
 System.Type.GetType("System.String"))
dtContinents.Columns.Add("Population",
System.Type.GetType("System.String"))

Dim dr As DataRow
dr = dtContinents.NewRow

dr("Name") = "Africa"
dr("Extension") = "30,065,000 sq km"
dr("Population") = "807,419,000"

dtContinents.Rows.Add(dr)

dr = dtContinents.NewRow

dr("Name") = "America"
dr("Extension") = "42,293,000 sq km"
dr("Population") = "830,722,000"

dtContinents.Rows.Add(dr)
dr = dtContinents.NewRow

dr("Name") = "Asia"
dr("Extension") = "44,579,000 sq km"
dr("Population") = "3,701,000,000"

dtContinents.Rows.Add(dr)
dr = dtContinents.NewRow

dr("Name") = "Europe"
dr("Extension") = "9,938,000 sq km"
dr("Population") = "730,916,000"

dtContinents.Rows.Add(dr)
dr = dtContinents.NewRow

dr("Name") = "Oceania"
dr("Extension") = "8,112,000 sq km"
dr("Population") = "31,090,000"

dtContinents.Rows.Add(dr)

comboContinent.LoadingType =
MTGCComboBox.CaricamentoCombo.DataTable
comboContinent.SourceDataString = New String(2)
 {"Name", "Extension", "Population"}
comboContinent.SourceDataTable = dtContinents

Obviously, in most cases you won't fill the DataTable manually (this is just an example), but you will load the datatable through a DataAdapter from a database and then pass it to the combobox. The SourceDataString property is not mandatory, but remember that you have to set it BEFORE setting the SourceDataTable or it won't have any effect and the first ColumnNum columns of the datatable will be shown.

Selecting items (NEW)

To select an item in the combobox you can use the ItemSelect method, that allows you to specify the value to search and the column on which this value will be searched. The method returns a Boolean value, True if the value has been found or False otherwise. The method has two Optional parameters: CaseSensitive is used to determine if the value to search is Case Sensitive or not (default False); RaiseSelectedIndexChanged is used to specify if the SelectedIndexChanged event must be raised after the selection of an item in the combobox through this method or not (default False).

Here is an example:

VB.NET
'Usually, when I Select an item during the Loading of a Form,
'I set the SelectedIndex property to -1
'If no value is found, but this is not mandatory

If Not mcbo.ItemSelect(2, "Value1", False, False) Then
    mcbo.SelectedIndex = -1
End If

Accessing items (NEW)

When an item is selected, you can access each column's value in this way:

VB.NET
Dim Name, Population, Extension as String

Name = comboContinent.SelectedItem.col1
Extension = comboContinent.SelectedItem.col2
Population= comboContinent.SelectedItem.col3

Points of Interest

Did you know that the classic VS combobox is really bugged? Well, after all my work I perfectly know it! Some of the bugs I've found out:

  • When you open the DropDownList of a combobox, and write a character in the text area and then you click outside the control, the dropdownlist is closed and the text area contains the first item of the combo starting with that character. But actually no item is selected (SelectedIndex = -1)
  • The MouseLeave Event is not always raised, especially if you move the mouse quite fast inside and outside the combo

Work in progress

Yeah, we want to improve this control (so we need your help to correct bugs and with advices about new features). Right now, our work is focused on:

  • Enable the DataBinding on the combo (right now the original DataBinding doesn't work)
  • Full repaint of the combo: what we do now is paint over the derived combobox, so there is a small blinking. To take care of it, we have to paint the whole combobox (including the text area and the text itself) and not call the parent painting anymore
  • Allow to use an unlimited number of columns

History

  • Version 1.0.0.0 - October 20, 2004
    • Initial Release
  • Version 1.3.0.0 - September 06, 2007
    • Minor bug fixes (Added some properties to manage colors, fixed some errors in OnKeyPress event, OnLeave event, OnKeyUp event, added the ItemSelect method)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) MT Global Consulting srl
Italy Italy
Claudio Di Flumeri, Project Engineer at MT Global Consulting srl.

Written By
Web Developer
Italy Italy
Massimiliano Silvestro, Software Engineer at MT Global Consulting srl.

Comments and Discussions

 
GeneralRe: Bug Please Help Me Out Fast Pin
Claudio Di Flumeri6-Feb-06 4:51
Claudio Di Flumeri6-Feb-06 4:51 
GeneralRe: Bug Please Help Me Out Fast Pin
Shishir Babel31-Jan-06 21:02
Shishir Babel31-Jan-06 21:02 
GeneralRe: Bug Please Help Me Out Fast Pin
ashimaz27-Feb-07 0:37
ashimaz27-Feb-07 0:37 
Generalselectedvalue changed Pin
pummyrao24-Jan-06 4:56
pummyrao24-Jan-06 4:56 
GeneralRe: selectedvalue changed Pin
Claudio Di Flumeri31-Jan-06 5:35
Claudio Di Flumeri31-Jan-06 5:35 
Questionhow can i add this in datagridview? Pin
drachx22-Jan-06 17:23
drachx22-Jan-06 17:23 
AnswerRe: how can i add this in datagridview? Pin
Claudio Di Flumeri31-Jan-06 6:02
Claudio Di Flumeri31-Jan-06 6:02 
GeneralThis Control Can Embed in Datagrid (Urgent) Pin
HemaRawat27-Dec-05 1:06
HemaRawat27-Dec-05 1:06 
GeneralOnLeave - accessibilityObject.value Pin
eatwork23-Dec-05 12:17
eatwork23-Dec-05 12:17 
GeneralRe: OnLeave - accessibilityObject.value Pin
Claudio Di Flumeri31-Jan-06 5:25
Claudio Di Flumeri31-Jan-06 5:25 
QuestionBug? Pin
Kotolom23-Dec-05 3:27
Kotolom23-Dec-05 3:27 
AnswerRe: Bug? Pin
Claudio Di Flumeri23-Dec-05 3:52
Claudio Di Flumeri23-Dec-05 3:52 
QuestionUnable to paste control on form Pin
sh21161822-Dec-05 23:23
sh21161822-Dec-05 23:23 
AnswerRe: Unable to paste control on form Pin
Kotolom23-Dec-05 3:36
Kotolom23-Dec-05 3:36 
AnswerRe: Unable to paste control on form Pin
Claudio Di Flumeri23-Dec-05 3:41
Claudio Di Flumeri23-Dec-05 3:41 
GeneralRe: Unable to paste control on form Pin
sh21161824-Dec-05 11:37
sh21161824-Dec-05 11:37 
GeneralRe: Unable to paste control on form Pin
sh21161811-Jan-06 3:47
sh21161811-Jan-06 3:47 
GeneralUnable to display multi columns Pin
Sufyan_shani20-Dec-05 21:38
Sufyan_shani20-Dec-05 21:38 
GeneralRe: Unable to display multi columns Pin
Claudio Di Flumeri21-Dec-05 6:20
Claudio Di Flumeri21-Dec-05 6:20 
QuestionWhy it doesn't supports Resizing? Pin
b_jaysingh7-Dec-05 4:36
b_jaysingh7-Dec-05 4:36 
QuestionString Collection items Pin
jeffrshaw6-Dec-05 1:12
jeffrshaw6-Dec-05 1:12 
GeneralString collection items for combo box Pin
jeffrshaw1-Dec-05 4:17
jeffrshaw1-Dec-05 4:17 
GeneralRe: String collection items for combo box Pin
solnt14-Jun-06 23:33
solnt14-Jun-06 23:33 
QuestionError creating window handle Pin
Carl Evans29-Nov-05 10:11
Carl Evans29-Nov-05 10:11 
QuestionSelecting corrent item? Pin
Avishesh8-Nov-05 16:36
Avishesh8-Nov-05 16:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.