Question Double Buffering Custom Drawn ListBox Items

Joined
Mar 18, 2012
Messages
22
Programming Experience
5-10
Hi there,
I have a basic and specific-use media player.
I am using a listbox to select the song number to be played.

I decided to not keep the default color that the list items have when they are selected and deselected. I decided to change them to custom colors.
The only thing I want to know now is whether I should utilise double buffering somehow to reduce any possible graphics glitches on slower machines.

Here is my current DrawItemEventArgs code:
[HR][/HR]
 Private Sub lstSongs_DrawItem(sender As Object, e As DrawItemEventArgs)
        Dim g As Graphics = e.Graphics
        Dim br As SolidBrush
        Dim s As String
        Dim sf As New StringFormat


        sf.Alignment = StringAlignment.Center


        Try
            s = lstSongs.Items.Item(e.Index).ToString
        Catch ex As Exception
            s = ""
            'Trace.WriteLine(ex.ToString)
        End Try


        br = New SolidBrush(Color.FromArgb(64, 64, 64))


        g.FillRectangle(br, e.Bounds)


        If lstSongs.Enabled = True Then
            If CBool(e.State And DrawItemState.Selected) Then
                g.FillRectangle(Brushes.Black, e.Bounds)
            End If


            g.DrawString(s, lstSongs.Font, Brushes.White, _
      RectangleF.op_Implicit(e.Bounds), sf)
        Else
            If CBool(e.State And DrawItemState.Selected) Then
                g.FillRectangle(Brushes.White, e.Bounds)
            End If


            g.DrawString(s, lstSongs.Font, Brushes.Black, _
    RectangleF.op_Implicit(e.Bounds), sf)
        End If




        br.Dispose()
    End Sub


[HR][/HR]
Please let me know if you need any further information :)
Thanks!
 

VicJ

Well-known member
Joined
Aug 12, 2008
Messages
79
Programming Experience
5-10
[HR][/HR]
You could if you wanted make a double buffered ListBox by inheriting ListBox and setting DoubleBuffered=True in the constructor. But are you actually getting any flickering? There's nothing in the posted painting code (different brushes, different fonts etc.) that could cause flickering any more than drawing a standard ListBox item. The Try-Catch looks suspect because catching an exception would cause a lag in rendering. But I can't see how that could ever be triggered -- unless you were misguidedly trying to call the sub directly instead of letting the system call it as an event handler, in which case anything could go wrong and double buffering won't help.

VicJ
 
Joined
Mar 18, 2012
Messages
22
Programming Experience
5-10
Hi VicJ and thank you for your comment :)

Yes, I do get flickering. While resizing the form the items would flicker.

With the use of your helpful comment, I used the code that I show below:

' Form code:


    Private Sub dbLstSongs_DrawItem(sender As Object, e As DrawItemEventArgs) Handles dbLstSongs.DrawItem
        Dim g As Graphics = e.Graphics
        Dim br As SolidBrush
        Dim s As String
        Dim sf As New StringFormat


        sf.Alignment = StringAlignment.Center
        s = dbLstSongs.Items.Item(e.Index).ToString


        br = New SolidBrush(Color.FromArgb(40, 40, 40))


        g.FillRectangle(br, e.Bounds)


        If dbLstSongs.Enabled = True Then
            If CBool(e.State And DrawItemState.Selected) Then
                g.FillRectangle(Brushes.White, e.Bounds)
                g.DrawString(s, dbLstSongs.Font, Brushes.Black, RectangleF.op_Implicit(e.Bounds), sf)
            Else
                g.DrawString(s, dbLstSongs.Font, Brushes.White, RectangleF.op_Implicit(e.Bounds), sf)
            End If
        Else
            If CBool(e.State And DrawItemState.Selected) Then
                g.FillRectangle(Brushes.White, e.Bounds)
            End If


            g.DrawString(s, dbLstSongs.Font, Brushes.Black, RectangleF.op_Implicit(e.Bounds), sf)
        End If


        br.Dispose()
    End Sub



