Home
Search
 
What's New
Index
Books
Links
Q & A
Newsletter
Banners
 
Feedback
Tip Jar
 
C# Helper...
 
XML RSS Feed
Follow VBHelper on Twitter Follow VBHelper on Twitter
 
 
 
MSDN Visual Basic Community
 
 
 
 
 
TitleMake an owner-drawn ListView control that draws server status information in Visual Basic .NET
DescriptionThis example shows how to make an owner-drawn ListView control that draws server status information in Visual Basic .NET.
Keywordscontrols, graphics, ListView, owner draw, owner draws, OwnerDraw, example, example program, Windows Forms programming, Visual Basic .NET, VB.NET
CategoriesControls, Graphics
 

Normally a ListView displays textual data but you can change that behavior to make it display anything you can draw. This example draws images and colored circles to show the status of its entries.

The approach is to set the ListView's OwnerDraw property to True and then make items and subitems as placeholders in the control. Then code then catches the ListView's DrawColumnHeader, DrawItem, and DrawSubItem event handlers and draws the items appropriately.

The only really non-obvious issue is that the DrawSubItem event handler that is called to draw the details view executes for every subitem in each row including the item itself. That means the DrawItem event handler should not draw the item because it will be taken care of by the DrawSubItem event handler.

The basic ideas aren't too complicated but you do need to do a fair amount of drawing so this is a fairly long example.

The program uses the following ServerStatus class to hold information about each server.

 
Public Class ServerStatus
    Public ServerName As String
    Public Logo As Image
    Public StatusColor As Color

    Public Sub New(ByVal new_ServerName As String, ByVal _
        new_Logo As Image, ByVal new_StatusColor As Color)
        ServerName = new_ServerName
        Logo = new_Logo
        StatusColor = new_StatusColor
    End Sub
End Class
 
This class holds a server's name, a picture representing the server, and a color indicating its status.

When the program loads, it creates the ListView controls' data as shown in the following code.

 
' Make some data.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal _
    e As System.EventArgs) Handles MyBase.Load
    Dim list_views() As ListView = {lvwList, lvwSmallIcon, _
        lvwLargeIcon, lvwTile, lvwDetails}

    For Each lvw As ListView In list_views
        AddItem(lvw, "Butterfly", My.Resources.Butterfly, _
            Color.Green)
        AddItem(lvw, "Guppy", My.Resources.Fish, Color.Red)
        AddItem(lvw, "Peggy", My.Resources.Peggy, _
            Color.Yellow)
    Next lvw
End Sub

' Make a server status item.
Private Sub AddItem(ByVal lvw As ListView, ByVal server As _
    String, ByVal logo As Image, ByVal status As Color)
    ' Make the item.
    Dim item As New ListViewItem(server)

    ' Save the ServeStatus item in the Tag property.
    Dim server_status As New ServerStatus(server, logo, _
        status)
    item.Tag = server_status
    item.SubItems(0).Name = "Server"

    ' Add subitems so they can draw.
    item.SubItems.Add("Logo")
    item.SubItems.Add("Status")

    ' Add the item to the ListView.
    lvw.Items.Add(item)
End Sub
 
The form's Load event handler calls AddItem to add items to the ListView controls. AddItem makes a ListViewItem object and adds it to a ListView. It sets the item's Tag property to a ServerStatus object so the program can late get information about each item.

The following code shows the DrawColumnHeader event handler.

 
' Just draw the column's text.
Private Sub lvw_DrawColumnHeader(ByVal sender As _
    System.Object, ByVal e As _
    System.Windows.Forms.DrawListViewColumnHeaderEventArgs) _
    Handles lvwTile.DrawColumnHeader, _
    lvwSmallIcon.DrawColumnHeader, lvwList.DrawColumnHeader, _
    lvwLargeIcon.DrawColumnHeader, _
    lvwDetails.DrawColumnHeader
    Using string_format As New StringFormat()
        string_format.Alignment = StringAlignment.Center
        string_format.LineAlignment = StringAlignment.Center

        Dim text As String = _
            lvwList.Columns(e.ColumnIndex).Text
        Select Case e.ColumnIndex
            Case 0
                e.Graphics.DrawString(text, lvwList.Font, _
                    Brushes.Black, e.Bounds)
            Case 1
                e.Graphics.DrawString(text, lvwList.Font, _
                    Brushes.Blue, e.Bounds)
            Case 2
                e.Graphics.DrawString(text, lvwList.Font, _
                    Brushes.Green, e.Bounds)
        End Select
    End Using
End Sub
 
In this example, the DrawColumnHeader event handler is fairly simple. It just draws each column's text within the indicated bounds. It gets the text from the ListView's column text set at design time.

