Introduction
When looking around the internet for a clue to implementing a simple scrolling news ticker style panel for an application I have in production, the samples I found all pointed to the use of Graphics.Drawstring
, which when turned into a control flickered annoyingly no matter what I tried. The code I will present below in my first ever submission (please be gentle!) provides two ways that I found to overcome this. Please feel free to add/amend and otherwise improve on my examples!
Also, I should add that there are a lot of omissions in the class, like extra properties that could be implemented and there is virtually no error trapping... I will leave that up to your individual requirements.
Implementation 1 - Text Rendering
Having started to code a control against the sample ideas I found and having already implemented double buffering to little effect, I took a look at an alternative way to actually draw the text to the control's surface and came across the TextRenderer
class
The key area of the first implementation is to use a TextRenderer
object instead of the aforementioned Graphics.Drawstring
call. In the snippet below, Gr
is a graphics object defined for the UserControl itself, _news
is the actual string to present in the control and the variables X
and Y
are the starting width and height offset of the string.
Gr.Clear(Me.BackColor)
TextRenderer.DrawText(Gr, _news, Me.Font, New Point(CInt(X), CInt(Y)), Me.ForeColor)
The text is moved across the panel by manipulating the value of X
in response to a timer Tick
event until it is found to be less than zero minus the length of the string itself (ie: it has disappeared off the left side of the control) when it is reset to the starting point, the right side of the control.
Altering the values of the timer's interval and a step value _speed
allows the user to fine-tune the control for least flicker.
The two central methods in the control respond to the initial setup during MyBase.Load
and the timer tick response
Private Sub SetupWindow()
Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
Gr = Me.CreateGraphics()
X = Me.Width
Me.BorderStyle = Windows.Forms.BorderStyle.Fixed3D
Me.Dock = DockStyle.Bottom
_strSize = Gr.MeasureString(_news, Me.Font)
If (Me.Height < _strSize.Height + 4) Then Me.Height = CInt(_strSize.Height) + 4
Timer.Interval = 25
Timer.Start()
End Sub
Private Sub UpdateDisplay()
Dim tHeight As Double = _strSize.Height
Select Case Me.TextPosition
Case TextPlacement.Top : Y = 0
Case TextPlacement.Middle : Y = CSng((Me.Height / 2) - (tHeight / 2))
Case TextPlacement.Bottom : Y = CSng(Me.Height - tHeight)
End Select
Gr.Clear(Me.BackColor)
TextRenderer.DrawText(Gr, _news, Me.Font, New Point(CInt(X), CInt(Y)), Me.ForeColor)
If X <= (0 - _strSize.Width) Then
X = Me.Width
Else
X = CSng(X - _speed)
End If
End Sub
Implementation 2 - Repositioning a Label
While I was looking at ways to reduce the flicker in the above code, I considered the possibility of not redrawing the text directly at all but just moving it, so I added a label to my UserControl, set Autosize = True
and set its text property to the string to be shown (_news
). By manipulating the same X
and Y
variables as above, having first set them to the label's width and height offset it was possible to implement the same effect of a scrolling text with (to my mind) even less flickering evident than that noticeable in the first implementation
lbl.Location = New Point(CInt(X), CInt(Y))
The full listing for implementation two is reproduced here (Note: I am omitting the label insertion in the designer partial class, although this should be placed on the UserControl and named lbl
for this code to operate correctly)
Public Class NewsTicker
Inherits System.Windows.Forms.UserControl
Enum TextPlacement
Top
Middle
Bottom
End Enum
Dim X As Single
Dim Y As Single
Dim Gr As Graphics
Dim _news As String = "There is no news to display at this time"
Dim _strSize As SizeF
Private _tpos As TextPlacement = TextPlacement.Top
Private _speed As Double = 1.0
Private Sub NewsTicker_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
SetupWindow()
End Sub
Private Sub NewsTicker_Resize(sender As Object, e As System.EventArgs) Handles Me.Resize
SetupWindow()
End Sub
Private Sub Timer_Tick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Timer.Tick
UpdateDisplay()
End Sub
Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
lbl.Height = Me.Height - 4
lbl.Text = _news
lbl.AutoSize = True
lbl.Location = New Point(Me.Width, 0)
Me.Dock = DockStyle.Bottom
Me.BorderStyle = Windows.Forms.BorderStyle.Fixed3D
X = Me.Width
If (Me.Height < lbl.Height + 4) Then Me.Height = CInt(lbl.Height) + 4
Timer.Interval = 25
Timer.Start()
End Sub
Private Sub UpdateDisplay()
Dim tHeight As Double = lbl.Height
Select Case Me.TextPosition
Case TextPlacement.Top : Y = 0
Case TextPlacement.Middle : Y = CSng((Me.Height / 2) - (tHeight / 2))
Case TextPlacement.Bottom : Y = CSng(Me.Height - tHeight)
End Select
lbl.Location = New Point(CInt(X), CInt(Y))
If X <= (0 - lbl.Width) Then
X = Me.Width
Else
X = CSng(X - _speed)
End If
End Sub
Public Property News As String
Get
Return _news
End Get
Set(value As String)
_news = value
SetupWindow()
End Set
End Property
Public Property TextPosition As TextPlacement
Get
Return _tpos
End Get
Set(value As TextPlacement)
_tpos = value
End Set
End Property
Public Property Speed As Double
Get
Return _speed
End Get
Set(value As Double)
If value < 0.01 Or value > 100 Then
Throw New ArgumentOutOfRangeException("Speed", _
"Cannot be less than 0.01 or greater than 100")
Else
_speed = value
SetupWindow()
End If
End Set
End Property
End Class
I will upload the full project source as a .ZIP when I can work out why I keep getting an 'Unexpected error' during the upload procedure? - .ZIP now uploaded thanks to the guys at CP!
History
- v1.0 - Initial release: 27 December 2013.
- v1.01 - .ZIP upload issue resolved.