' DoubleBufferedListBox control-generating class


Public Class DoubleBufferedListBox
    Inherits ListBox
    Sub New()
        Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
    End Sub
End Class


Unfortunately, even with the double-buffered listbox, I still get flickering when resizing the form :(
 

VicJ

Well-known member
Joined
Aug 12, 2008
Messages
79
Programming Experience
5-10
Unfortunately, even with the double-buffered listbox, I still get flickering when resizing the form :(
I can't see why it happens but try setting DoubleBuffered=True as I suggested. ControlStyles.OptimizedDoubleBuffer only works in combination with another ControlStyle (UserDraw if I remember correctly). But the use of SetStyle statements for this purpose was superseded by the DoubleBuffered property in VB2005.

By the way, if you are customizing an inherited control you should put the custom code in the On.. sub instead of handling the Event directly. Type Protected Overrides Sub OnDrawItem and Visual Studio will generate the empty sub; insert your present code before the automatic call to MyBase.OnDrawItem. This is just a conventional pattern for control customization, so I doubt it will make an observable difference as regards flickering.

VicJ

AFTERTHOUGHT: You could also try setting the Form's DoubleBuffered property to True in the Designer or in Code. But it would be preferable to solve the problem in the control itself, if possible.
 
Last edited:
Joined
Mar 18, 2012
Messages
22
Programming Experience
5-10
Hi,

I set the DoubleBuffered property to true on the form and I merged the two pieces of code into the same class :)

Public Class DoubleBufferedListBox
    Inherits ListBox
    Sub New()
        Me.DoubleBuffered = True
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or
        ControlStyles.ResizeRedraw Or
        ControlStyles.ContainerControl Or
        ControlStyles.OptimizedDoubleBuffer Or
        ControlStyles.SupportsTransparentBackColor, True)
    End Sub


    Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
        Dim g As Graphics = e.Graphics
        Dim br As SolidBrush
        Dim s As String
        Dim sf As New StringFormat


        sf.Alignment = StringAlignment.Center
        s = frmKHSS.dbLstSongs.Items.Item(e.Index).ToString


        br = New SolidBrush(Color.FromArgb(40, 40, 40))


        g.FillRectangle(br, e.Bounds)


        If frmKHSS.dbLstSongs.Enabled = True Then
            If CBool(e.State And DrawItemState.Selected) Then
                g.FillRectangle(Brushes.White, e.Bounds)
                g.DrawString(s, frmKHSS.dbLstSongs.Font, Brushes.Black, RectangleF.op_Implicit(e.Bounds), sf)
            Else
                g.DrawString(s, frmKHSS.dbLstSongs.Font, Brushes.White, RectangleF.op_Implicit(e.Bounds), sf)
            End If
        Else
            If CBool(e.State And DrawItemState.Selected) Then
                g.FillRectangle(Brushes.White, e.Bounds)
            End If


            g.DrawString(s, frmKHSS.dbLstSongs.Font, Brushes.Black, RectangleF.op_Implicit(e.Bounds), sf)
        End If


        br.Dispose()
        MyBase.OnDrawItem(e)
    End Sub
End Class


Unfortunately again, I am getting some very noticeable flickering going on. Is there any other way to approach this other than double buffering?

UPDATE: I located more graphics code and this is what I came up with:
'Main form class

Private _backBuffer As Bitmap


    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        If _backBuffer Is Nothing Then
            _backBuffer = New Bitmap(Me.ClientSize.Width, Me.ClientSize.Height)
        End If


        Dim g As Graphics = Graphics.FromImage(_backBuffer)


        g.Dispose()


        e.Graphics.DrawImageUnscaled(_backBuffer, 0, 0)
    End Sub


    Protected Overrides Sub OnPaintBackground(ByVal pevent As PaintEventArgs)
    End Sub 'Don't allow the background to paint


    Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)
        If Not (_backBuffer Is Nothing) Then
            _backBuffer.Dispose()
            _backBuffer = Nothing
        End If
        MyBase.OnSizeChanged(e)
    End Sub

