【问题标题】:C# VS2010: how to draw scrolling text using drawstring without flickerC# VS2010:如何使用drawstring绘制滚动文本而不闪烁
【发布时间】:2015-02-17 15:45:59
【问题描述】:

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

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

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

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

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

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

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

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

【问题讨论】:

    标签: visual-studio-2010 drawstring c3


    【解决方案1】:

    当您尝试以这种方式在控件上绘图时,该控件将“有帮助地”为您绘制背景。不幸的是,这会导致闪烁。

    我过去所做的是创建一个新类,该类继承自您实际想要绘制的控件。这个类应该用一个空子覆盖OnPaintBackground。然后在你的表单上使用这个子类而不是 stock 类。

    我只对图片框做过此操作,因此使用其他类型控件的结果可能会有所不同。

    您可能还可以像这样设置控件样式(AllPaintingInWmPaint 在这种情况下是最重要的。):

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

    【讨论】:

    • 不幸的是,这没有任何区别。我还尝试启用/禁用视觉样式,启用/禁用表单的双缓冲,启用/禁用单独到相关面板的缓冲,以及使用各种代码 sn-ps 都声称可以实现相同的“闪烁”免费绘图。最终的结果是一样的,我认为这是因为问题不是“闪烁”,因为它是由绘画引起的,而是因为我有文字在屏幕上滚动并且在没有运动模糊的情况下实现这一点超出了我的能力范围在 .NET 中完成 :-(
    • 我很确定我以前做过。下班回家后,我将深入研究我的代码并尝试找到一个示例。
    • 我使用旧项目中的代码提交了一个新答案。
    【解决方案2】:

    这是我不久前创建的滚动标签控件的代码。它可能需要一些调整,并且它是用 VB.NET 编写的,您必须对其进行转换。它对我来说流畅滚动,没有闪烁。您可以通过更改 2 个计时器调用中的数字或更改 Tick 子中的 .25 来调整速度。

    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
    

    【讨论】:

    • 由于我不在家,因此无法对此进行测试,但是查看代码时,我发现有两件事对我造成了问题。首先,您没有使控件无效,而是使整个表单无效。我正在尝试改进标签中歌词的滚动,但我的表格非常庞大并且有很多事情要做。我只需要使标签无效。其次,您使用的是受保护的覆盖子 OnPaint,它是表单的覆盖,但我只需要覆盖标签的 OnPaint,我似乎无法做到这一点。现在我的代码在 label_Paint 事件上,是一样的吗?这不是覆盖。
    • 再次检查,这两个都是对标签控件的调用。这是一个继承自 Label 的自定义控件实现。 Me(C# 中的 this)指的是当前类,标签的子类。如果您将此代码添加到您的项目并构建它,您将能够直接从工具箱中将 ScrollingLabel 放置在您的表单上,并将其设置为 .Text 属性,就像传统的标签一样。所有代码都是完全独立的,您根本不需要表单上的任何特殊代码。
    • 哇,我感觉自己像个白痴,因为我错过了顶部的类声明。今晚我会尝试将它移植到 C# 中,如果它有什么不同,我会告诉你。
    • 想要更新我尝试了代码,并将它保留在我现有的标签旁边,所以我有这个新的滚动标签和常规标签,它们看起来与滚动的歌词完全相同。由于我在标签的绘制函数上绘制歌词字符串,我想我正在完成的工作与您的代码通过绘制文本完成的工作相同。重点是,没有区别,我怀疑我遇到的问题是其他人不会遇到的。所以我暂时不会再花时间在这上面了。感谢您的帮助。
    • 你在我的控制下闪烁?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-27
    相关资源
    最近更新 更多