|
|
Title | Make an owner-drawn ListView control that draws server status information in Visual Basic .NET |
Description | This example shows how to make an owner-drawn ListView control that draws server status information in Visual Basic .NET. |
Keywords | controls, graphics, ListView, owner draw, owner draws, OwnerDraw, example, example program, Windows Forms programming, Visual Basic .NET, VB.NET |
Categories | Controls, 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.)
|
|
|
|
|
|
|
|
|