In this section, I’ll show you how to customize the list controls (such as the ListBox, ComboBox, and TreeView controls). You won’t build new custom controls in this section; actually, you’ll hook custom code into certain events of a control to take charge of the rendering of its items.
Some of the Windows controls can be customized far more than it is possible through their properties. These are the list controls that allow you to supply your own code for drawing each item. You can use this technique to create a ListBox control that displays its items in different fonts, uses alternating background colors, and so on. You can even put bitmaps on the background of each item, draw the text in any color, and create items of varying heights. This is an interesting technique because without it, as you recall from our discussion of the ListBox control, all items have the same height and you must make the control wide enough to fit the longest item (if this is known at design time). The controls that allow you to take charge of the rendering process of their items are the ListBox, CheckedListBox, ComboBox, and TreeView controls.
To create an owner-drawn control, you must program two events: the MeasureItem and DrawItem events. In the MeasureItem event, you determine the dimensions of the rectangle in which the drawing will take place. In the DrawItem event, you insert the code for rendering the items on the control. Every time the control is about to display an item, it fires the MeasureItem event first and then the DrawItem event. By inserting the appropriate code in the two event handlers, you can take control of the rendering process.
These two events don’t take place unless you set the DrawMode property of the control accordingly. Because only controls that expose the DrawMode property can be owner-drawn, you have a quick way of figuring out whether a control’s appearance can be customized with the techniques discussed in this section. The DrawMode property can be set to Normal (the control draws its own surface), OwnerDrawnFixed (you can draw the control, but the height of the drawing area remains fixed), or OwnerDrawnVariable (you can draw the control and use a different height for each item). The same property for the TreeView control has three different settings: None, OwnerDrawText (you provide the text for each item), and OwnerDrawAll (you’re responsible for drawing each node’s rectangle).
Designing Owner-Drawn ListBox Controls
The default look of the ListBox control will work fine with most applications, but you might have to create owner-drawn ListBoxes if you want to use different colors or fonts for different types of items, or to populate the list with items of widely different lengths (Download the example).
The example you’ll build in this section, shown in Figure 8.8, uses an alternating background color, and each item has a different height, depending on the string it holds. Lengthy strings are broken into multiple lines at word boundaries. Because you’re responsible for breaking the string into lines, you can use any other technique — for example, you can place an ellipsis to indicate that the string is too long to fit on the control, use a smaller font, and so on. The fancy ListBox of Figure 8.8 was created with the OwnerDrawnList project.
To custom-draw the items in a ListBox control (or a ComboBox, for that matter), you use the MeasureItem event to calculate the item’s dimensions, and the DrawItem event to actually draw the item. Each item is a rectangle that exposes a Graphics object, and you can call any of the Graphics object’s drawing methods to draw on the item’s area. The drawing techniques we’ll use in this example are similar to the ones we used in the previous section, but after you learn more about the drawing methods in Chapter “Drawing and Painting with Visual Basic 2008”, you can create even more elaborate designs than the ones shown here.
Each time an item is about to be drawn, the MeasureItem and DrawItem events are fired in this order. In the MeasureItem event handler, we set the dimensions of the item with the statements shown in Listing 8.15.
Listing 8.15: Setting Up an Item’s Rectangle in an Owner-Drawn ListBox Control
Code language: PHP (php)
Private Sub ListBox1 MeasureItem(ByVal sender As Object, _ ByVal e As System.Windows.Forms.MeasureItemEventArgs) _ Handles ListBox1.MeasureItem If fnt Is Nothing Then Exit Sub Dim itmSize As SizeF Dim S As New SizeF(ListBox1.Width, 200) itmSize = e.Graphics.MeasureString(ListBox1.Items(e.Index).ToString, fnt, S) e.ItemHeight = itmSize.Height e.ItemWidth = itmSize.Width End Sub
The MeasureString method of the Graphics object accepts as arguments a string, the font in which the string will be rendered, and a SizeF object. The SizeF object provides two members: the Width and Height members, which you use to pass to the method information about the area in which we want to print the string. In our example, we’ll print the string in a rectangle that’s as wide as the ListBox control and as tall as needed to fit the entire string. I’m using a height of 200 pixels (enough to fit the longest string that users might throw at the control). Upon return, the MeasureString method sets the members of the SizeF object to the width and height actually required to print the string.
The two members of the SizeF object are then used to set the dimensions of the current item (properties e.ItemWidth and e.ItemHeight). The custom rendering of the current item takes place in the ItemDraw event handler, which is shown in Listing 8.16. The Bounds property of the handler’s e argument reports the dimensions of the item’s cell as you calculated them in the MeasureItem event handler.
Listing 8.16: Drawing an Item in an Owner-Drawn ListBox Control
Code language: PHP (php)
Private Sub ListBox1_DrawItem(ByVal sender As Object, _ ByVal e As System.Windows.Forms.DrawItemEventArgs) _ Handles ListBox1.DrawItem If e.Index = -1 Then Exit Sub Dim txtBrush As SolidBrush Dim bgBrush As SolidBrush Dim txtfnt As Font If e.Index / 2 = CInt(e.Index / 2) Then ' color even numbered items txtBrush = New SolidBrush(Color.Blue) bgBrush = New SolidBrush(Color.LightYellow) Else ' color odd numbered items txtBrush = New SolidBrush(Color.Blue) bgBrush = New SolidBrush(Color.Cyan) End If If e.State And DrawItemState.Selected Then ' use red color and bold for the selected item txtBrush = New SolidBrush(Color.Red) txtfnt = New Font(fnt.Name, fnt.Size, FontStyle.Bold) Else txtfnt = fnt End If e.Graphics.FillRectangle(bgBrush, e.Bounds) e.Graphics.DrawRectangle(Pens.Black, e.Bounds) Dim R As New RectangleF(e.Bounds.X, e.Bounds.Y, _ e.Bounds.Width, e.Bounds.Height) e.Graphics.DrawString(items(e.Index).ToString, txtfnt, txtBrush, R) e.DrawFocusRectangle() End Sub
To test the custom-drawn ListBox control, place two buttons on the form, as shown in Figure 8.8. The Add New Item button prompts the user for a new item (a string) and adds it to the control’s Items collection. Listing 8.17 shows the code that adds a new item to the list.
Listing 8.17: Adding an Item to the List at Runtime
Code language: PHP (php)
Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Dim newItem As String newItem = InputBox("Enter item to add to the list") items.Add(newItem) ListBox1.Items.Add("") End Sub