How to: Create an Owner-Drawn List Box

[This documentation is for preview only, and is subject to change in later releases. Blank topics are included as placeholders.]

You can create an owner-drawn list box with the .NET Compact Framework, although not as easily as with the full .NET Framework. The .NET Compact Framework does not support the DrawMode, DrawItem, and other drawing members for list box and other controls, but you can program that functionality. This example provides a custom control class to create an owner-drawn list box, and implements the control as a list box for selecting fonts.

This sample contains a base class, OwnerDrawnListBox, from which the FontListBox is derived. The OwnerDrawnListBox core functions include the following:

  • Provides the vertical scroll bar, an array for containing the items, and a bitmap for constructing the list box.

  • Provides an item height property and determines how many items can be drawn given the height of the control.

  • Provides a SelectedIndex property, which determines the selected item by current scroll bar, item height, and mouse pointer coordinate values.

  • Provides a SelectedIndexChanged event.

  • Ensures the visibility of items.

  • Removes the scroll bar, if not needed.

  • Provides keyboard operation with the up and down navigation keys.

The FontListBox class inherits from OwnerDrawnListBox. The implementations and techniques include the following:

  • Determines the proper item height.

  • Draws the items, with the selected item drawn as highlighted, using an overridden OnPaint method. To prevent flicker, this method does not display the control until all its graphical features are drawn.

  • Provides the SelectedFaceName property, containing the name of the selected font.

The form, DrawFontList, does the following:

  • Creates a new instance of FontListBox, and adds it to the controls collection.

  • Adds an event handler for the SelectedIndexChanged event.

  • Populates the FontListBox with a list of fonts.

  • Provides labels for displaying the sample of the selected font.

