Search code examples
vb.netwinformscomboboxicons

Is there a way to add a clickable Icon to ComboBox?


I'm currently working on a WinForms App in vb.net, where you can select different data in a combobox. The items inside these combobox can be edited or deleted, so heres my question: Is there a way to add icons, e.g. a pencil and a trash, for each item to show the user "Click here to edit" or "Click here to delete"?

In my head it looks kind of the following picture:

enter image description here

Thank you very much :)


Solution

  • I created a new combo box control by inheriting a class from ComboBox.

    Imports System.ComponentModel
    
    Public Class ComboBoxEx
        Inherits ComboBox
    
        ...
    End Class
    

    The idea is to use the DrawMode DrawMode.OwnerDrawFixed and to do all the drawing in code. This allows us to draw the images representing the clickable buttons. I added two images as resources to the project (My.Resources.pencil and My.Resources.Trash_16x16, yours might have different names).

    Const IconSize = 20
    
    Dim stringFormat As StringFormat = New StringFormat() With {.LineAlignment = StringAlignment.Center}
    
    Public Sub New()
        DrawMode = DrawMode.OwnerDrawFixed
        DropDownStyle = ComboBoxStyle.DropDownList
        ItemHeight = 21
    End Sub
    
    Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
        e.DrawBackground()
    
        If e.Index >= 0 Then
            Dim g As Graphics = e.Graphics
            Dim brushColor = If(((e.State And DrawItemState.Selected) <> 0),
                SystemColors.Highlight,
                e.BackColor)
            Using brush As Brush = New SolidBrush(brushColor)
                g.FillRectangle(brush, e.Bounds)
            End Using
            Using textBrush As Brush = New SolidBrush(e.ForeColor)
                g.DrawString(Items(e.Index).ToString(), e.Font, textBrush, e.Bounds, stringFormat)
            End Using
    
            ' Skip the default item at index = 0 and the text box area (DrawItemState.ComboBoxEdit)
            If e.Index > 0 And (e.State And DrawItemState.ComboBoxEdit) = 0 Then
                Dim image = My.Resources.pencil
                Dim point = New Point(
                    Width - 2 * IconSize + (IconSize - image.Width) \ 2,
                    e.Bounds.Y + (ItemHeight - image.Height) \ 2)
                g.DrawImage(image, point)
    
                image = My.Resources.Trash_16x16
                point = New Point(
                    Width - IconSize + (IconSize - image.Width) \ 2,
                    e.Bounds.Y + (ItemHeight - image.Height) \ 2)
                g.DrawImage(image, point)
            End If
        End If
    
        e.DrawFocusRectangle()
    End Sub
    

    This was the visual part. Now we must detect mouse clicks on the buttons in the drop down and also raise events when they are clicked.

    Dim isDroppedDown As Boolean
    
    Public Event Button1Clicked()
    Public Event Button2Clicked()
    
    Protected Overrides Sub OnDropDown(e As EventArgs)
        isDroppedDown = True
        MyBase.OnDropDown(e)
    End Sub
    
    Protected Overrides Sub OnDropDownClosed(e As EventArgs)
        isDroppedDown = False
        MyBase.OnDropDownClosed(e)
    End Sub
    
    Protected Overrides Sub WndProc(ByRef m As Message)
        Const WM_COMMAND = &H111
    
        If LicenseManager.UsageMode = LicenseUsageMode.Runtime And isDroppedDown And
            m.Msg = WM_COMMAND And (CType(m.WParam, Int64) >> 16) = 1 Then
    
            Dim button = ButtonClicked()
            ' If the user clicked a button (skipping default item)
            If button <> 0 And SelectedIndex > 0 Then
                m.Result = New IntPtr(1)
                If button = 1 Then
                    RaiseEvent Button1Clicked()
                Else
                    RaiseEvent Button2Clicked()
                End If
                Return
            End If
        End If
        MyBase.WndProc(m)
    End Sub
    
    Private Function ButtonClicked() As Integer
        Dim pos = PointToClient(MousePosition)
    
        If pos.X > Size.Width - IconSize Then
            Return 2
        ElseIf pos.X > Size.Width - 2 * IconSize Then
            Return 1
        End If
        Return 0
    End Function
    

    After compiling your project, this new ComboBoxEx appears in the winforms toolbox and you can drag and drop it to your form.

    In the form you can then handle the button events

    Private Sub ComboBoxEx1_Button1Clicked() Handles ComboBoxEx1.Button1Clicked
        Label1.Text = $"Pen clicked. Item = {ComboBoxEx1.SelectedItem.ToString()}"
    End Sub
    
    Private Sub ComboBoxEx1_Button2Clicked() Handles ComboBoxEx1.Button2Clicked
        Label1.Text = $"Trash bin clicked. Item = {ComboBoxEx1.SelectedItem.ToString()}"
    End Sub
    

    You may have to tweak the icon size, text size etc. to fit your needs.