Question ListBox DrawItem event

gchq

Well-known member
Joined
Dec 14, 2007
Messages
168
Programming Experience
10+
Hi there

I'm attempting to redraw the DisplayMember to red if certain criteria are met

So far I have (using a dynamic form, and a dynamic panel)

VB.NET:
Dim RevenueNominalLB As New ListBox
        With RevenueNominalLB
            .Name = "RevenueNominalLB"
            .Size = New Point(200, 100)
            .Location = New Point(345, 60)
            .DrawMode = DrawMode.OwnerDrawFixed
        End With
        AddHandler RevenueNominalLB.DrawItem, AddressOf listBox_DrawItem
        vPanel.Controls.Add(RevenueNominalLB)

The list box is populated via a DataTable like this

VB.NET:
Private Sub RevenueNominalAddNew_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim RevenueLB As ListBox = RFC(ReportForm, "RevenueListBox")
        If RevenueLB.SelectedIndex = -1 Then
            AppBox.Show("You must select a category, from the box on the left first!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
            Exit Sub
        End If
        NominalDataPopup()
        If Not GenCode = 0 Then
            
            Dim CatID As Integer = RevenueLB.SelectedValue
            Dim NomCode As Integer = GenCode
            Dim NomName As String = GenName
            Dim vRows As Integer = RevenueNomDT.Rows.Count
            Dim vLastInt As Integer = 0
            If vRows > 0 Then
                vLastInt = RevenueNomDT.Compute("MAX(Position)", Nothing) + 1
            End If
            For Each Column As DataColumn In RevenueNomDT.PrimaryKey
                Column.AutoIncrement = True
                Column.AutoIncrementStep = 1
                Column.AutoIncrementSeed = vLastInt + 1
            Next
            Dim RevenueNomCB As CheckBox = RFC(ReportForm, "RevenueNomCB")
            Dim vNegValue As Integer = 0
            If RevenueNomCB.Checked = True Then
                vNegValue = 1
            End If
            Dim vNewRow As DataRow = RevenueNomDT.NewRow
            vNewRow("CatID") = CatID
            vNewRow("NomName") = GenName
            vNewRow("NomCode") = GenCode
            vNewRow("Position") = vLastInt
            vNewRow("NegValue") = vNegValue
            RevenueNomDT.Rows.Add(vNewRow)
            Dim vDV As New DataView(RevenueNomDT)
            vDV.Sort = "Position"
            RevenueNomDT = vDV.ToTable
            vDV = Nothing
            Dim RevenueNomLB As ListBox = RFC(ReportForm, "RevenueNominalLB")
            RevenueNomLB.DataSource = RevenueNomDT
            RevenueNomLB.DisplayMember = "NomName"
            RevenueNomLB.ValueMember = "ID"
            RevenueNomCB.Checked = False

        End If
 End Sub

... and the DrawItem event is called like this

VB.NET:
 Private Sub listBox_DrawItem(ByVal sender As Object, ByVal e As DrawItemEventArgs)
        e.DrawBackground()
        Dim myBrush As Brush = Brushes.Black
        For Each row As DataRow In RevenueNomDT.Rows
            Dim vNeg As Integer = row("NegValue")
            Dim vPos As Integer = row("Position")
            If vNeg = 1 Then
                Select Case e.Index
                    Case vPos
                        myBrush = Brushes.Red
                End Select
            End If
            e.Graphics.DrawString(sender.Items(e.Index).ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault)
            e.DrawFocusRectangle()
        Next
    End Sub

The problems I am having are:-

1. The DisplayMember is showing as 'System.Data.DataRowView'
2. Can't find a way to get the selected row to paint in white not black

Other than that the desired effect of the red row is great - sigh!

Any assistance would be appreciated!
 
VB.NET:
sender.Items(e.Index).ToString()

This is a DataRowView. You need to supply the Item that will be drawn. (Even though you've already specified the display member since you're overwriting it.)

Here's a quick example:

VB.NET:
    Dim ds As New DataSet
    Dim dt As New DataTable("Sample")

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Me.ListBox1.DrawMode = DrawMode.OwnerDrawFixed
        FillDt()
        AddHandler ListBox1.DrawItem, AddressOf ListBox1_DrawItem

        With ListBox1
            .DataSource = dt
            .DisplayMember = "Position"
            .ValueMember = "ID"
        End With

    End Sub

    Private Sub FillDt()
        With dt.Columns
            .Add("ID", GetType(Integer)).AutoIncrement = True
            .Add("NegValue", GetType(Integer))
            .Add("Position", GetType(Integer))
        End With

        With dt.Rows
            .Add(Nothing, 1, 2)
            .Add(Nothing, 2, 2)
            .Add(Nothing, 1, 1)
            .Add(Nothing, 1, 3)
            .Add(Nothing, 3, 1)
        End With
    End Sub

    Private Sub ListBox1_DrawItem(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles ListBox1.DrawItem
        e.DrawBackground()
        Dim myBrush As Brush = Brushes.Black
        If CType(sender, ListBox).Items(e.Index).Item("NegValue") = 1 Then
            myBrush = Brushes.Red
        End If
        e.Graphics.DrawString(CType(sender, ListBox).Items(e.Index).Item("NegValue"), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault)
        e.DrawFocusRectangle()
    End Sub
 
OK - it makes sense, but I'm still lost as to how I get the item value back into the loop and repaint it! I've tried all sorts....

Forget the above - didn't see the last part of your message - scollbar was out of view!

That works fine now, thank you very much..

The only question remaining is how to set the right colour for the selected item as, at the moment, they can't be read
 
Last edited:
The only question remaining is how to set the right colour for the selected item as, at the moment, they can't be read
Since you require a special foreground color for some items, the change must be made to the background. It would be non-consistent and confusing to user to suddenly change the appearance of these special items just because they are selected. The DrawBackground call draws the default background both for selected and not selected items, and this is what you have to modify.

To draw a custom background use the FillRectangle method, you only need two parameters here; the brush and the rectangle. For the brush select a color that goes well with both the default blacks and the special reds, it can be any color you fancy. The rectangle you already know from e.Bounds.

When is an item selected? It is selected when its state is Selected. Is it really that simple? Yes and no. e.State will give you the state of the item. The value is type DrawItemState which is a flags enum. This means the state can be a combination of several values, such values you must handle using bitwise operations. The code example below shows how to check if Selected flag is one of the current states of that item.

This code sample fills a light-blue background for selected items, and use default background for others:
VB.NET:
If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
    e.Graphics.FillRectangle(Brushes.LightBlue, e.Bounds)
Else
    e.DrawBackground()
End If
 
John

Thanks for that - I was attempting to keep the existing format as there are eight ListBoxs on the same form!

I did try

VB.NET:
Dim vSelected As Integer = CType(sender, ListBox).SelectedIndex

and

VB.NET:
 Case vSelected
        myBrush = Brushes.White

That, of course, retained the same colour even when not selected. A combination of your suggestion, and the above, cracked it!

VB.NET:
 If (e.State And DrawItemState.Selected) = DrawItemState.Selected Then
                myBrush = Brushes.White
            End If

Thank you both for your help
 
Back
Top