3

我会先说我已经尝试了很多方法来实现我的目标,我现在正停留在“最好”的解决方案上,但它离我认为的“伟大”还很远......所以我希望在这里得到建议。这是在 C# 中使用 Visual Studio 2010。

我的程序播放带有可能包括歌词的元数据的音频文件。当我有了歌词时,用户可以选择让歌词一次显示一个短语,或者随着音频滚动。这发生在单行标签上。这不是卡拉 OK 风格,你拥有整个短语并且它会被着色或其他东西,实际上歌词会随着音乐从左到右滚动。

我尝试过启用和禁用双缓冲。启用它后,它会更好,但仍然不完美。

1) 在运行时为标签创建一个 Graphics 对象,然后计时器将使用其 Graphics 对象直接绘制到 Label 上。我尝试清除图形,而不是仅绘制图形大小的填充矩形以避免清除闪烁。在其中任何一个之后,都会绘制文本字符串。我在这里尝试了 25 毫秒、50 毫秒和 100 毫秒,结果大致相同。

2)在运行时为标签创建一个图形对象,然后计时器将创建一个标签大小的位图,从该位图创建一个图形对象,绘制填充矩形并在图形对象中绘制字符串,然后复制该到 Label 的图形对象,我也尝试将位图复制到 Label.Image 字段。

3) 没有创建专用的 Graphics 对象。相反,让计时器使标签无效。然后在Label的Paint事件上,使用e参数的Graphics对象直接绘制填充的矩形,绘制文本字符串。

在所有情况下,结果都是正确滚动的文本,在滚动时会抖动且难以阅读,但在暂停播放时看起来很完美。绘制的时间和内容是准确的。#3 是我尝试过的众多变体中“最好的”,但正如我所说,阅读文本仍然不容易。鉴于计时器值在 40FPS 和 10FPS 之间变化,并且结果在易读性上差别不大,我认为这归结为我绘制图纸的效率低下。

我是否犯了一些明显的错误或根本缺乏导致这种行为的基础?我很想就如何改进这一点提供一些意见。谢谢。

4

2 回答 2

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);
于 2015-02-17T18:42:02.057 回答
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
于 2015-02-24T17:58:25.397 回答