Flat-MultiColumn Combobox with Autocomplete






4.86/5 (80 votes)
Oct 21, 2004
9 min read

1149657

29280
MTGCCombobox: a .NET Combobox that is flat, multicolumn and with Autocomplete feature

Contents
- Introduction
- Features
- Public Properties
- Using the code
- Using the control
- Points of Interest
- Work in Progress
- History
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 textbox
es 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 onMouseEnter
andGotFocus
events - Autocomplete text written in the text area, based on first column values
- Overridden
DropDownStyle
property to manage Autocomplete feature withDropDownList
style - Loading the
combobox
viaMTGCComboboxItem
or through aDataTable
- 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
Name | Description |
DropDownList |
This property is just present in the "parent" combobox , but in this one is overridden and now has 2 possible values:
|
ManagingFastMouseMoving |
If true , a timer is used to manage the combobox repaint in case of fast mouse moving inside and outside the combo |
ManagingFastMouseMovingInterval |
Timer interval (in ms) used when ManagingFastMouseMoving is set to true |
NormalBorderColor |
This 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 |
DropDownForeColor |
Text Color of the item selected in the DropDownList |
DropDownBackColor |
Background Color of the item selected in the DropDownList |
DropDownArrowBackColor |
Background Color of the Arrow box when DropDownList is open |
ColumnNum |
Number of Columns shown in the DropDownList (max 4) |
ColumnWidth |
Size of columns in pixel, split by ; |
GridLineVertical |
If true , there will be a vertical line dividing every column in the DropDownList |
GridLineHorizontal |
If true , there will be a horizontal line dividing every rows in the DropDownList |
CharacterCasing |
Use this if you want normal, lower or upper casing text (just like in TextBoxes ) |
BorderStyle |
Set this to FlatXP if you want the Flat look, or to Fixed3D for the usual look |
LoadingType |
There are two ways to load our combobox
|
HighLightBorderColor |
Color of the combobox border when highlighted (when the mouse is over or it has the focus) |
HighLightBorderOnMouseEvents |
Are you tired of the highlighting feature? Set this property to false and you will disable it |
SourceDataTable |
DataTable used as source when the LoadingType property is set to DataTable |
SourceDataString |
String 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:
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 totrue
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:
'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
.
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.
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):
'......
'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 TextBox
es? 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:
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 MTGCComboBoxItem
s and then using the Combobox.Items.AddRange
method to load data. Here is the code:
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:
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:
'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:
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 acombobox
, and write a character in the text area and then you click outside the control, thedropdownlist
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 originalDataBinding
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 wholecombobox
(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 theItemSelect
method)
- Minor bug fixes (Added some properties to manage colors, fixed some errors in