Creating a Scrolling Marquee Label Control

How to create a scrolling marquee user control in C# using Visual Studio

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.

Written by KB3HHA on 12/17/2020