Click here to Skip to main content
14,970,219 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Does anyone know why the Listbox1.Refresh() command may not trigger the ListBox1_DrawItem sub every time?

In Microsoft Visual Basic 2010, a listbox has a forcolor and backcolor property. These properties change the forcolour and backcolor for all the items in the listbox. By default there is no property for the forecolor and backcolor of an individual item on a listbox, I am aware there is on a list view but I would still wish to use a listbox. I am trying to have the ability to change the forecolor and backcolor properties of individual items in the listbox. To do this the listbox's draw item sub must be used with the listbox's drawmode property set to OwnerDrawFixed. Then using a brush colour along with the e.graphics the forecolor or backcolor can be changed. I have seen and followed examples of how to do this for the currently selected item. Such as the one from ehow's website. What I am tiring to do however is change the colour of the litsbox item as it is added depending on a variable.

Here is my code:
VB
Private Sub listbox_add()

        Me.ListBox1.Items.Add(listbox_text(list_num)) ' adds the line to the list box
        add_item_colour = True
        ListBox1.Refresh()

End Sub



    Private Sub ListBox1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles ListBox1.DrawItem
    Dim myBrush As Brush = Brushes.Black

    e.DrawBackground()
    If add_item_colour = True Then
        If blue_message = True Then
            myBrush = Brushes.Blue
        Else
            myBrush = Brushes.Black
        End If
        e.Graphics.DrawString(ListBox1.Items.Item(list_num), ListBox1.Font, myBrush, _
        New RectangleF(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height))
        add_item_colour = False
    End If
    e.DrawFocusRectangle()
End Sub


The listbox_text is a string array that stores the string being added, the list_num is a integer that increments when new items are added to the listbox and the blue_message is a Boolean that it true when I want a blue message and false when I don't.

The problem I seem to be having is that Listbox1.Refresh() command does not seem to be triggering the ListBox1_DrawItem sub every time it is called. I found this by using brake points. Does anyone know why this might be the case and how I could fix it?

Thanks, any help on this would be much appreciated.
Posted
Updated 8-Sep-20 2:09am
Comments
Richard C Bishop 3-Sep-13 10:24am
   
Why do you think calling the .Refresh() method will run a different method?
Member 10190163 3-Sep-13 10:30am
   
Because I was under the impression that the refresh would force the listbox to be re-drawn, meaning the ListBox1_DrawItem sub would be called. Is this not the case?
Richard C Bishop 3-Sep-13 11:36am
   
It does redraw itself. Right click on the .Refresh() method and "Go to Definition". That is the code it runs.

1 solution

Successful Example - Revised 5 Sep 2013 10:57 EDT to include RemoveAt
The following code works.
I tested using Visual Studio 2012 on a Windows 8 PC.

By using the Visual Studio Interactive Debugger, I determined that all visible rows in the ListBox are refreshed when a new row is added and that if a new row is not visible, it is not drawn. Therefore, I had to save the brush color for each row in a collection before adding the new row and use that brush color each time the associated ListBox row is drawn. I use Collection Class to save the brush colors for each row of the ListBox.

The Refresh method was not needed.

VB
Dim colColors As Collection = New Collection ' Space to save brush colors for each row in the ListBox

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    ListBox1.DrawMode = DrawMode.OwnerDrawFixed
End Sub

Private Sub listbox_add_Blue()
    SetColorForNewRow(Brushes.Blue)
    ListBox1.Items.Add("Blue") ' adds the line to the list box
End Sub

Private Sub listbox_add_Black()
    SetColorForNewRow(Brushes.Black)
    ListBox1.Items.Add("Black") ' adds the line to the list box
End Sub

Sub listbox_remove_0()
    If ListBox1.Items.Count < 1 Then Exit Sub
    ' Must remove brush color from colColors collection first!
    colColors.Remove(1) ' Collection index starts at 1
    ListBox1.Items.RemoveAt(0)
End Sub