The following code shows the DrawItem event handler.

 
' Draw the item. In this case, the server's logo.
Private Sub lvw_DrawItem(ByVal sender As System.Object, _
    ByVal e As _
    System.Windows.Forms.DrawListViewItemEventArgs) Handles _
    lvwTile.DrawItem, lvwSmallIcon.DrawItem, _
    lvwList.DrawItem, lvwLargeIcon.DrawItem, _
    lvwDetails.DrawItem
    ' Draw Details view items in the DrawSubItem event
    ' handler.
    Dim lvw As ListView = e.Item.ListView
    If (lvw.View = View.Details) Then Return

    ' Get the ListView item and the ServerStatus object.
    Dim item As ListViewItem = e.Item
    Dim server_status As ServerStatus = DirectCast(item.Tag, _
        ServerStatus)

    ' Clear.
    e.DrawBackground()

    ' Draw a status indicator.
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
    Dim rect As New Rectangle( _
        e.Bounds.Left + 1, e.Bounds.Top + 1, _
        e.Bounds.Height - 2, e.Bounds.Height - 2)
    Using br As New SolidBrush(server_status.StatusColor)
        e.Graphics.FillEllipse(br, rect)
    End Using
    e.Graphics.DrawEllipse(Pens.Black, rect)
    Dim left As Integer = rect.Right + 2

    ' See how much we must scale it.
    Dim scale As Single = e.Bounds.Height / _
        CSng(server_status.Logo.Height)

    ' Scale and position the image.
    e.Graphics.ScaleTransform(scale, scale)
    e.Graphics.TranslateTransform( _
        left, _
        e.Bounds.Top + (e.Bounds.Height - _
            server_status.Logo.Height * scale) / 2, _
        System.Drawing.Drawing2D.MatrixOrder.Append)

    ' Draw the image.
    e.Graphics.DrawImage(server_status.Logo, 0, 0)

    ' Draw the focus rectangle if appropriate.
    e.Graphics.ResetTransform()
    e.DrawFocusRectangle()
End Sub
 
If this is the ListView's details view, the code simply exits because the details view is drawn by the DrawSubItem event handler.

If this is not the details view, the code gets the item's corresponding ServerStatus object from the item's Tag property.

Next the code clears the item's background. It makes a Rectangle in the left of the area where the item should be drawn that is as wide as it is tall and draws a status indicator circle there.

The code then calculates the scale at which it should draw the server's logo image to fit the available area nicely. It next draws the image to the right of the status circle.

The event handler finishes by calling DrawFocusRectangle to draw a focus rectangle around the item if it has the focus. (The first item in the top left ListView has the focus in the picture shown above.)

That's all the code needed to draw the item unless the ListView is displaying the detail view. The following DrawSubItem event handler produces all of the graphics for the detail view.

 
' Draw subitems for Detail view.
Private Sub lvw_DrawSubItem(ByVal sender As System.Object, _
    ByVal e As _
    System.Windows.Forms.DrawListViewSubItemEventArgs) _
    Handles lvwTile.DrawSubItem, lvwSmallIcon.DrawSubItem, _
    lvwList.DrawSubItem, lvwLargeIcon.DrawSubItem, _
    lvwDetails.DrawSubItem
    ' Get the ListView item and the ServerStatus object.
    Dim item As ListViewItem = e.Item
    Dim server_status As ServerStatus = DirectCast(item.Tag, _
        ServerStatus)

    ' Draw.
    Select Case e.ColumnIndex
        Case 0
            ' Draw the server's name.
            e.Graphics.DrawString(server_status.ServerName, _
                _
                lvwList.Font, Brushes.Black, e.Bounds)
        Case 1
            ' Draw the server's logo.
            Dim scale As Single = e.Bounds.Height / _
                CSng(server_status.Logo.Height)
            e.Graphics.ScaleTransform(scale, scale)
            e.Graphics.TranslateTransform( _
                e.Bounds.Left, _
                e.Bounds.Top + (e.Bounds.Height - _
                    server_status.Logo.Height * scale) / 2, _
                    _
                MatrixOrder.Append)
            e.Graphics.DrawImage(server_status.Logo, 0, 0)
        Case 2
            ' Draw the server's status.
            Dim rect As New Rectangle( _
                e.Bounds.Left + 1, e.Bounds.Top + 1, _
                e.Bounds.Width - 2, e.Bounds.Height - 2)
            Using br As New _
                SolidBrush(server_status.StatusColor)
                e.Graphics.FillRectangle(br, rect)
            End Using
            Dim pen_color As Color = Color.FromArgb(255, _
                255 - server_status.StatusColor.R, _
                255 - server_status.StatusColor.G, _
                255 - server_status.StatusColor.B)
            Using br As New SolidBrush(pen_color)
                Using string_format As New StringFormat()
                    string_format.Alignment = _
                        StringAlignment.Center
                    string_format.LineAlignment = _
                        StringAlignment.Center
                    Using the_font As New Font(lvwList.Font, _
                        FontStyle.Bold)
                        e.Graphics.DrawString(server_status.StatusColor.Name, _
                            _
                            Font, br, e.Bounds, _
                                string_format)
                    End Using
                End Using
            End Using
    End Select

    ' Draw the focus rectangle if appropriate.
    e.Graphics.ResetTransform()
    Dim lvw As ListView = e.Item.ListView
    If (lvw.FullRowSelect) Then
        e.DrawFocusRectangle(e.Item.Bounds)
    ElseIf (e.SubItem.Name = "Server") Then
        e.DrawFocusRectangle(e.Bounds)
    End If
End Sub
 
This code starts by getting the item corresponding to the subitem that it should draw. It then gets the ServerStatus object corresponding to that item.

Next the code uses a switch statement to see which of the item's columns it should draw. Depending on which column this is, the code draws:

  • The server's name
  • The server's logo
  • The server's status inside a colored box

The event handler finishes by calling DrawFocusRectangle to draw a focus rectangle around the item if it has the focus. If the ListView control's FullRowSelect property is true, the control passes DrawFocusRectangle the bounds of the item so it draws the rectangle around the whole row. If FullRowSelect is false, it only calls DrawFocusRectangle if the code is drawing the server's name so the box appears only around the name. (Change FullRowSelect to false at design time to see this.)

 
 
 
 
Copyright © 1997-2010 Rocky Mountain Computer Consulting, Inc.   All rights reserved.
  Updated