3

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.

TrojanNemo
  • 117
  • 9

2 Answers2

0

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);
Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
  • Unfortunately this made no difference. I also tried enabling/disabling visual styles, enabling/disabling double buffering for the form, enabling/disabling buffering individually to the panels in question, and with various code snippets that all claim to achieve the same "flicker" free drawing. The end result is the same, and I think it's because the problem is not "flicker" in the sense that it's caused by painting, but by the fact that I have words scrolling across the screen and achieving that without motion blur is beyond what can be accomplished in .NET :-( – TrojanNemo Feb 24 '15 at 15:12
  • I'm fairly sure I've done it before in the past. Once I get home from work I'll dig through my code and try to find an example. – Bradley Uffner Feb 24 '15 at 15:21
  • I submitted a new answer with the code from my old project. – Bradley Uffner Feb 24 '15 at 17:58
0

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
Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
  • I haven't been able to test this as I'm not at home, but reviewing the code I see two things that create a problem for me. First, you're not invalidating the control, you're invalidating the whole form. I'm trying to improve the scrolling of the lyrics in the label, but my form is immense and has a lot going on. I need to only invalidate the label. Second, is that you use Protected Overrides Sub OnPaint, which is an override for the form, but I need to override only the label's OnPaint, which I can't seem to do. Now my code is on the label_Paint event, is that the same? It's not an override. – TrojanNemo Feb 24 '15 at 21:28
  • Check again, both of those are calls to the label control. This is a custom control implementation that inherits from Label. Me (this in C#) refers to the current class, a subclass of label. If you add this code to your project and build it, you will be able to directly place a ScrollingLabel on your form from the toolbox and set it's .Text property just like a traditional Label. All of the code is entirely self contained, you won't need any special code on the form at all. – Bradley Uffner Feb 24 '15 at 21:31
  • 1
    Wow, I feel like an idiot for missing the class declaration on top. I will try to port this to C# tonight and will let you know if it makes a difference. – TrojanNemo Feb 24 '15 at 22:41
  • Wanted to update that I tried the code, and kept it alongside my existing label, so I had this new scrolling label and the regular label, and they both looked exactly the same as the lyrics scrolled. Since I am drawing the lyric string on the paint function of the label, I guess what I'm accomplishing is the same as what your code accomplishes by drawing the text. Point being, no difference, and I suspect that what I'm having a problem with is something other people wouldn't. So I'm not spending any more time on this for the time being. Thanks for the help. – TrojanNemo Feb 25 '15 at 16:00
  • You are getting flickering from my control? – Bradley Uffner Feb 25 '15 at 16:02
  • It's not "flickering" - it's more like it's hard to read the text as it's scrolling. If you pause playback the text is drawn clearly, but during playback it's hard to read. I've tried many ways to alleviate that, your code included, but I think it's just a limitation of the horizontal "animation" of the text. I don't have a way to show you what I mean at this point, unfortunately. – TrojanNemo Feb 25 '15 at 17:28
  • Try lowering the change in _textX in the Tick sub of my code. That's the amount of change between animations, so lowering it will make it smoother. My example has it at -.25, which is 1/4 pixel per tick. Try -.1, then then lowering the timer interval to compensate for the extra slowdown. That should make it smoother, but the numbers are pretty low already, it may not get any smoother. Making the font larger, or bold may fix the effect somewhat too. This might be the limit using WinForms. WPF may be able to get better performance through actual animations though. – Bradley Uffner Feb 25 '15 at 17:32
  • I used your code partially because as I said, there's a lot else going on. When the animation needs to happen is tied to the audio playback timer, which in turn gets the timing from the playback itself. Considering how many variations of "this will definitely fix it" code samples I've tried, I do think the limitation is on WinForms and it's just something to live with. This is for a hobby, non-commercial usage and I'm about 5,000 lines in, I'm not switching forms now! Thanks for all the help. – TrojanNemo Feb 25 '15 at 18:43