To create an owner drawn list box

  1. Add the OwnerDrawnListBox custom control class to your project.

    ' Base custom control for DrawFontList
    Class OwnerDrawnListBox
        Inherits Control
        Private Const SCROLL_WIDTH As Integer = 20
        Private itemH As Integer = -1
        Private selIndex As Integer = -1
        Private offScreenBitmap As Bitmap
        Private vs As VScrollBar
        Private itemsAList As ArrayList
        Public Sub New()
            Me.vs = New VScrollBar
            Me.vs.Parent = Me
            Me.vs.Visible = False
            Me.vs.SmallChange = 1
            AddHandler Me.vs.ValueChanged, AddressOf Me.ScrollValueChanged
            Me.itemsAList = New ArrayList
        End Sub
        Public ReadOnly Property Items() As ArrayList
                Return Me.itemsAList
            End Get
        End Property
        Protected ReadOnly Property OffScreen() As Bitmap
                Return Me.offScreenBitmap
            End Get
        End Property
        Protected ReadOnly Property VScrollBar() As VScrollBar
                Return Me.vs
            End Get
        End Property
        Friend Event SelectedIndexChanged As EventHandler
        Protected Overridable Sub OnSelectedIndexChanged(ByVal e As EventArgs)
            RaiseEvent SelectedIndexChanged(Me, e)
        End Sub
        Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
            Me.SelectedIndex = Me.vs.Value + e.Y / Me.ItemHeight
            ' Invalidate the control so we can draw the item as selected.
        End Sub
        ' Get or set index of selected item.
        Public Property SelectedIndex() As Integer
                Return Me.selIndex
            End Get
            Set(ByVal Value As Integer)
                Me.selIndex = Value
                RaiseEvent SelectedIndexChanged(Me, Nothing)
            End Set
        End Property
        Protected Sub ScrollValueChanged(ByVal o As Object, ByVal e As EventArgs)
        End Sub
        Protected Overridable Property ItemHeight() As Integer
                Return Me.itemH
            End Get
            Set(ByVal Value As Integer)
                Me.itemH = Value
            End Set
        End Property
        ' If the requested index is before the first visible index then set the
        ' first item to be the requested index. If it is after the last visible
        ' index, then set the last visible index to be the requested index.
        Public Sub EnsureVisible(ByVal index As Integer)
            If index < Me.vs.Value Then
                Me.vs.Value = index
            ElseIf index >= Me.vs.Value + Me.DrawCount Then
                Me.vs.Value = index - Me.DrawCount + 1
            End If
        End Sub
        ' Need to set focus to the control when it 
        ' is clicked so that keyboard events occur.
        Protected Overrides Sub OnClick(ByVal e As EventArgs)
        End Sub
        ' Selected item moves when you use the keyboard up/down keys.
        Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
            Select Case e.KeyCode
                Case Keys.Down
                    If Me.SelectedIndex < Me.vs.Maximum Then
                        Me.SelectedIndex = Me.SelectedIndex + 1
                        EnsureVisible(Me.SelectedIndex + 1)
                    End If
                Case Keys.Up
                    If Me.SelectedIndex > Me.vs.Minimum Then
                        Me.SelectedIndex = Me.SelectedIndex - 1
                        EnsureVisible(Me.SelectedIndex - 1)
                    End If
                Case Keys.PageDown
                    Me.SelectedIndex = Math.Min(Me.vs.Maximum, Me.SelectedIndex + Me.DrawCount)
                Case Keys.PageUp
                    Me.SelectedIndex = Math.Max(Me.vs.Minimum, Me.SelectedIndex - Me.DrawCount)
                Case Keys.Home
                    Me.SelectedIndex = 0
                Case Keys.End
                    Me.SelectedIndex = Me.itemsAList.Count - 1
            End Select
        End Sub
        ' Calculate how many items we can draw given the height of the control.
        Protected ReadOnly Property DrawCount() As Integer
                If Me.vs.Value + Me.vs.LargeChange > Me.vs.Maximum Then
                    Return Me.vs.Maximum - Me.vs.Value + 1
                    Return Me.vs.LargeChange
                End If
            End Get
        End Property
        Protected Overrides Sub OnResize(ByVal e As EventArgs)
            Dim viewableItemCount As Integer = Me.ClientSize.Height / Me.ItemHeight
            Me.vs.Bounds = New Rectangle(Me.ClientSize.Width - SCROLL_WIDTH, 0, SCROLL_WIDTH, Me.ClientSize.Height)
            ' Determine if scrollbars are needed
            If Me.itemsAList.Count > viewableItemCount Then
                Me.vs.Visible = True
                Me.vs.LargeChange = viewableItemCount
                Me.offScreenBitmap = New Bitmap(Me.ClientSize.Width - SCROLL_WIDTH - 1, Me.ClientSize.Height - 2)
                Me.vs.Visible = False
                Me.vs.LargeChange = Me.itemsAList.Count
                Me.offScreenBitmap = New Bitmap(Me.ClientSize.Width - 1, Me.ClientSize.Height - 2)
            End If
            Me.vs.Maximum = Me.itemsAList.Count - 1
        End Sub
    End Class
  2. Add the FontListBox class (which is an implementation of OwnerDrawnListBox) to your project.

    ' Derive an implementation of the
    ' OwnerDrawnListBox class
    Class FontListBox
        Inherits OwnerDrawnListBox
        Private Const FONT_SIZE As Integer = 10
        Private Const DRAW_OFFSET As Integer = 5
        Public Sub New()
            ' Determine what the item height should be
            ' by adding 30% padding after measuring
            ' the letter A with the selected font.
            Dim g As Graphics = Me.CreateGraphics()
            Me.ItemHeight = Fix(g.MeasureString("A", Me.Font).Height * 1.3)
        End Sub
        ' Return the name of the selected font.
        Public ReadOnly Property SelectedFaceName() As String
                Return CStr(Me.Items(Me.SelectedIndex))
            End Get
        End Property
        ' Determine what the text color should be
        ' for the selected item drawn as highlighted
        Function CalcTextColor(ByVal backgroundColor As Color) As Color
            If backgroundColor.Equals(Color.Empty) Then
                Return Color.Black
            End If
            Dim sum As Integer = backgroundColor.R + backgroundColor.G + backgroundColor.B
            If sum > 256 Then
                Return Color.Black
                Return Color.White
            End If
        End Function
        Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
            Dim font As Font
            Dim fontColor As Color
            ' The base class contains a bitmap, offScreen, for constructing
            ' the control and is rendered when all items are populated.
            ' This technique prevents flicker.
            Dim gOffScreen As Graphics = Graphics.FromImage(Me.OffScreen)
            gOffScreen.FillRectangle(New SolidBrush(Me.BackColor), Me.ClientRectangle)
            Dim itemTop As Integer = 0
            ' Draw the fonts in the list.
            Dim n As Integer
            For n = Me.VScrollBar.Value To (Me.VScrollBar.Value + DrawCount) - 1
                ' If the font name contains "dings" it needs to be displayed
                ' in the list box with a readable font with the default font.
                If CStr(Me.Items(n)).ToLower().IndexOf("dings") <> -1 Then
                    font = New Font(Me.Font.Name, FONT_SIZE, FontStyle.Regular)
                    font = New Font(CStr(Me.Items(n)), FONT_SIZE, FontStyle.Regular)
                End If
                ' Draw the selected item to appear highlighted
                If n = Me.SelectedIndex Then
                    gOffScreen.FillRectangle(New SolidBrush(SystemColors.Highlight), 1, itemTop + 1, Me.ClientSize.Width - IIf(Me.VScrollBar.Visible, Me.VScrollBar.Width, 2), Me.ItemHeight)
                    ' If the scroll bar is visible, subtract the scrollbar width
                    ' otherwise subtract 2 for the width of the rectangle
                    fontColor = CalcTextColor(SystemColors.Highlight)
                    fontColor = Me.ForeColor
                End If
                ' Draw the item
                gOffScreen.DrawString(CStr(Me.Items(n)), font, New SolidBrush(fontColor), DRAW_OFFSET, itemTop)
                itemTop += Me.ItemHeight
            Next n
            ' Draw the list box
            e.Graphics.DrawImage(Me.OffScreen, 1, 1)
        End Sub
        ' Draws the external border around the control.
        Protected Overrides Sub OnPaintBackground(ByVal e As PaintEventArgs)
            e.Graphics.DrawRectangle(New Pen(Color.Black), 0, 0, Me.ClientSize.Width - 1, Me.ClientSize.Height - 1)
        End Sub
    End Class
  3. In the form, add two Label controls and name them descLabel and sampleLabel; the latter will show a font example selected from FontListBox.

  4. Create an instance of FontListBox for the form.

  5. Add the following code to the constructor of the Load event for form.

    ' Create a new instance of FontListBox.
    myListBox.Parent = Me
    ' Configure the event handler for the SelectedIndexChanged event. 
    AddHandler myListBox.SelectedIndexChanged, AddressOf Me.SelectedIndexChanged
    ' Draw the bounds of the FontListBox.
    myListBox.Bounds = New Rectangle(5, 5, 150, 100)
    ' Add fonts to list, repeat the list 4 times
    ' so that the scroll bar can be demonstrated.
    Dim n As Integer
    For n = 0 To 3
        myListBox.Items.Add("Courier New")
        myListBox.Items.Add("Frutiger Linotype")
    Next n
    ' Labels to show the selected font from the list box. Assumes
    ' instances of these labels have been declared for the form.
    descLabel.Parent = Me
    descLabel.Text = "Font Sample:"
    descLabel.Bounds = New Rectangle(10, myListBox.Bottom + 20, Me.ClientSize.Width - 10, 30)
    ' Assumes an instance sampleLabel is declared for the form.
    sampleLabel.Parent = Me
    sampleLabel.Bounds = New Rectangle(10, descLabel.Bottom, Me.ClientSize.Width - 10, 30)
    sampleLabel.Text = "AaBbCc 123"
  6. Add code for the SelectedChangedEvent event of the FontListBox control, which displays the selected font in the sampleLabel control.

    ' Event handler for SelectedIndexChanged to
    ' display a sample of the selected font.
    ' Assumes that an instace of myListBox has been declared for the form.
    Friend Sub SelectedIndexChanged(ByVal o As Object, ByVal e As EventArgs)
        sampleLabel.Font = New Font(myListBox.SelectedFaceName, 12, FontStyle.Regular)
    End Sub

This example requires references to the following namespaces: