OwnerDraw ComboBox

Introduction

Drop down lists are great for selecting from a list of items.  However, a plain text list can be difficult to read if there are a lot of items.  Icons help to make a list easier to read, however the standard ComboBox control does not support images.

OwnerDraw

As with the ListView control that I customised here, the ComboBox can also be modified.

My new control supports not only adding icons, but also indenting and disabling specific items.

        

The first image shows multicoloured icons with randomly applied indenting.  Indents can be any value you require.  Only 0 and 1 are shown here.  The second image shows all green icons, however items 3 and 4 are “disabled”.  Notice that the icons and text appear greyed out.

The Code

As with the ListView control, a few more lines of code are required…

ComboBox Definition

# Define ComboBox control
$cmoComboBox                       = New-Object 'System.Windows.Forms.ComboBox'

# Add OwnerDraw code
$cmoComboBox.ItemHeight            = '22'
$cmoComboBox.DrawMode              = 'OwnerDrawFixed'
$cmoComboBox.Add_DrawItem($ComboIcons_OnDrawItem)
$cmoComboBox.Add_SelectedIndexChanged($ComboIcons_SelectedIndexChanged)

# Set ComboBox properties
$cmoComboBox.Location              = ' 12,  65'
$cmoComboBox.Size                  = '370,  35'
$cmoComboBox.Font                  = $sysFont
$cmoComboBox.DropDownStyle         = 'DropDownList'

# Add control to the main form
$MainFORM.Controls.Add($cmoComboBox)

OwnerDraw Code

# Required Bits
[int]$script:ComboIcons_SelectedItem = 0               # Keeps track of the currently selected item
[System.Collections.ArrayList]$IconComboItems = @{}    # Holds all the custom items for the ComboxBox
Function New-IconComboItem { Return ( New-Object -TypeName PSObject -Property @{'Icon' = ''; 'Name' = ''; 'Text' = ''; 'Indent' = '0'; 'Enabled' = $True; } ) }

# Used to "disable" an image (turn it into a greyscale image of itself)
Function GreyScaleImage ([System.Drawing.Image]$Image)
{
    If ([string]::IsNullOrEmpty($Image) -eq $True) { Return $Image }
    [System.Drawing.Image]                  $newImage  = New-Object 'System.Drawing.Bitmap'($Image.Width, $Image.Height)
    [System.Drawing.Graphics]               $graphics  = [System.Drawing.Graphics]::FromImage($newImage)
    [System.Drawing.Imaging.ColorMatrix]    $matrix    = New-Object 'System.Drawing.Imaging.ColorMatrix'
    [System.Drawing.Imaging.ImageAttributes]$imgAttrib = New-Object 'System.Drawing.Imaging.ImageAttributes'
    $matrix.Matrix00 = '0.0'; $matrix.Matrix10 = '1.0'; $matrix.Matrix11 = '1.0'; $matrix.Matrix12 = '1.0'; $matrix.Matrix22 = '0.0'; $matrix.Matrix33 = '0.5'
    $imgAttrib.SetColorMatrix($matrix, [System.Drawing.Imaging.ColorMatrixFlag]::Default, [System.Drawing.Imaging.ColorAdjustType]::Bitmap)
    $graphics.DrawImage($Image, (New-Object 'System.Drawing.Rectangle'(0, 0, $Image.Width, $Image.Height)), 0, 0, $newImage.Width, $newImage.Height, [System.Drawing.GraphicsUnit]::Pixel, $imgAttrib)
    $graphics.Dispose()
    Return $newImage
}

# Custom draws each cmoComboBox item
$ComboIcons_OnDrawItem = {
    [System.Windows.Forms.DrawItemEventArgs]$e = $_
    $e.DrawBackground()
    $e.DrawFocusRectangle()

    [System.Drawing.Rectangle]$bounds = $e.Bounds    
    If (($e.Index -gt -1) -and ($e.Index -lt $IconComboItems.Count))
    {
        $currItem = $IconComboItems[$e.Index]

        [int]                      $indent     = ($currItem.Indent * 14) 
        [System.Drawing.Image]     $icon       = $imgResult.Images[$currItem.Icon]
        [System.Drawing.SolidBrush]$solidBrush = [System.Drawing.SolidBrush]$e.ForeColor

        If ($currItem.Enabled -eq $False) {
            $icon = (GreyScaleImage -Image $icon)
            $solidBrush.Color = [System.Drawing.SystemColors]::GrayText
         }

        $iconRect = New-Object 'System.Drawing.RectangleF'((($bounds.Left) + 5 + $indent), (($bounds.Top) + 2), 16, 16)
        $textRect = New-Object 'System.Drawing.RectangleF'((($bounds.Left) + ($iconRect.Width) + 9 + $indent), $bounds.Top, (($bounds.Width) - ($iconRect.Width) - 9 - $indent), $bounds.Height)
        $format   = New-Object 'System.Drawing.StringFormat'
        $format.Alignment     = [System.Drawing.StringAlignment]::Near
        $format.LineAlignment = [System.Drawing.StringAlignment]::Center

        If ($icon -ne $null) { $e.Graphics.DrawImage($icon, $iconRect) }
        $e.Graphics.DrawString($currItem.Text, $e.Font, $solidBrush, $textRect, $format)
    }
}

# Get the currently selected item (but only if not disabled)
$ComboIcons_SelectedIndexChanged = {
    If ($cmoComboBox.SelectedIndex -lt 0) { Return }
    If ($cmoComboBox.SelectedIndex -lt $IconComboItems.Count)
    {
        If ($IconComboItems[$cmoComboBox.SelectedIndex].Enabled -eq $false) { $cmoComboBox.SelectedIndex = $script:ComboIcons_SelectedItem }
        Else                                                             { $script:ComboIcons_SelectedItem = $cmoComboBox.SelectedIndex }
    }
    $lbl_Description.Text = $($IconComboItems[$script:ComboIcons_SelectedItem])
}

ComboxBox List Items

There is a little more to adding items to the ComboBox list however.  You need to create a new object, populate it your required values, then add that to the $IconComboItems array.  Once you have created all your items, add them to the ComboBox control

Single Item Example

# Define new custom ComboBox item
$newItem         = New-IconComboItem
$newItem.Name    = 'item01'
$newItem.Text    = 'This is Item 01'
$newItem.Icon    = 0
$newItem.Indent  = 0
$newItem.Enabled = $True

# Add the new item to the array of items
[void]$IconComboItems.Add($newItem)

# Repeat the above until you have added all the items you need

# Add the array of items to the ComboBox itself
[void]$cmoComboBox.Items.AddRange($IconComboItems)
$cmoComboBox.SelectedIndex = 0

Coloured Folder Example (Screen Shot 1)

[string[]]$colors = @('Black', 'Blue', 'Cyan', 'Green', 'Grey', 'Orange', 'Pink', 'Red', 'White', 'Yellow')
0..9 | ForEach-Object -Process {
    $tmp = New-IconComboItem
    $tmp.Name = "Item$_"
    $tmp.Text = $colors[$_]
    $tmp.Icon = $_
    If ($_ -gt 0) { $tmp.Indent = (Get-Random -Maximum 2 -Minimum 0) }

    [void]$IconComboItems.Add($tmp)
    $tmp = $null
}

[void]$cmoComboBox.Items.AddRange($IconComboItems)
$cmoComboBox.SelectedIndex = 0

 

Conclusion

Obviously there is a lot of code missing that is used in the screen shots, but the important part is there in order to get you up and running.  Have a play, and let me know what you think in the comments below.

A full demo can be found on my GitHub

Leave a Reply

Your email address will not be published. Required fields are marked *