Contents
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).
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.
Name |
Description |
DropDownList |
This property is just present in the "parent" combobox , but in this one is overridden and now has 2 possible values:
DropDown (default one)
DropDownList
|
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
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
|
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 |
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
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
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
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
If Me.BorderStyle = TipiBordi.FlatXP Then
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:
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
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))
g.FillRectangle(stripeColorBrush, left - 5, top - 1,
arrowWidth + 6, height + 2)
DrawArrow(g, True)
End If
Highlighted = False
End If
End Sub
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
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)
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)
If Me.Enabled = False Then
DrawColor = SystemColors.InactiveBorder
End If
Dim pen As pen = New pen(DrawColor, 1)
g.DrawRectangle(pen, ClientRectangle.Left, ClientRectangle.Top,
ClientRectangle.Width - 1, ClientRectangle.Height - 1)
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!
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)
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 _
End If
Else
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
currentText = Me.AccessibilityObject.Value
Dim posizione As Long
Dim testingString As String
posizione = InStr(Me.Text, "&")
If posizione > 0 Then
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)
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
Select Case e.KeyCode
Case Keys.Left, Keys.Right, Keys.Up, Keys.Delete, _
Keys.Down, Keys.End, Keys.Home
Return
End Select
sTypedText = Me.Text
If e.KeyCode <> Keys.Back Then
iFoundIndex = Me.FindString(sTypedText)
Else
iFoundIndex = Me.FindStringExact(sTypedText)
End If
If iFoundIndex >= 0 AndAlso Me.Text <> "" Then
oFoundItem = Me.Items(iFoundIndex)
sFoundText = Me.GetItemText(oFoundItem)
sAppendText = sFoundText.Substring(sTypedText.Length)
Me.Text = sTypedText & sAppendText
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
Me.SelectedIndex = -1
Me.SelectedItem = Nothing
End If
End If
PressedKey = False
End Sub
Protected Overrides Sub OnLeave(ByVal e As System.EventArgs)
Dim iFoundIndex As Integer
Dim currentText As String
currentText = Me.AccessibilityObject.Value
Dim testingString As String
Dim posizione As Long
posizione = InStr(Me.Text, "&")
If posizione > 0 Then
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.
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
Inherits ListViewItem
Implements IComparable
Public Col1 As String
Public Col2 As String
Public Col3 As String
Public Col4 As String
Sub New(ByVal C1 As String, Optional ByVal C2 As String = "",
Optional ByVal C3 As String = "", Optional ByVal C4 As String = "")
MyBase.New()
Col1 = C1
Col2 = C2
Col3 = C3
Col4 = C4
Me.Text = C1
End Sub
Private Function CompareTo(ByVal obj As Object)
As Integer Implements IComparable.CompareTo
If obj Is Nothing Then Return 1
Dim other As MTGCComboBoxItem = CType(obj, MTGCComboBoxItem)
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):
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
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()
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.
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....
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.
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:
If Not mcbo.ItemSelect(2, "Value1", False, False) Then
mcbo.SelectedIndex = -1
End If
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
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
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
- Version 1.0.0.0 - October 20, 2004
- 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)