Private Sub ListBox1_DrawItem(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles ListBox1.DrawItem
    e.DrawBackground()
    If e.Index >= 0 AndAlso e.Index < ListBox1.Items.Count Then
        e.Graphics.DrawString(ListBox1.Items(e.Index).ToString(), _
            e.Font, colColors.Item(1 + e.Index), _
            e.Bounds, StringFormat.GenericDefault)
    End If
    e.DrawFocusRectangle()
End Sub

Sub SetColorForNewRow(ByVal MyBrush As Brush)
    colColors.Add(MyBrush)
End Sub

Private Sub btnBlack_Click(sender As Object, e As EventArgs) Handles btnBlack.Click
    listbox_add_Black()
End Sub

Private Sub btnBlue_Click(sender As Object, e As EventArgs) Handles btnBlue.Click
    listbox_add_Blue()
End Sub

Private Sub btnRemove_Click(sender As Object, e As EventArgs) Handles btnRemove.Click
    listbox_remove_0()
End Sub




_____________________________________________________________________________________________
First attempt using an array to store brush colors (Partial Success)
The following code works for small number of items in the ListBox. I tested using Visual Studio 2012 on a Windows 8 PC. I am still studying how to handle the DrawItem event when the vertical scroll bar is active.

By using the Visual Studio Interactive Debugger, I determined that all rows in the ListBox are refreshed when a new row is added. Therefore, I had to save the brush color for each row in an array and use that brush color each time the associated ListBox row is redrawn. The Refresh method was not needed.

This would have been a lot easier with a ListView control.

VB
Dim blue_message As Boolean = False ' Use Black or Blue Brush
Dim AddMode As Boolean = False  ' Tells DrawItem that it is in Add to ListBox mode
Dim intNewIndex As Integer ' The index of the new item to be added to the ListBox
Dim arrColors(0 To 100) As Brush ' Space to save brush colors for each row in the ListBox

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    ListBox1.DrawMode = DrawMode.OwnerDrawFixed
End Sub

Private Sub listbox_add_Blue()
    blue_message = True
    AddMode = True
    intNewIndex = ListBox1.Items.Count
    ListBox1.Items.Add("Blue") ' adds the line to the list box
End Sub

Private Sub listbox_add_Black()
    blue_message = False
    AddMode = True
    intNewIndex = ListBox1.Items.Count
    ListBox1.Items.Add("Black") ' adds the line to the list box
End Sub

Private Sub ListBox1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles ListBox1.DrawItem
    Dim myBrush As Brush = Brushes.Black
    e.DrawBackground()
    If AddMode AndAlso e.Index = intNewIndex Then
        If blue_message Then
            myBrush = Brushes.Blue
        End If
        e.Graphics.DrawString(ListBox1.Items(e.Index).ToString(), e.Font, myBrush, _
            e.Bounds, StringFormat.GenericDefault)
        If e.Index > arrColors.GetUpperBound(0) Then ' Is the array filled
            ReDim Preserve arrColors(0 To arrColors.Count + 100) ' Make some more room
        End If
        arrColors(e.Index) = myBrush ' Save which brush we used
        AddMode = False ' Finished drawing the new item (It is the last row of the ListBox
    Else
        e.Graphics.DrawString(ListBox1.Items(e.Index).ToString(), _
            e.Font, arrColors(e.Index), _
            e.Bounds, StringFormat.GenericDefault)
    End If
    e.DrawFocusRectangle()
End Sub

Private Sub btnBlack_Click(sender As Object, e As EventArgs) Handles btnBlack.Click
    listbox_add_Black()
End Sub

Private Sub btnBlue_Click(sender As Object, e As EventArgs) Handles btnBlue.Click
    listbox_add_Blue()
End Sub



_____________________________________________________________________________________________
Original Version of the Solution
I looked at the Microsoft Help page for ListBox.DrawItem event and made a few changes to e.Graphics.DrawString statement. Also, notice that I moved the location of add_item_colour = True.

I did not test.

VB
Private Sub listbox_add()
        add_item_colour = True
        Me.ListBox1.Items.Add(listbox_text(list_num)) ' adds the line to the list box
        ListBox1.Refresh()
End Sub

    Private Sub ListBox1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles ListBox1.DrawItem
    Dim myBrush As Brush = Brushes.Black

    e.DrawBackground()
    If add_item_colour = True Then
        If blue_message = True Then
            myBrush = Brushes.Blue
        Else
            myBrush = Brushes.Black
        End If
        e.Graphics.DrawString(ListBox1.Items(e.Index).ToString(), e.Font, myBrush, _
            e.Bounds, StringFormat.GenericDefault)
        add_item_colour = False
    End If
    e.DrawFocusRectangle()
End Sub
   
v25
Comments
Member 10190163 4-Sep-13 4:51am
   
Thanks for the suggestion, but unfortunately it did not work.
I got an exception every time something was added: A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
Also I saw some black and blue items being added, but the problem was it only added them to the first item then they were cleared.
Member 10190163 4-Sep-13 4:53am
   
I have also tied it with this line: e.Graphics.DrawString(ListBox1.Items.Item(list_num).ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault)
just in-case this fixes the problem of it being added only to the first item and cleared, but again I had the same problem.
Member 10190163 4-Sep-13 4:56am
   
Also if you are going to test it, you need to remember to change the draw mode property on the listbox.
Mike Meinz 4-Sep-13 9:35am
   
See revised Solution 1. It took me two tries to get a version of the solution that work correctly. My first version did not work correctly when there was a vertical scroll bar present. See the comments within Solution 1.
Member 10190163 4-Sep-13 11:45am
   
I am using Windows 7 and Microsoft Visual Basic 2010 Express. I have created a new project and tried to apply your code to this. I have a problem with the line:
e.Graphics.DrawString(ListBox1.Items(e.Index).ToString(), e.Font, arrColors(e.Index), e.Bounds, StringFormat.GenericDefault)
It causes an exception when press run and I can not continue with the program because it brings up a dialog box that lets me brake or continue and when I continue the debugger stops. The dialog box messages is as follows: An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in System.Windows.Forms.dll
Additional information: InvalidArgument=Value of '-1' is not valid for 'index'.
Mike Meinz 4-Sep-13 12:27pm
   
I modified ListBox1_DrawItem in Solution 1. I put an If statement around the e.Graphics.DrawString statement to ensure that it is executed only when e.Index > -1.

Please try this change.
Member 10190163 5-Sep-13 9:47am
   
That's quite an obvious solution when you look at the error message, I should have been able to do that. Thanks for the code, you have been a grate help. I have tested this now on my system and it works well. I am having a problem implementing it into my other code that I was working with as the blue and black messages seem out of sync, this is probably an issue with timing. But as that is a separate issue it will probably be best if I open that as a new question, so thanks for your help you have solved my issue. Well my first issue anyway.
Mike Meinz 5-Sep-13 10:09am
   
I don't think timing is the cause of your issue.
Mike Meinz 5-Sep-13 10:11am
   
If you post this new issue as a new question, please reply to this message with the URL of the new question.
Member 10190163 5-Sep-13 10:23am
   
Your right its not to do with timing, the problem is that later on in my code I limit the number of listbox entries with this line: Me.ListBox1.Items.Remove(listbox_text(0)) ' take off the top list box entry
and that will mean the ListBox1_DrawItem sub will be called but a new item is not being added to it, so I think that's why it messes up.
Mike Meinz 5-Sep-13 10:27am
   
If you are removing items from the ListBox, then you have to make the same change to the arrColors array that contains the brush colors. The arrColors array has to stay in sync with the ListBox. If you are going to be removing rows from the ListBox, it might be easier to use a Collection instead of an array to store the brush colors.
Member 10190163 5-Sep-13 11:13am
   
Alright thanks,
I have uped the array to 0 to 550, since that is the maximum value
and I have fixed it now with
For counter = 0 To 549
arrColors(counter) = arrColors(counter + 1)
Next
just before the: Me.ListBox1.Items.Remove(listbox_text(0))
line
I haven't used a collection though.
Mike Meinz 5-Sep-13 11:16am
   
See the revised Solution 1.
The revision uses a Collection and provide a listbox_remove_0 for you.
Mike Meinz 5-Sep-13 10:59am
   
I modified the example in Solution 1 so that it includes a listbox_remove_0() and uses a Collection instead of an array to save the brush colors.
Member 10190163 5-Sep-13 11:15am
   
Ow sorry, I didn't see this reply before. I will have a another look at solution 1.
Mike Meinz 5-Sep-13 12:28pm
   
Here is a ListView example that does the same thing. Much simpler!


Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
ListView1.View = View.List
End Sub


Private Sub listview_add(ByVal strText As String, ByVal MyColor As Color)
Dim lstRow As New ListViewItem(strText)
lstRow.ForeColor = MyColor
ListView1.Items.Add(lstRow)
End Sub

Private Sub listview_Remove0()
If ListView1.Items.Count < 1 Then Exit Sub
ListView1.Items.RemoveAt(0)
End Sub

Private Sub btnBlackListView_Click(sender As Object, e As EventArgs) Handles btnBlackListView.Click
listview_add("Black", Color.Black)
End Sub

Private Sub btnBlueListView_Click(sender As Object, e As EventArgs) Handles btnBlueListView.Click
listview_add("Blue", Color.Blue)
End Sub

Private Sub btnRemoveListView_Click(sender As Object, e As EventArgs) Handles btnRemoveListView.Click
listview_Remove0()
End Sub
Member 10190163 6-Sep-13 9:12am
   
I put your solution with the listbox_remove_0 into a vb project and it works, however I am still having sync issues when I implement it into my other code. One of those issues was because I cleared the listbox with listbox1.items.clear(), but I have fixed this now by clearing the collection with colColors.Clear(). But I still have other problems, because it is still getting out of sync.
Also I have tried using a listview but it does not accomplish what I am trying to achieve do to columns.
Mike Meinz 6-Sep-13 9:21am
   
You must be deleting or adding rows to the ListBox somewhere else in your program.

I don't understand your comment about ListView and columns. ListView can do columns when the View Property is set to View.Details. When the View Property is set to View.List, ListView acts like ListBox (i.e. No Columns). What are you trying to do that you think ListView can't do? You're using up a lot of time trying to make ListBox work when ListView should be able to handle your requirements easily.
Member 10190163 6-Sep-13 9:48am
   
OK I have fixed it now, it was because I do a check to see if the thread is being aborted before I add or remove a item from the listbox, but I wasn't doing this check before adding or removing an item from the collection. It works now.
Thanks for your help on this issue though.
Mike Meinz 6-Sep-13 10:53am
   
I still wonder why you couldn't use a ListView control with the View property set to View.List.
Mike Meinz 7-Sep-13 5:53am
   
I noticed that you have not yet accepted the Solution so that th9is question will be marked as Solved.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900