שתף באמצעות


Multiple columns in Combobox and ListBox

Question

Thursday, September 7, 2017 2:04 AM | 1 vote

I would like to use multiple columns in databound combo boxes and/or list boxes.  What are the properties required to do this?

gwboolean

All replies (16)

Thursday, September 7, 2017 7:17 AM ✅Answered | 2 votes

Hi gwboolean,

Do you want to do like this:

You can do this by Combobox_DrawItem event, like this:

 Private Sub Form7_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        fun()
        ComboBox1.DrawMode = DrawMode.OwnerDrawVariable
    End Sub
    Dim dt As New DataTable
    Private Sub fun()
        Dim str As String = "Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=D:\C# and VB Support\Example(VB)\Demo\Data3.mdf;Integrated Security=True;Connect Timeout=30"
        Dim conn As New SqlConnection(str)
        conn.Open()
        Dim sql As String = "select ID,ProductName,Vendor,Material from Test12"
        Dim cmd As New SqlCommand(sql, conn)
        Dim adapter As New SqlDataAdapter(cmd)
        'fill data into dataset
        adapter.Fill(dt)

        ComboBox1.DisplayMember = "ID"
        ComboBox1.DataSource = dt
    End Sub

    Private Sub ComboBox1_DrawItem(sender As Object, e As DrawItemEventArgs) Handles ComboBox1.DrawItem
        e.DrawBackground()
        Dim drv As DataRowView = DirectCast(ComboBox1.Items(e.Index), DataRowView)

        Dim id As String = drv("ID").ToString()
        Dim productname As String = drv("ProductName").ToString()
        Dim Vendor As String = drv("Vendor").ToString()
        Dim material As String = drv("Material").ToString()

        Dim r1 As Rectangle = e.Bounds
        r1.Width = r1.Width / 4

        Using sb As New SolidBrush(Color.Black)
            e.Graphics.DrawString(id, e.Font, sb, r1)
        End Using

        'Using p As New Pen(Color.AliceBlue)
        '    e.Graphics.DrawLine(p, r1.Right, 0, r1.Right, r1.Bottom)
        'End Using

        Dim r2 As Rectangle = e.Bounds
        r2.X = e.Bounds.Width / 8
        r2.Width = r2.Width / 4

        Using sb As New SolidBrush(Color.Black)
            e.Graphics.DrawString(productname, e.Font, sb, r2)
        End Using

        'Using p As New Pen(Color.AliceBlue)
        '    e.Graphics.DrawLine(p, r2.Right, 0, r2.Right, r2.Bottom)
        'End Using

        Dim r3 As Rectangle = e.Bounds
        r3.X = e.Bounds.Width / 3
        r3.Width = r3.Width / 4

        Using sb As New SolidBrush(Color.Black)
            e.Graphics.DrawString(Vendor, e.Font, sb, r3)
        End Using

        'Using p As New Pen(Color.AliceBlue)
        '    e.Graphics.DrawLine(p, r3.Right, 0, r3.Right, r3.Bottom)
        'End Using

        Dim r4 As Rectangle = e.Bounds
        r4.X = e.Bounds.Width / 1.5
        r4.Width = r4.Width / 4

        Using sb As New SolidBrush(Color.Black)
            e.Graphics.DrawString(material, e.Font, sb, r4)
        End Using

    End Sub

Best Regards,

Cherry

MSDN Community Support
Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.


Thursday, September 7, 2017 9:56 AM ✅Answered | 2 votes

Here is a custom ComboBox done in C#, shown below in vb.net.

Imports System.Drawing.Drawing2D

