OwnerDraw ListView

Introduction

I am writing a small PowerShell tool that will perform a few checks against several virtual machines in vCenter.  I wanted to show the results in a nice grid view.  The ListView control is the easy choice for this.

 

Basic Grid

This picture below shows the basic layout that I started with (with random dummy data), and as you can see it’s not great.

The ‘P’ass and ‘F’ail display could be better, icons would be great.  The problem is that the basic ListView does not support more than one icon in the grid – usually the first one next to the name.

There are some tweaks you can do to move the single icon about by changing columns, but it’s still just one icon.

OwnerDraw

With a lot of the Windows forms controls they support an OwnerDraw method.  This allows developers to hack around with controls and make them do things that they can’t normally do – like having more that one icon in a ListView, including its subitems.  🙂

Looks great doesn’t it.!

It works by replacing “P” or “F” in the top screenshot with “ICON|x” where “x” is the index number of the image you want to use.  For example, my ticks are in position 1, and the crosses are position 2.  Therefore “ICON|1” or “ICON|2” would be used.  My code has an additional option of displaying an icon and text in the same box.  To show this use “BOTH|x|text”

The Code

In order to change a standard ListView into an awsome ListView does not require a lot of code.

about 50 lines of additional code for the OwnerDraw part, and 3x lines in the middle of the ListView definition.

ListView Definition

# Define ListView Control
$lstList                      = New-Object 'System.Windows.Forms.ListView'

# Add OwnerDraw Code
$lstList.OwnerDraw            = $True
$lstList.Add_DrawSubItem($SubIcons_DrawSubItem)
$lstList.Add_DrawColumnHeader($SubIcons_DrawColumnHeader)

# Set ListView properties
$lstList.Location             = ' 43,  12'
$lstList.Size                 = '589, 208'
$lstList.LabelEdit            = $True
$lstList.FullRowSelect        = $True
$lstList.GridLines            = $True
$lstList.HideSelection        = $True
$lstList.HeaderStyle          = 'Nonclickable'
$lstList.SmallImageList       = $imgResult
$lstList.Sorting              = 'Ascending'
$lstList.View                 = 'Details'

# Add first two columns
$lstList_CH_1                 = New-Object 'System.Windows.Forms.ColumnHeader'
$lstList_CH_2                 = New-Object 'System.Windows.Forms.ColumnHeader'
$lstList_CH_1.Text            = 'Virtual Machine Name'
$lstList_CH_2.Text            = 'Status'
$lstList_CH_1.Width           = 244
$lstList_CH_2.Width           = 100
$lstList_CH_2.TextAlign       = 'Center'
$lstList.Columns.Add($lstList_CH_1) | Out-Null
$lstList.Columns.Add($lstList_CH_2) | Out-Null

# Add 'results' columns
For ($i=0; $i -le 7; $i++)
{
    $newCH           = New-Object 'System.Windows.Forms.ColumnHeader'
    $newCH.Text      = 'RESULTS'.Substring($i, 1)
    $newCH.Width     = 32
    $newCH.TextAlign = 'Center'
    $lstList.Columns.Add($newCH) | Out-Null
}

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

Most of this code you should already know if you write PowerShell Forms.  Lines 05,06 and 07 are the new ones that you need for the OwnerDraw parts.

OwnerDraw Code

This next code block has two functions, they are explained below.

$SubIcons_DrawColumnHeader = {
    [System.Windows.Forms.DrawListViewColumnHeaderEventArgs]$e = $_
    $e.DrawDefault = $True
    $e.DrawBackground()
    $e.DrawText()
}

$SubIcons_DrawSubItem = {
    [System.Windows.Forms.DrawListViewSubItemEventArgs]$e = $_

    If ($e.SubItem.Text.Length -le 5) { $e.DrawDefault = $True }
    Else
    {
        If ($e.SubItem.Text.Contains('|') -eq $True)
        {
            [System.Drawing.Image]$icon = ($imgResult.Images[$e.SubItem.Text.Split('|')[1] -as [int]])
            [int]$xPos = ($e.SubItem.Bounds.X + (($e.SubItem.Bounds.Width  / 2) -as [int]) - (($icon.Width  /2) -as [int]))
            [int]$yPos = ($e.SubItem.Bounds.Y + (($e.SubItem.Bounds.Height / 2) -as [int]) - (($icon.Height /2) -as [int]))
        }

        Switch ($e.SubItem.Text.Substring(0,5).ToUpper())
        {
            'ICON|'
            {
                $r = New-Object 'System.Drawing.Rectangle'($xPos, $yPos, $icon.Width, $icon.Height)

                $e.DrawDefault = $false
                $e.Graphics.DrawImage($icon, $r)
            }

            'BOTH|'
            {
                [int]$fPos = ((($e.SubItem.Bounds.Height - $sysFont.Height) / 2) -as [int])
                $fColor    = New-Object 'System.Drawing.SolidBrush'($e.SubItem.ForeColor)

                $rI = New-Object 'System.Drawing.Rectangle'($($e.SubItem.Bounds.X + 3), $yPos, $icon.Width, $icon.Height)
                $rS = New-Object 'System.Drawing.PointF'   ($($e.SubItem.Bounds.X + 5 + $icon.Width), $($e.SubItem.Bounds.Y + $fPos))

                $e.DrawDefault = $false
                $e.Graphics.DrawImage($icon, $rI)
                $e.Graphics.DrawString($e.SubItem.Text.Split('|')[2], $sysFont, $fColor, $rS)
            }

            Default { $e.DrawDefault = $True }
        }
    }
}

The first SubIcons_DrawColumnHeader can largely be ignored.  It’s needed to draw the default column headers.

The second is the main worker.  It turns the text “ICON|x” into the image to be used, and displays it centred in the subitem window.  It also displays “BOTH|x|text” with the icon on the left with the text left-aligned next to it.

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

4 thoughts on “OwnerDraw ListView

  1. angrySysadmin says:

    “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.”

    Why even post it if you aren’t willing to share the code? Come on man. Ever used FOSS-Software? Those guys shared it with the world for free too.
    Please post raw source-code too, thanks.

Leave a Reply

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