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
“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.
You mean the 1000 of lines of code I have already shared on my GitHub page.? https://github.com/My-Random-Thoughts
If you know what you are doing when writing PowerShell forms then the code I pasted is enough you get you working. If you don’t know how to write PowerShell forms, then this code is not for you.
Would love to see the source code to this projekt on your github too. Didn’t mean to offend you. It’s always nice having the source-code to something don’t you think 🙂
Demo located here – https://github.com/My-Random-Thoughts/Various-Code/blob/master/Demos/OwnerDraw-ListView-Demo