TrojanNemo
TrojanNemo

Reputation: 117

C# VS2010: how to draw scrolling text using drawstring without flicker

I'll preface this by saying I've already tried a bunch of ways of achieving my goal and I'm now resting at the "best" solution, but it's still far from what I would consider "great"...so I was hoping to get a suggestion here. This is in C# using Visual Studio 2010.

My program plays audio files that are accompanied by metadata that may include lyrics. When I have the lyrics, the user has the option to have the lyrics either display one phrase at a time, or scrolling along with the audio. This takes place on a one-line Label. It's not karaoke style where you have the entire phrase and it gets colored in or something, literally the lyrics scroll from left to right in time with the music.

I have tried with both DoubleBuffering enabled and disabled. With it enabled, it is better, but still, not perfect.

1) having a Graphics object created at runtime for the label, then a timer would draw directly on to the Label using its Graphics object. I tried both clearing the graphics, and instead just drawing a filled rectangle the size of the graphics to avoid the flicker of clearing. After either of these, the text string is drawn. I've tried 25ms, 50ms, and 100ms with about the same result here.

2) having a Graphics object created at runtime for the label, then a timer would create a bitmap the size of the label, create a graphics object from that bitmap, draw the filled rectangle and draw the string in the graphics object, then copy that to the Label's graphics object, and I have also tried copying the bitmap to the Label.Image field.

3) having no dedicated Graphics object created. instead, have the timer Invalidate the Label. Then on the Label's Paint event, use the e argument's Graphics object to directly draw the filled rectangle and draw the text string.

In all instances, the result is a correctly scrolling text that is jittery and hard to read as it scrolls, but looks perfect when playback is paused. Timing and content of what is drawn is accurate. #3 is the "best" of the many variations I have tried, but as I say, it's still not easy to read the text. Given the timer values have varied between 40FPS and 10FPS, and the result is not very different in legibility, I'm assuming it's coming down to an inefficient way of doing the drawings on my part.

Is there some obvious mistake I'm making or a fundamental lack of foundation that is causing this behavior? I would love some input on how I can improve this. Thanks.

Upvotes: 3

Views: 1055

Answers (2)

Bradley Uffner
Bradley Uffner

Reputation: 16991

This is code from a scrolling label control I created a while ago. It may need some tweaking, and it's written in VB.NET, you will have to convert it. It scrolls smoothly for me without and flicker. You can adjust the speed by changing the number in the 2 timer calls, or chaning the .25 in the Tick sub.

Imports System.ComponentModel

Public Class ScrollingLabel
    Inherits Label

    Private _buffer As Bitmap
    Private _textX As Double
    Private _brush As Brush
    Private _timer As Threading.Timer
    Private _textWidth As Integer

    Public Sub New()
        MyBase.New()
        If Not IsDesignMode() Then
            _timer = New Threading.Timer(AddressOf Tick, Nothing, 25, Threading.Timeout.Infinite)
        End If
        _brush = New SolidBrush(Me.ForeColor)
        _textX = Me.Width
    End Sub

    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        Using g As Graphics = Graphics.FromImage(_buffer)
            g.Clear(Me.BackColor)
            g.DrawString(Me.Text, Me.Font, _brush, New PointF(CSng(_textX), 0))
        End Using
        e.Graphics.DrawImage(_buffer, 0, 0)
    End Sub

    Private Sub ScrollingLabel_Resize(sender As Object, e As EventArgs) Handles Me.Resize
        If _buffer IsNot Nothing Then
            _buffer.Dispose()
        End If
        _buffer = New Bitmap(Me.Width, Me.Height, Imaging.PixelFormat.Format32bppArgb)
    End Sub

    Public Overrides Property ForeColor As Color
        Get
            Return MyBase.ForeColor
        End Get
        Set(value As Color)
            MyBase.ForeColor = value
            If _brush IsNot Nothing Then
                _brush.Dispose()
            End If
            _brush = New SolidBrush(Me.ForeColor)
        End Set
    End Property

    Public Overrides Property Text As String
        Get
            Return MyBase.Text
        End Get
        Set(value As String)
            MyBase.Text = value

            Using g As Graphics = Graphics.FromImage(_buffer)
                _textWidth = CInt(g.MeasureString(Me.Text, Me.Font).Width)
            End Using
        End Set
    End Property

    Private Sub Tick(state As Object)
        If Me.Parent.InvokeRequired Then
            Me.BeginInvoke(New Action(Of Object)(AddressOf Tick), New Object() {state})
        End If
        _textX -= 0.25
        If Math.Abs(_textX) > _textWidth Then
            _textX = Me.Width
        End If
        _timer.Change(25, Threading.Timeout.Infinite)
        Me.Invalidate()
    End Sub

    Private Function IsDesignMode() As Boolean
        If DesignMode Then
            Return True
        End If
        Return CBool(LicenseManager.UsageMode = LicenseUsageMode.Designtime)
    End Function
End Class

Upvotes: 0

Bradley Uffner
Bradley Uffner

Reputation: 16991

When you try to draw on a control that way the control will "helpfully" paint it's background for you. Unfortunately this can cause flicker.

What I've done in the past is to create a new class that inherits from the control you actually want to draw on. This class should override OnPaintBackground with an empty sub. Then use this subclass on your form instead of the stock class.

I've only ever done this with picture boxes, so your results with other types of controls may vary.

You may also have some luck setting the control style like this (AllPaintingInWmPaint being the most important one in this case.):

this.SetStyle(
    ControlStyles.AllPaintingInWmPaint | 
    ControlStyles.UserPaint | 
    ControlStyles.DoubleBuffer, 
    true);

Upvotes: 0

Related Questions