Reputation: 117
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
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
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