[HR][/HR]
'ListBox class


Public Class DoubleBufferedListBox
    Inherits ListBox
    Sub New()
        Me.DoubleBuffered = True
    End Sub


    Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
        Dim currentContext As BufferedGraphicsContext = BufferedGraphicsManager.Current
        Dim bG As BufferedGraphics
        Dim newArgs As DrawItemEventArgs
        Dim br As SolidBrush
        Dim s As String
        Dim sf As New StringFormat
        sf.Alignment = StringAlignment.Center


        bG = currentContext.Allocate(e.Graphics, e.Bounds)


        If e.Index < frmKHSS.dbLstSongs.Items.Count AndAlso e.Index > -1 Then
            s = frmKHSS.dbLstSongs.Items.Item(e.Index).ToString
        Else
            s = "null"
        End If


        br = New SolidBrush(Color.Black)


        bG.Graphics.FillRectangle(br, e.Bounds)


        If frmKHSS.dbLstSongs.Enabled = True Then
            If CBool(e.State And DrawItemState.Selected) Then
                bG.Graphics.FillRectangle(Brushes.White, e.Bounds)
                bG.Graphics.DrawString(s, frmKHSS.dbLstSongs.Font, Brushes.Black, RectangleF.op_Implicit(e.Bounds), sf)
            Else
                bG.Graphics.DrawString(s, frmKHSS.dbLstSongs.Font, Brushes.White, RectangleF.op_Implicit(e.Bounds), sf)
            End If
        Else
            If CBool(e.State And DrawItemState.Selected) Then
                bG.Graphics.FillRectangle(Brushes.White, e.Bounds)
            End If


            bG.Graphics.DrawString(s, frmKHSS.dbLstSongs.Font, Brushes.Black, RectangleF.op_Implicit(e.Bounds), sf)
        End If


        br.Dispose()


        bG.Render()
        bG.Render(Me.CreateGraphics)
        bG.Dispose()


        newArgs = New DrawItemEventArgs(bG.Graphics, e.Font, e.Bounds, e.Index, e.State)
        MyBase.OnDrawItem(newArgs)
    End Sub
End Class


[HR][/HR]And guess what, still the flickering remains :\
 
Last edited:

VicJ

Well-known member
Joined
Aug 12, 2008
Messages
79
Programming Experience
5-10
Do you have the listbox anchored or docked so that it resizes whenever you resize the form? I have been doing a few tests and it seems that even a well-filled standard ListBox can flicker a bit when you resize it, and a custom drawn one seems to be even more prone to laggy redrawing. Making the Form double buffered helps a tiny bit, but making the ListBox double buffered doesn't at all! So I have to pull back from my earlier suggestions. Never mind, here's a way that may eliminate flickering caused by resizing the Form. Add this code to the form:

Code:
    Private Sub Form1_ResizeBegin(sender As Object, e As System.EventArgs) Handles Me.ResizeBegin
        Me.SuspendLayout()
    End Sub

    Private Sub Form1_ResizeEnd(sender As Object, e As System.EventArgs) Handles Me.ResizeEnd
        Me.ResumeLayout(True)
    End Sub
The effect is to put off changing the size of any Docked or Anchored controls until you release the mouse. Does it solve your problem?

If you decide to go ahead with making a custom (inherited) ListBox control which you can put in the VS ToolBox, then there are some changes needed. You shouldn't refer to the host form from the control class because that will cause problems when an instance of the control is used on a different form. For example, replace frmKHSSfrmKHSS.dbLstSongs.Font by Me.Font. Similarly, replace frmKHSS.dbLstSongs.Items.Item(e.Index) by Me.Items(e.Index). Then when using an instance of the class, set the control's own Font and Items properties in the Designer or in code. But if you only need it once, you may prefer to go back to coding the DrawItem event handler as in your first post.

