Friday, September 10, 2010 Register    Login
Blog_List
  Print    

Blog_Archive
Archive
<September 2010>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
Monthly
May, 2009
  Print    

Search_Blog
  Print    

VB.Net Creating a ListBox with a Gradient Background
Location: BlogsVB.Net Code ExamplesWinforms VB.Net Development    
Posted by: Ibrahim 5/25/2009 10:47 AM

How to Create Listbox with a Gradient Background in VB.Net

The Problem Definition:

A person on the MSDN Forum asked about adding a Gradient background to a list box.  At first glance I figured I could do it from the DrawItem event handler. I actually presented code to the user that did, in fact, create a gradient background on the Listbox as the user had asked for. But it was plagued with redraw flicker and ghosts on some resize events.   As I further looked at and played with the resulting code that I had offered to the person I realized that I had taken the easy (read lazy) way and hd provided code that was way less than professional.  Hey, it was free advice!

But I couldn’t leave the issue alone so I decided to work this out into a professional solution with no flkickers and no ghosts. What follows is the result of my efforts to provide a quality solution to the MSDN requester.

The Solution

The solution turned out to require sub-classing the Listbox control and then overriding some of the methods and events and adding some custom properties.

 

 CustomGradientListBox.bmp

Drawing the Background.

Drawing a Gradient background was a simple exercise in getting the client rectangle and filling it with a LinearGradientBrush.  I added to properties to the sub-classed control named GradientColor1 and GradientColor2.  This allowed the user of the control to select the colors to be used in the creation of the LinearGradientBrush.  In the Set part of the property methods I added a call to the paint background so that the changes to the gradient colors would take effect immediately including during design time. Redrawing the background needs to be done during the Paint event, Resize event as well as on the OnPaintBackGround event.

 

    Protected Overrides Sub OnCreateControl()

 

        MyBase.OnCreateControl()

        Me.DrawMode = Windows.Forms.DrawMode.OwnerDrawVariable

        Me.EnableDoubleBuffering()

        Me._BackGroundBrush = New LinearGradientBrush(New Point(0, 0), _

                                                      New Point(ClientRectangle.Width, 0), _

                                                      Me._GradientColor1, _

                                                      Me._GradientColor2)

 

    End Sub

 

The DrawMode needs to be set to OwnerDrawVariable. I am using OwnerDrawVariable instead of OwnerDrawFixed because I intend to allow the height of list items to be variable to accommodate Images, custom fonts and other enhancements. The EnableDoubleBuffering() is a call to a custom method I added to the class that sets up the draw routines to doublebuffer the graphics.  What this means is that a client rectangle is drawn off screen then moved to the control’s client rectangle so that the actual drawing action is not seen thus eliminating almost all flickering. I also create a default background brush for the initialization of the control.

 

Drawing the Items List

So now we have the background getting drawn.  As long as we do not add any items to the ListBoz the background will remain nice and pretty as we intended.  But since we are going to have to add items to the ListBox at some point, we have to take care of the actual drawing of those list Items to the Client Rectangle.  I begin by creating a method that iterates through the Items list and calls the OnDrawItem event for each item passing a new System.Windows.Forms.DrawItemEventArgs object with each call .  while I do supply a default Font and FontWeight in the argument, I actually do not use them in the OnDrawItem event, using instead some custom properties that we will get to later.  Here is that code:

 

    'calls List_DrawItem  for all items in the listbox

    Private Sub PaintItemsList(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)

 

        Dim MyRect As System.Drawing.Rectangle = Nothing

 

        Try

 

            For Icount = 0 To MyBase.Items.Count - 1

 

                MyRect = Me.GetItemRectangle(Icount)

                If e.ClipRectangle.IntersectsWith(MyRect) Then

                    If Me.SelectedItems.Contains(Me.Items(Icount)) _

                    Or Me.SelectedItem = (Me.Items(Icount)) Then

                        OnDrawItem(New System.Windows.Forms.DrawItemEventArgs(e.Graphics, _

                                                      New Font("Ariel", 10, FontStyle.Regular), _

                                                       MyRect, Icount, DrawItemState.Selected))

                    Else

                        OnDrawItem(New System.Windows.Forms.DrawItemEventArgs(e.Graphics, _

                                                      New Font("Ariel", 10, FontStyle.Regular), _

                                                       MyRect, Icount, DrawItemState.Default))

 

                    End If

 

                End If

            Next

 

        Catch ex As Exception

            Throw ex

        End Try

 

    End Sub

This gets the ball rolling for drawing the items to the client rectangle. In the actual OnDrawItem event method I could have simply done a Graphics.DrawString() call to display the items and a call to DrawFocusRectangle() and I would be done.  But like all programmers who like what they do, I could not avoid doing some Gold Plating of the solution.  What is gold plating?  It is adding features that where not originally called for in the specifications.

Gold Plating, or adding Images, Custom Fonts, and what not….

Looking at the Sub-classed ListBox at this point I came to the conclusion that adding images, selective fonts and font colors for each List Item might be fun. So I added some new properties for a ImageList object, a sorted list of a custom class I called ItemImageID and some housekeeping code to make it all work.  Basically I set it up so that you could add an ImageList to the control and then select an image from that list and associate it with a ListItem. You can also select a Font for that list item, and a forecolor.  So when I was all done, the OnDrawItem event method ending up looking  like this:

 

       'draws each item into the client rectangle

    Private Sub List_DrawItem(ByVal sender As Object, _

                              ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles Me.DrawItem

 

        Dim MyImage As Image = Nothing

        'default font...

        Dim MYfont As Font = New Font("Ariel", 12, FontStyle.Regular)

        'default forecolor

        Dim MyBrush As Brush = Brushes.Black

 

        e.Graphics.FillRectangle(Me._BackGroundBrush, e.Bounds)

        Me._BackGroundBrush = New LinearGradientBrush(New Point(0, 0), _

                                                      New Point(ClientRectangle.Width, 0), _

                                                      Me._GradientColor1, Me.GradientColor2)

 

        If Me._ImageIndex.Keys.Contains(e.Index) AndAlso Me._ImageIndex.Item(e.Index) IsNot Nothing Then

 

            If Me._ImageList.Images.Count >= Me._ImageIndex(e.Index).ImageIndex Then

                MyImage = Me._ImageList.Images(Me._ImageIndex(e.Index).ImageIndex)

            ElseIf Me._ImageList.Images.ContainsKey(Me._ImageIndex(e.Index).ImageName) Then

                MyImage = Me._ImageList.Images(Me._ImageIndex(e.Index).ImageName)

            End If

 

            If Me._ImageIndex(e.Index).Font IsNot Nothing Then

                MYfont = Me._ImageIndex(e.Index).Font

            End If

 

            If Me._ImageIndex(e.Index).ForeColor IsNot Nothing Then

                MyBrush = Me._ImageIndex(e.Index).ForeColor

            End If

 

            If MyImage IsNot Nothing Then

                e.Graphics.DrawImage(MyImage, New Point(0, e.Bounds.Y))

            End If

 

        End If

 

        e.Graphics.DrawString(MyBase.Items(e.Index).ToString, _

                 MYfont, MyBrush, _

                e.Bounds.X + 20, e.Bounds.Y)

        e.DrawFocusRectangle()

 

    End Sub

 

That wraps up the article for this Gradient Background Listbox. You can download the code here!

 

Permalink |  Trackback