<ComponentModel.DesignerCategoryAttribute("Code")>
Public Class MultiColumnCombo
    Inherits ComboBox

    Private MyColumnWidths As String = "100"
    Private mColumnWidths As String()
    Private DoNotReact As Boolean = False
    Private TextHasChanged As Boolean = False

    Sub New()
        MyBase.New()
        DrawMode = DrawMode.Normal

        SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        SetStyle(ControlStyles.DoubleBuffer, True)
    End Sub
    Public Property ColumnWidths() As String
        Get
            Return MyColumnWidths
        End Get
        Set(ByVal Value As String)

            MyColumnWidths = Value
            mColumnWidths = Value.Split(CType(";", Char))
            Dim FinalWidth As Integer = 0

            For Each ColumnWidth As String In mColumnWidths
                FinalWidth += CInt(ColumnWidth)
            Next ColumnWidth

            MyBase.DropDownWidth = FinalWidth + 10

        End Set
    End Property
    <DebuggerStepThrough()>
    Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
        Dim myBrush As Brush

        Dim LightColor As Color = Color.Blue
        Dim DarkColor As Color = Color.Black
        Dim GradBrush As Brush = New LinearGradientBrush(e.Bounds,
                                                         LightColor,
                                                         DarkColor,
                                                         LinearGradientMode.Horizontal)

        Select Case CInt((e.State And DrawItemState.Selected))
            Case DrawItemState.Selected
                myBrush = New SolidBrush(BackColor)
                e.Graphics.FillRectangle(GradBrush, e.Bounds)
            Case Else
                myBrush = New SolidBrush(ForeColor)
                e.Graphics.FillRectangle(New SolidBrush(BackColor), e.Bounds)
        End Select

        Dim str As String
        ' Draw the current item text based on the current Font and the custom brush settings.
        Dim row As DataRowView = (CType(MyBase.Items(e.Index), DataRowView))
        Dim newpos As Integer = e.Bounds.X
        Dim endpos As Integer = e.Bounds.X

        For indx As Integer = 0 To UBound(mColumnWidths)
            Dim ColLength As Integer = CType(mColumnWidths(indx), Integer)
            endpos += ColLength

            Dim Charaant As Integer = CInt(Math.Round(CDbl(ColLength) / 6.2))
            Dim rawitem As String = row.Item(indx).ToString()

            If ColLength <> 0 Then
                If Charaant > rawitem.Length Then
                    str = rawitem
                Else
                    str = rawitem.Substring(0, Charaant)
                End If

                Dim r As RectangleF = New RectangleF(newpos + 2,
                                                     e.Bounds.Y,
                                                     endpos - 1,
                                                     e.Bounds.Height)

                e.Graphics.DrawString(str, e.Font, myBrush, r)


                If indx <= UBound(mColumnWidths) Then

                    e.Graphics.DrawLine(New Pen(Color.Black),
                                        endpos,
                                        e.Bounds.Y,
                                        endpos,
                                        Me.ItemHeight * Me.MaxDropDownItems
                                        )

                    e.Graphics.DrawLine(New Pen(Color.LightGray),
                                        endpos + 1,
                                        e.Bounds.Y, endpos + 1,
                                        Me.ItemHeight * Me.MaxDropDownItems
                                        )

                End If
            End If
            newpos = endpos
        Next

        '  e.DrawFocusRectangle()

        myBrush.Dispose()
        GradBrush.Dispose()
    End Sub
    <DebuggerStepThrough()>
    Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
        Dim foundIndex As Integer, textlngth As Integer

        If DoNotReact = False Then

            If Me.AccessibilityObject.Value Is Nothing Then
                textlngth = 0
                Exit Sub
            Else
                textlngth = Me.AccessibilityObject.Value.Length
            End If

            If textlngth = 0 Then
                MyBase.OnTextChanged(e)
                DoNotReact = False
                TextHasChanged = False
                Exit Sub
            End If

            If textlngth <> 0 Then
                DoNotReact = True
                foundIndex = FindString(Me.AccessibilityObject.Value)
                If foundIndex > -1 Then
                    Me.SelectedIndex = foundIndex
                    Me.Select(textlngth, Me.Text.Length - textlngth)
                    TextHasChanged = True
                Else
                    TextHasChanged = False
                End If

                DoNotReact = False

            End If

            MyBase.OnTextChanged(e)

        End If

        DoNotReact = False

    End Sub
    Protected Overrides Sub OnKeyDown(ByVal e As System.Windows.Forms.KeyEventArgs)

        Select Case e.KeyCode
            Case Keys.Back, Keys.Enter, Keys.Delete
                DoNotReact = True
            Case Keys.Down
                DroppedDown = True
                Invalidate()
        End Select

        MyBase.OnKeyDown(e)

    End Sub
End Class

Usage after compiling and placing the control on a form, note all that matters for loading is we have a DataTable.

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load


        Dim dataTable As New DataTable("Employees")

        dataTable.Columns.Add("Employee ID", GetType(String))
        dataTable.Columns.Add("Name", GetType(String))
        dataTable.Columns.Add("Designation", GetType(String))

        dataTable.Rows.Add(New String() {"D1", "Natalia", "Developer"})
        dataTable.Rows.Add(New String() {"D2", "Jonathan", "Developer"})
        dataTable.Rows.Add(New String() {"D3", "Jake", "Developer"})
        dataTable.Rows.Add(New String() {"D4", "Abraham", "Developer"})
        dataTable.Rows.Add(New String() {"T1", "Mary", "Team Lead"})
        dataTable.Rows.Add(New String() {"PM1", "Calvin", "Project Manager"})
        dataTable.Rows.Add(New String() {"T2", "Sarah", "Team Lead"})
        dataTable.Rows.Add(New String() {"D12", "Monica", "Developer"})
        dataTable.Rows.Add(New String() {"D13", "Donna", "Developer"})

        MultiColumnCombo1.DataSource = dataTable
        MultiColumnCombo1.DisplayMember = "Employee ID"
        MultiColumnCombo1.ValueMember = "Name"
        MultiColumnCombo1.ColumnWidths = "100;100"

    End Sub

Note you adjust column width via the last line of code above. Also need to set the following

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


Thursday, September 7, 2017 3:18 AM

I would like to use multiple columns in databound combo boxes and/or list boxes.  What are the properties required to do this?

The ListBox MultiColumn property will arrange your items in as many columns as required to avoid scrolling. See:
https://msdn.microsoft.com/en-us/library/system.windows.forms.listbox.multicolumn(v=vs.110).aspx


Thursday, September 7, 2017 4:04 AM

Thanks Acamar, that is perfect.  Would you happen to be able to pull one like that for a combo box out of your pocket as well?

gwboolean


Thursday, September 7, 2017 4:39 AM

Thanks Acamar, that is perfect.  Would you happen to be able to pull one like that for a combo box out of your pocket as well?

Something like this?
https://support.microsoft.com/en-au/help/982498/how-to-create-a-multiple-column-drop-down-list-for-a-combo-box-in-wind

That may need some small adjustments for a WIndows Forms project.

This does not come from any private source.  Generally speaking, if you type the topic of the post into your favourite search engine together with '.net' or 'visual basic .net' then you will see a number of likely solutions.


Thursday, September 7, 2017 6:44 AM

Acamar, 

I could not find any information about a multi column combobox on that page you gave.

Success
Cor


Thursday, September 7, 2017 7:03 AM

A multicolumn listbox is a datagridview with the Horizontal and vertical headers set to false. 

For the same combobox (and not a horizontal shown multi column combobox) you can search Internet it is not default in the Microsoft range.

(Or buy 3rd party controls).

 

Success
Cor


Thursday, September 7, 2017 9:59 AM

A multicolumn listbox is a datagridview with the Horizontal and vertical headers set to false. 

For the same combobox (and not a horizontal shown multi column combobox) you can search Internet it is not default in the Microsoft range.

(Or buy 3rd party controls).

 

Success
Cor

I agree that a DataGridView or third party control is a good solution for no hassles.

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


Thursday, September 7, 2017 5:47 PM

Cherry,

I have tried working through your methodology, but I lose it at the line:

Dim conn As New SqlConnection(str)

I am using an Access Dataset and am unable to find an equivalent command for the connection string.

gwboolean


Thursday, September 7, 2017 5:53 PM

Karen,

I can see that in your methodology you create a data table with defined/declared rows and data.

I am using an existing table and I would assume that I would need a loop that carries me through the table.  I would probably need to filter the table to get a desired subgroup as well.

Anyway, could you show me how to use an existing table?

You know, I am beginning to think a datagridview might be the best answer, as you suggest.

Thanks

gwboolean


Thursday, September 7, 2017 5:56 PM

Cherry,

I have tried working through your methodology, but I lose it at the line:

Dim conn As New SqlConnection(str)

I am using an Access Dataset and am unable to find an equivalent command for the connection string.

gwboolean

That has nothing to do with the combobox. 

However, the equal code for an MS Access database is

Dim conn as New OleDbConnection(str)

Success
Cor


Thursday, September 7, 2017 6:12 PM

I do understand that Cor.  But thanks for the appropriate statement for my dataset.

gwboolean


Thursday, September 7, 2017 6:38 PM

Karen,

I can see that in your methodology you create a data table with defined/declared rows and data.

I am using an existing table and I would assume that I would need a loop that carries me through the table.  I would probably need to filter the table to get a desired subgroup as well.

Anyway, could you show me how to use an existing table?

You know, I am beginning to think a datagridview might be the best answer, as you suggest.

Thanks

gwboolean

It does not matter if I used the method shown in my code sample or via loading from a database table.

So here is random example of loading a DataTable from a table in a database.

Public Class Sample
    Private Builder As New OleDbConnectionStringBuilder With
    {
        .Provider = "Microsoft.ACE.OLEDB.12.0",
        .DataSource = IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "DatabaseKu.accdb")
    }
    Public Function ReadUserData(ByVal UserName As String, ByVal UserPassword As String) As DataTable
        Dim dt As New DataTable
        Using cn As New OleDbConnection With {.ConnectionString = Builder.ConnectionString}
            Using cmd As New OleDbCommand With {.Connection = cn}
                cmd.CommandText =
                    <SQL>
                        SELECT * FROM LOGIN WHERE username = @UserName, [password] = @password
                    </SQL>.Value

                cmd.Parameters.AddWithValue("@UserName", UserName)
                cmd.Parameters.AddWithValue("@password", UserPassword)
                cn.Open()
                dt.Load(cmd.ExecuteReader)
            End Using
        End Using

        Return dt

    End Function
End Class

In the above I would set the DataTable returned from the function same as I did in my first example.

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


Thursday, September 7, 2017 7:18 PM

I get that.  I was just looking to see what it looks like to do it that way.  However, I can see that the way  I thought I should code to display from a table is not correct. 

You guys always assume that I know more than I know.  I don't know what I don't know.

Thanks Karen

gwboolean


Friday, September 8, 2017 2:28 PM

I get that.  I was just looking to see what it looks like to do it that way.  However, I can see that the way  I thought I should code to display from a table is not correct. 

You guys always assume that I know more than I know.  I don't know what I don't know.

Thanks Karen

gwboolean

GWBoolean,

What is your purpose of this, the reply you marked as answer has absolute nothing to do with the question. 

I think you want less replies I gues, because this is quite offending. 

Success
Cor


Friday, September 8, 2017 4:40 PM

Sorry Cor, I was not really paying attention and perhaps clicked the wrong one.  I did not know it mattered.  At any rate, I went back and changed it to the replies that provided the information that I was able to use.

I am not sure why this would be offensive to anyone.

gwboolean