Creating a Scrolling Marquee Label Control
While developing my weather screen saver project I had a need to display
weather related alerts in a scrolling marquee style display. Windows Forms
doesn't provide an already built control to do this job, so I decided to
build a custom control for this job. It seemed like all I would need is a
Timer control and overriding the Paint event. I knew this wasn't a
particularly challenging or unusual control to create, and I know that it
has most likely been done many times. A quick search on the Internet found
some good information on creating custom controls and even a bit about
building scrolling/marquee style controls. I started with some of the
examples I found online and created my own simple version of a Marquee
label control. This article will demonstrate the steps needed to build the
same control I created using Visual Studio.
The first step in building the custom control is to create a new project
or open an existing project to house the control. Then right click on the
project in the solution explorer and select Add/User Control (Windows
Forms). In the Add New Item dialog box name the control MarqueeLabel and
click on Add. This will add a new User Control called MarqueeLabel to your
project.
Next you need to add a Timer control to the MarqueeLabel control you just
created. The timer will be used to scroll the text displayed in the
Marquee Control. On every timer Tick event we will move and redraw the
text in the control, creating a scrolling effect. Open the MarqueeLabel
control in design mode by double clicking on MarqueeLabel.cs in the
Solution Explorer. Add a Timer control and name it ScrollTimer.
Now we get to add some code. First we declare some variables we will be
using later.
private string text;
private int position;
Next we need to define some properties for the control. I chose to
override the Text property, and added some new properties for enabling the
scrolling, setting the scroll direction, and specifying how frequently and
how far to scroll the text.
I wanted to be able to scroll the text either left to right or right to
left depending on the application. To do that I created an enum for the
scroll direction and added a property.
public enum ScrollDirectionType
{
LeftToRight,
RightToLeft
}
[Category("Behavior"), Description("Specifies the direction the text will scroll")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public ScrollDirectionType ScrollDirection { get; set; }
Enabling and disabling the scrolling is as simple as starting and
stopping the ScrollTimer control. Here's the basic code for the property:
[Category("Behavior"), Description("Starts/Stops text scrolling")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(false)]
public bool ScrollTimerEnabled
{
get
{
return ScrollTimer.Enabled;
}
set
{
if (!this.DesignMode)
{
ScrollTimer.Enabled = value;
}
}
}
For the Text property, we need to make sure the control is redrawn
whenever the text changes. I also decided to reset the text position to
the start whenever the text changes. Here's the code for that:
[EditorBrowsable(EditorBrowsableState.Always)]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Bindable(true)]
public override string Text
{
get
{
return text;
}
set
{
// only reset if text actually changes
if (text == null || !text.Equals(value))
{
text = value;
ResetTextPosition();
this.Invalidate();
}
}
}
private void ResetTextPosition()
{
if (ScrollDirection == ScrollDirectionType.LeftToRight)
{
position = this.Width;
}
else
{
position = this.Width + 1;
}
}
The properties that control the scroll amount and frequency are pretty
straightforward.
[Category("Behavior"), Description("Specifies how frequently the text will scroll")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int ScrollTimeInterval
{
get
{
return ScrollTimer.Interval;
}
set
{
ScrollTimer.Interval = value;
}
}
[Category("Behavior"), Description("Specifies how far the text will scroll each ScrollTimeInterval")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(10)]
public int ScrollPixelAmount { get; set; }
The constructor adds some property initialization to the standard
constructor generated by Visual Studio:
public MarqueeLabel()
{
InitializeComponent();
ScrollPixelAmount = 10;
ScrollDirection = ScrollDirectionType.LeftToRight;
}
Now we just need to handle some events. Our Form_Load event looks like
this:
private void MarqueeLabel_Load(object sender, EventArgs e)
{
this.ResizeRedraw = true;
}
The timer Tick event for the ScrollTimer control does the job of
determining the next position for the scrolled text based on teh scroll
direction and amount to scroll. After determining the next position it
calls Invalidate()
to force the control to redraw itself
with the text in the new position.
private void ScrollTimer_Tick(object sender, EventArgs e)
{
if (ScrollDirection == ScrollDirectionType.LeftToRight)
{
position -= ScrollPixelAmount;
}
else
{
position += ScrollPixelAmount;
}
// Force a refresh.
this.Invalidate();
}
The last thing we need to do is handle the actual drawing of the control
by overriding OnPaint and OnPaintBackground. One example I found uses an
off screen buffer to draw the text and background and then copies it to
the screen. I liked it so I adapted that code to handle scrolling in
either direction. It draws the entire text every time and depends on
clipping to keep the painting within the control bounds. Most of this code
was "plagiarized" directly from this code example by Matthew MacDonald.
protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e)
{
// Do nothing.
// To prevent flicker, we will draw both the background and the text
// to a buffered image, and draw it to the control all at once.
}
protected override void OnPaint(PaintEventArgs e)
{
// The following line avoids a design-time error that would
// otherwise occur when the control is first loaded (but does not yet
// have a defined size).
if (e.ClipRectangle.Width == 0)
{
return;
}
base.OnPaint(e);
if (ScrollDirection == ScrollDirectionType.LeftToRight)
{
if (position < -(int)e.Graphics.MeasureString(text, this.Font).Width)
{
// Reset the text to scroll back onto the control.
position = this.Width;
}
}
else
{
if (position > this.Width)
{
// Reset the text to scroll back onto the control.
position = -(int)e.Graphics.MeasureString(text, this.Font).Width;
}
}
// Create the drawing area in memory.
// Double buffering is used to prevent flicker.
Bitmap blt = new Bitmap(e.ClipRectangle.Width, e.ClipRectangle.Height);
Graphics g = Graphics.FromImage(blt);
g.FillRectangle(new SolidBrush(this.BackColor), e.ClipRectangle);
g.DrawString(text, this.Font, new SolidBrush(this.ForeColor), position, 0);
// Render the finished image on the form.
e.Graphics.DrawImageUnscaled(blt, 0, 0);
g.Dispose();
}
I am using this code in my screen saver application and it works well. I
thought somebody might be able to use it as the basis for a more powerful
control or at least find it educational. As always, the full source code
is available at my
web site.