That looks like a bold attempt to use BufferedGraphics to implement custom double buffering but I don't think it will work any better than standard double buffering. It could be of some benefit when correctly applied in a multithreading situation, but I have never seen it used convincingly. As to the code you have added to the form in your last post ... yuck:cocksure:!

VicJ
 
Joined
Mar 18, 2012
Messages
22
Programming Experience
5-10
Yes the listbox is docked in a split container.

I believe you lead me into the right direction. By using your code as a base for building on, I have found a solution I am happy with.
After reviewing the effects of your code in the program, I realised that suspending the layout of the entire form wasn't looking as smooth as I would have liked.
I then thought to myself, what about suspending the layout of just the list box?
Upon failing at doing so in the way I thought would work, lstSongs.SuspendLayout() , I then searched for another solution and stumbled upon some nice code which I show below.

[HR][/HR]
'InterfaceHandler class

Imports System.Runtime.CompilerServices


Module InterfaceHandler
    Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    Private Const WM_SETREDRAW As Integer = 11


    ' Extension methods for Control
    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control, ByVal Redraw As Boolean)
        SendMessage(Target.Handle, WM_SETREDRAW, 1, 0)
        If Redraw Then
            Target.Refresh()
        End If
    End Sub


    <Extension()>
    Public Sub SuspendDrawing(ByVal Target As Control)
        SendMessage(Target.Handle, WM_SETREDRAW, 0, 0)
    End Sub


    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control)
        ResumeDrawing(Target, True)
    End Sub

'[code omitted]
End Module


[HR][/HR]Yes, I understand what you meant about replacing lstSongs with Me in the control class, but since I was using it on only one form, there weren't any issues for me to have noticed it.
It's a shame really that the double buffering didn't work :p Maybe next time :)

Here is the updated code:
'Main form class

Private Sub frmMain_ResizeBegin(sender As Object, e As System.EventArgs) Handles Me.ResizeBegin
        InterfaceHandler.SuspendDrawing(lstSongs)
    End Sub


    Private Sub frmMain_ResizeEnd(sender As Object, e As System.EventArgs) Handles Me.ResizeEnd
        InterfaceHandler.ResumeDrawing(lstSongs)
    End Sub


    Private Sub lstSongs_DrawItem(sender As Object, e As DrawItemEventArgs) Handles lstSongs.DrawItem
        Dim g As Graphics = e.Graphics
        Dim br As SolidBrush
        Dim s As String
        Dim sf As New StringFormat


        sf.Alignment = StringAlignment.Center


        If e.Index < lstSongs.Items.Count AndAlso e.Index > -1 Then
            s = lstSongs.Items.Item(e.Index).ToString
        Else
            s = "null"
        End If


        br = New SolidBrush(Color.FromArgb(40, 40, 40))


        g.FillRectangle(br, e.Bounds)


        If lstSongs.Enabled = True Then
            If CBool(e.State And DrawItemState.Selected) Then
                g.FillRectangle(Brushes.White, e.Bounds)
                g.DrawString(s, lstSongs.Font, Brushes.Black, RectangleF.op_Implicit(e.Bounds), sf)
            Else
                g.DrawString(s, lstSongs.Font, Brushes.White, RectangleF.op_Implicit(e.Bounds), sf)
            End If
        Else
            If CBool(e.State And DrawItemState.Selected) Then
                g.FillRectangle(Brushes.White, e.Bounds)
            End If


            g.DrawString(s, lstSongs.Font, Brushes.Black, RectangleF.op_Implicit(e.Bounds), sf)
        End If


        br.Dispose()
    End Sub


[HR][/HR]I am quite pleased with the results, what do you think? :)
 
Last edited:
Top Bottom