Udostępnij za pośrednictwem


How to: Create a Windows Forms Control That Takes Advantage of Design-Time Features

The following example illustrates how to create a custom control and an associated custom designer. When this library is built, you can build custom MarqueeControl implementations that run on a form.

There is extensive support for this task in Visual Studio.

Example

Imports System


' This interface defines the contract for any class that is to 
' be used in constructing a MarqueeControl. 
Public Interface IMarqueeWidget

   ' This method starts the animation. If the control can  
   ' contain other classes that implement IMarqueeWidget as 
   ' children, the control should call StartMarquee on all 
   ' its IMarqueeWidget child controls. 
   Sub StartMarquee()

   ' This method stops the animation. If the control can  
   ' contain other classes that implement IMarqueeWidget as 
   ' children, the control should call StopMarquee on all 
   ' its IMarqueeWidget child controls. 
   Sub StopMarquee()

   ' This method specifies the refresh rate for the animation, 
   ' in milliseconds. 
   Property UpdatePeriod() As Integer 

End Interface
using System;

namespace MarqueeControlLibrary
{
    // This interface defines the contract for any class that is to 
    // be used in constructing a MarqueeControl. 
    public interface IMarqueeWidget
    {
        // This method starts the animation. If the control can  
        // contain other classes that implement IMarqueeWidget as 
        // children, the control should call StartMarquee on all 
        // its IMarqueeWidget child controls. 
        void StartMarquee();

        // This method stops the animation. If the control can  
        // contain other classes that implement IMarqueeWidget as 
        // children, the control should call StopMarquee on all 
        // its IMarqueeWidget child controls. 
        void StopMarquee();

        // This method specifies the refresh rate for the animation, 
        // in milliseconds. 
        int UpdatePeriod
        {
            get;
            set;
        }
    }
}
Imports System
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Diagnostics
Imports System.Drawing
Imports System.Drawing.Design
Imports System.Threading
Imports System.Windows.Forms
Imports System.Windows.Forms.Design

' This defines the possible values for the MarqueeBorder 
' control's SpinDirection property. 
Public Enum MarqueeSpinDirection
   CW
   CCW
End Enum 

' This defines the possible values for the MarqueeBorder 
' control's LightShape property. 
Public Enum MarqueeLightShape
    Square
    Circle
End Enum

<Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _
ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
ToolboxItemFilterType.Require)> _
Partial Public Class MarqueeBorder
    Inherits Panel
    Implements IMarqueeWidget

    Public Shared MaxLightSize As Integer = 10

    ' These fields back the public properties. 
    Private updatePeriodValue As Integer = 50
    Private lightSizeValue As Integer = 5
    Private lightPeriodValue As Integer = 3
    Private lightSpacingValue As Integer = 1
    Private lightColorValue As Color
    Private darkColorValue As Color
    Private spinDirectionValue As MarqueeSpinDirection = MarqueeSpinDirection.CW
    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square

    ' These brushes are used to paint the light and dark 
    ' colors of the marquee lights. 
    Private lightBrush As Brush
    Private darkBrush As Brush

    ' This field tracks the progress of the "first" light as it 
    ' "travels" around the marquee border. 
    Private currentOffset As Integer = 0

    ' This component updates the control asynchronously. 
    Private WithEvents backgroundWorker1 As System.ComponentModel.BackgroundWorker


    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()

        ' Initialize light and dark colors  
        ' to the control's default values. 
        Me.lightColorValue = Me.ForeColor
        Me.darkColorValue = Me.BackColor
        Me.lightBrush = New SolidBrush(Me.lightColorValue)
        Me.darkBrush = New SolidBrush(Me.darkColorValue)

        ' The MarqueeBorder control manages its own padding, 
        ' because it requires that any contained controls do 
        ' not overlap any of the marquee lights. 
        Dim pad As Integer = 2 * (Me.lightSizeValue + Me.lightSpacingValue)
        Me.Padding = New Padding(pad, pad, pad, pad)

        SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
    End Sub 

    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "IMarqueeWidget implementation" 

    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' The MarqueeBorder control may contain any number of  
        ' controls that implement IMarqueeWidget, so find 
        ' each IMarqueeWidget child and call its StartMarquee 
        ' method. 
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then 
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)

                widget.StartMarquee()
            End If 
        Next cntrl

        ' Start the updating thread and pass it the UpdatePeriod. 
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub 


    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' The MarqueeBorder control may contain any number of  
        ' controls that implement IMarqueeWidget, so find 
        ' each IMarqueeWidget child and call its StopMarquee 
        ' method. 
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then 
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)

                widget.StopMarquee()
            End If 
        Next cntrl

        ' Stop the updating thread. 
        Me.backgroundWorker1.CancelAsync()
    End Sub


    <Category("Marquee"), Browsable(True)> _
    Public Overridable Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod

        Get 
            Return Me.updatePeriodValue
        End Get 

        Set(ByVal Value As Integer)
            If Value > 0 Then 
                Me.updatePeriodValue = Value
            Else 
                Throw New ArgumentOutOfRangeException("UpdatePeriod", _
                "must be > 0")
            End If 
        End Set 

    End Property

#End Region

    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "Public Properties"

    <Category("Marquee"), Browsable(True)> _
    Public Property LightSize() As Integer 
        Get 
            Return Me.lightSizeValue
        End Get 

        Set(ByVal Value As Integer)
            If Value > 0 AndAlso Value <= MaxLightSize Then 
                Me.lightSizeValue = Value
                Me.DockPadding.All = 2 * Value
            Else 
                Throw New ArgumentOutOfRangeException("LightSize", _
                "must be > 0 and < MaxLightSize")
            End If 
        End Set 
    End Property


    <Category("Marquee"), Browsable(True)> _
    Public Property LightPeriod() As Integer 
        Get 
            Return Me.lightPeriodValue
        End Get 

        Set(ByVal Value As Integer)
            If Value > 0 Then 
                Me.lightPeriodValue = Value
            Else 
                Throw New ArgumentOutOfRangeException("LightPeriod", _
                "must be > 0 ")
            End If 
        End Set 
    End Property


    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color
        Get 
            Return Me.lightColorValue
        End Get 

        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the  
            ' client provides a different value. Comparing values  
            ' from the ToArgb method is the recommended test for 
            ' equality between Color structs. 
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then 
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If 
        End Set 
    End Property


    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color
        Get 
            Return Me.darkColorValue
        End Get 

        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the  
            ' client provides a different value. Comparing values  
            ' from the ToArgb method is the recommended test for 
            ' equality between Color structs. 
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then 
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If 
        End Set 
    End Property


    <Category("Marquee"), Browsable(True)> _
    Public Property LightSpacing() As Integer 
        Get 
            Return Me.lightSpacingValue
        End Get 

        Set(ByVal Value As Integer)
            If Value >= 0 Then 
                Me.lightSpacingValue = Value
            Else 
                Throw New ArgumentOutOfRangeException("LightSpacing", _
                "must be >= 0")
            End If 
        End Set 
    End Property


    <Category("Marquee"), Browsable(True), _
    EditorAttribute(GetType(LightShapeEditor), _
    GetType(System.Drawing.Design.UITypeEditor))> _
    Public Property LightShape() As MarqueeLightShape

        Get 
            Return Me.lightShapeValue
        End Get 

        Set(ByVal Value As MarqueeLightShape)
            Me.lightShapeValue = Value
        End Set 

    End Property


    <Category("Marquee"), Browsable(True)> _
    Public Property SpinDirection() As MarqueeSpinDirection

        Get 
            Return Me.spinDirectionValue
        End Get 

        Set(ByVal Value As MarqueeSpinDirection)
            Me.spinDirectionValue = Value
        End Set 

    End Property

#End Region

    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "Implementation" 

    Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs)
        MyBase.OnLayout(levent)

        ' Repaint when the layout has changed. 
        Me.Refresh()
    End Sub 


    ' This method paints the lights around the border of the  
    ' control. It paints the top row first, followed by the 
    ' right side, the bottom row, and the left side. The color 
    ' of each light is determined by the IsLit method and 
    ' depends on the light's position relative to the value 
    ' of currentOffset. 
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim g As Graphics = e.Graphics
        g.Clear(Me.BackColor)

        MyBase.OnPaint(e)

        ' If the control is large enough, draw some lights. 
        If Me.Width > MaxLightSize AndAlso Me.Height > MaxLightSize Then 
            ' The position of the next light will be incremented  
            ' by this value, which is equal to the sum of the 
            ' light size and the space between two lights. 
            Dim increment As Integer = _
            Me.lightSizeValue + Me.lightSpacingValue

            ' Compute the number of lights to be drawn along the 
            ' horizontal edges of the control. 
            Dim horizontalLights As Integer = _
            (Me.Width - increment) / increment

            ' Compute the number of lights to be drawn along the 
            ' vertical edges of the control. 
            Dim verticalLights As Integer = _
            (Me.Height - increment) / increment

            ' These local variables will be used to position and 
            ' paint each light. 
            Dim xPos As Integer = 0
            Dim yPos As Integer = 0
            Dim lightCounter As Integer = 0
            Dim brush As Brush

            ' Draw the top row of lights. 
            Dim i As Integer 
            For i = 0 To horizontalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)

                DrawLight(g, brush, xPos, yPos)

                xPos += increment
                lightCounter += 1
            Next i

            ' Draw the lights flush with the right edge of the control.
            xPos = Me.Width - Me.lightSizeValue

            ' Draw the right column of lights. 
            'Dim i As Integer 
            For i = 0 To verticalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)

                DrawLight(g, brush, xPos, yPos)

                yPos += increment
                lightCounter += 1
            Next i

            ' Draw the lights flush with the bottom edge of the control.
            yPos = Me.Height - Me.lightSizeValue

            ' Draw the bottom row of lights. 
            'Dim i As Integer 
            For i = 0 To horizontalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)

                DrawLight(g, brush, xPos, yPos)

                xPos -= increment
                lightCounter += 1
            Next i

            ' Draw the lights flush with the left edge of the control.
            xPos = 0

            ' Draw the left column of lights. 
            'Dim i As Integer 
            For i = 0 To verticalLights - 1
                brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush)

                DrawLight(g, brush, xPos, yPos)

                yPos -= increment
                lightCounter += 1
            Next i
        End If 
    End Sub 

    ' This method determines if the marquee light at lightIndex 
    ' should be lit. The currentOffset field specifies where 
    ' the "first" light is located, and the "position" of the 
    ' light given by lightIndex is computed relative to this  
    ' offset. If this position modulo lightPeriodValue is zero, 
    ' the light is considered to be on, and it will be painted 
    ' with the control's lightBrush.  
    Protected Overridable Function IsLit(ByVal lightIndex As Integer) As Boolean 
        Dim directionFactor As Integer = _
        IIf(Me.spinDirectionValue = MarqueeSpinDirection.CW, -1, 1)

        Return (lightIndex + directionFactor * Me.currentOffset) Mod Me.lightPeriodValue = 0
    End Function 


    Protected Overridable Sub DrawLight( _
    ByVal g As Graphics, _
    ByVal brush As Brush, _
    ByVal xPos As Integer, _
    ByVal yPos As Integer)

        Select Case Me.lightShapeValue
            Case MarqueeLightShape.Square
                g.FillRectangle( _
                brush, _
                xPos, _
                yPos, _
                Me.lightSizeValue, _
                Me.lightSizeValue)
                Exit Select 
            Case MarqueeLightShape.Circle
                g.FillEllipse( _
                brush, _
                xPos, _
                yPos, _
                Me.lightSizeValue, _
                Me.lightSizeValue)
                Exit Select 
            Case Else
                Trace.Assert(False, "Unknown value for light shape.")
                Exit Select 
        End Select 

    End Sub 

    ' This method is called in the worker thread's context,  
    ' so it must not make any calls into the MarqueeBorder 
    ' control. Instead, it communicates to the control using  
    ' the ProgressChanged event. 
    ' 
    ' The only work done in this event handler is 
    ' to sleep for the number of milliseconds specified  
    ' by UpdatePeriod, then raise the ProgressChanged event. 
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)

        ' This event handler will run until the client cancels 
        ' the background task by calling CancelAsync. 
        While Not worker.CancellationPending
            ' The Argument property of the DoWorkEventArgs 
            ' object holds the value of UpdatePeriod, which  
            ' was passed as the argument to the RunWorkerAsync 
            ' method. 
            Thread.Sleep(Fix(e.Argument))

            ' The DoWork eventhandler does not actually report 
            ' progress; the ReportProgress event is used to  
            ' periodically alert the control to update its state.
            worker.ReportProgress(0)
        End While 
    End Sub 


    ' The ProgressChanged event is raised by the DoWork method. 
    ' This event handler does work that is internal to the 
    ' control. In this case, the currentOffset is incremented, 
    ' and the control is told to repaint itself. 
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
        Me.currentOffset += 1
        Me.Refresh()
    End Sub 

    ' This class demonstrates the use of a custom UITypeEditor.  
    ' It allows the MarqueeBorder control's LightShape property 
    ' to be changed at design time using a customized UI element 
    ' that is invoked by the Properties window. The UI is provided 
    ' by the LightShapeSelectionControl class. 
    Friend Class LightShapeEditor
        Inherits UITypeEditor

        Private editorService As IWindowsFormsEditorService = Nothing 

        Public Overrides Function GetEditStyle( _
        ByVal context As System.ComponentModel.ITypeDescriptorContext) _
        As UITypeEditorEditStyle
            Return UITypeEditorEditStyle.DropDown
        End Function 


        Public Overrides Function EditValue( _
        ByVal context As ITypeDescriptorContext, _
        ByVal provider As IServiceProvider, _
        ByVal value As Object) As Object 
            If (provider IsNot Nothing) Then
                editorService = _
                CType(provider.GetService(GetType(IWindowsFormsEditorService)), _
                IWindowsFormsEditorService)
            End If 

            If (editorService IsNot Nothing) Then 
                Dim selectionControl As _
                New LightShapeSelectionControl( _
                CType(value, MarqueeLightShape), _
                editorService)

                editorService.DropDownControl(selectionControl)

                value = selectionControl.LightShape
            End If 

            Return value
        End Function 

        ' This method indicates to the design environment that 
        ' the type editor will paint additional content in the 
        ' LightShape entry in the PropertyGrid. 
        Public Overrides Function GetPaintValueSupported( _
        ByVal context As ITypeDescriptorContext) As Boolean 

            Return True 

        End Function 

        ' This method paints a graphical representation of the  
        ' selected value of the LightShpae property. 
        Public Overrides Sub PaintValue( _
        ByVal e As PaintValueEventArgs)

            Dim shape As MarqueeLightShape = _
            CType(e.Value, MarqueeLightShape)
            Using p As Pen = Pens.Black

                If shape = MarqueeLightShape.Square Then

                    e.Graphics.DrawRectangle(p, e.Bounds)

                Else

                    e.Graphics.DrawEllipse(p, e.Bounds)

                End If 

            End Using 

        End Sub 

    End Class 

    Private Sub InitializeComponent()
        Me.backgroundWorker1 = New System.ComponentModel.BackgroundWorker

        '  
        ' backgroundWorker1 
        '  
        Me.backgroundWorker1.WorkerReportsProgress = True 
        Me.backgroundWorker1.WorkerSupportsCancellation = True 
    End Sub

#End Region

End Class
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace MarqueeControlLibrary
{
    // This defines the possible values for the MarqueeBorder 
    // control's SpinDirection property. 
    public enum MarqueeSpinDirection
    {
        CW,
        CCW
    }

    // This defines the possible values for the MarqueeBorder 
    // control's LightShape property. 
    public enum MarqueeLightShape
    {
        Square,
        Circle
    }

    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {

        public static int MaxLightSize = 10;

        // These fields back the public properties. 
        private int updatePeriodValue = 50;
        private int lightSizeValue = 5;
        private int lightPeriodValue = 3;
        private int lightSpacingValue = 1;
        private Color lightColorValue;
        private Color darkColorValue;
        private MarqueeSpinDirection spinDirectionValue = MarqueeSpinDirection.CW;
        private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;

        // These brushes are used to paint the light and dark 
        // colors of the marquee lights. 
        private Brush lightBrush;
        private Brush darkBrush;

        // This field tracks the progress of the "first" light as it
        // "travels" around the marquee border.
        private int currentOffset = 0;

        // This component updates the control asynchronously. 
        private System.ComponentModel.BackgroundWorker backgroundWorker1;

        public MarqueeBorder()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            // Initialize light and dark colors  
            // to the control's default values. 
            this.lightColorValue = this.ForeColor;
            this.darkColorValue = this.BackColor;
            this.lightBrush = new SolidBrush(this.lightColorValue);
            this.darkBrush = new SolidBrush(this.darkColorValue);

            // The MarqueeBorder control manages its own padding, 
            // because it requires that any contained controls do 
            // not overlap any of the marquee lights. 
            int pad = 2 * (this.lightSizeValue + this.lightSpacingValue);
            this.Padding = new Padding(pad, pad, pad, pad);

            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        }

        ///////////////////////////////////////////////////////////////////////
        #region IMarqueeWidget implementation

        public virtual void StartMarquee()
        {
            // The MarqueeBorder control may contain any number of  
            // controls that implement IMarqueeWidget, so find 
            // each IMarqueeWidget child and call its StartMarquee 
            // method. 
            foreach (Control cntrl in this.Controls)
            {
                if (cntrl is IMarqueeWidget)
                {
                    IMarqueeWidget widget = cntrl as IMarqueeWidget;
                    widget.StartMarquee();
                }
            }

            // Start the updating thread and pass it the UpdatePeriod. 
            this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
        }

        public virtual void StopMarquee()
        {
            // The MarqueeBorder control may contain any number of  
            // controls that implement IMarqueeWidget, so find 
            // each IMarqueeWidget child and call its StopMarquee 
            // method. 
            foreach (Control cntrl in this.Controls)
            {
                if (cntrl is IMarqueeWidget)
                {
                    IMarqueeWidget widget = cntrl as IMarqueeWidget;
                    widget.StopMarquee();
                }
            }

            // Stop the updating thread. 
            this.backgroundWorker1.CancelAsync();
        }

        [Category("Marquee")]
        [Browsable(true)]
        public virtual int UpdatePeriod
        {
            get
            {
                return this.updatePeriodValue;
            }

            set
            {
                if (value > 0)
                {
                    this.updatePeriodValue = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
                }
            }
        }


        #endregion

        ///////////////////////////////////////////////////////////////////////
        #region Public Properties

        [Category("Marquee")]
        [Browsable(true)]
        public int LightSize
        {
            get
            {
                return this.lightSizeValue;
            }

            set
            {
                if (value > 0 && value <= MaxLightSize)
                {
                    this.lightSizeValue = value;
                    this.DockPadding.All = 2 * value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("LightSize", "must be > 0 and < MaxLightSize");
                }
            }
        }

        [Category("Marquee")]
        [Browsable(true)]
        public int LightPeriod
        {
            get
            {
                return this.lightPeriodValue;
            }

            set
            {
                if (value > 0)
                {
                    this.lightPeriodValue = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("LightPeriod", "must be > 0 ");
                }
            }
        }

        [Category("Marquee")]
        [Browsable(true)]
        public Color LightColor
        {
            get
            {
                return this.lightColorValue;
            }

            set
            {
                // The LightColor property is only changed if the  
                // client provides a different value. Comparing values  
                // from the ToArgb method is the recommended test for 
                // equality between Color structs. 
                if (this.lightColorValue.ToArgb() != value.ToArgb())
                {
                    this.lightColorValue = value;
                    this.lightBrush = new SolidBrush(value);
                }
            }
        }

        [Category("Marquee")]
        [Browsable(true)]
        public Color DarkColor
        {
            get
            {
                return this.darkColorValue;
            }

            set
            {
                // The DarkColor property is only changed if the  
                // client provides a different value. Comparing values  
                // from the ToArgb method is the recommended test for 
                // equality between Color structs. 
                if (this.darkColorValue.ToArgb() != value.ToArgb())
                {
                    this.darkColorValue = value;
                    this.darkBrush = new SolidBrush(value);
                }
            }
        }

        [Category("Marquee")]
        [Browsable(true)]
        public int LightSpacing
        {
            get
            {
                return this.lightSpacingValue;
            }

            set
            {
                if (value >= 0)
                {
                    this.lightSpacingValue = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("LightSpacing", "must be >= 0");
                }
            }
        }

        [Category("Marquee")]
        [Browsable(true)]
        [EditorAttribute(typeof(LightShapeEditor), 
             typeof(System.Drawing.Design.UITypeEditor))]
        public MarqueeLightShape LightShape
        {
            get
            {
                return this.lightShapeValue;
            }

            set
            {
                this.lightShapeValue = value;
            }
        }

        [Category("Marquee")]
        [Browsable(true)]
        public MarqueeSpinDirection SpinDirection
        {
            get
            {
                return this.spinDirectionValue;
            }

            set
            {
                this.spinDirectionValue = value;
            }
        }


        #endregion

        ///////////////////////////////////////////////////////////////////////
        #region Implementation

        protected override void OnLayout(LayoutEventArgs levent)
        {
            base.OnLayout(levent);

            // Repaint when the layout has changed. 
            this.Refresh();
        }

        // This method paints the lights around the border of the  
        // control. It paints the top row first, followed by the 
        // right side, the bottom row, and the left side. The color 
        // of each light is determined by the IsLit method and 
        // depends on the light's position relative to the value 
        // of currentOffset. 
        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.Clear(this.BackColor);

            base.OnPaint(e);

            // If the control is large enough, draw some lights. 
            if (this.Width > MaxLightSize &&
                this.Height > MaxLightSize)
            {
                // The position of the next light will be incremented  
                // by this value, which is equal to the sum of the 
                // light size and the space between two lights. 
                int increment =
                    this.lightSizeValue + this.lightSpacingValue;

                // Compute the number of lights to be drawn along the 
                // horizontal edges of the control. 
                int horizontalLights =
                    (this.Width - increment) / increment;

                // Compute the number of lights to be drawn along the 
                // vertical edges of the control. 
                int verticalLights =
                    (this.Height - increment) / increment;

                // These local variables will be used to position and 
                // paint each light. 
                int xPos = 0;
                int yPos = 0;
                int lightCounter = 0;
                Brush brush;

                // Draw the top row of lights. 
                for (int i = 0; i < horizontalLights; i+)
                {
                    brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;

                    DrawLight(g, brush, xPos, yPos);

                    xPos += increment;
                    lightCounter++;
                }

                // Draw the lights flush with the right edge of the control.
                xPos = this.Width - this.lightSizeValue;

                // Draw the right column of lights. 
                for (int i = 0; i < verticalLights; i+)
                {
                    brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;

                    DrawLight(g, brush, xPos, yPos);

                    yPos += increment;
                    lightCounter++;
                }

                // Draw the lights flush with the bottom edge of the control.
                yPos = this.Height - this.lightSizeValue;

                // Draw the bottom row of lights. 
                for (int i = 0; i < horizontalLights; i+)
                {
                    brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;

                    DrawLight(g, brush, xPos, yPos);

                    xPos -= increment;
                    lightCounter++;
                }

                // Draw the lights flush with the left edge of the control.
                xPos = 0;

                // Draw the left column of lights. 
                for (int i = 0; i < verticalLights; i+)
                {
                    brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush;

                    DrawLight(g, brush, xPos, yPos);

                    yPos -= increment;
                    lightCounter++;
                }
            }
        }

        // This method determines if the marquee light at lightIndex 
        // should be lit. The currentOffset field specifies where 
        // the "first" light is located, and the "position" of the
        // light given by lightIndex is computed relative to this  
        // offset. If this position modulo lightPeriodValue is zero, 
        // the light is considered to be on, and it will be painted 
        // with the control's lightBrush.  
        protected virtual bool IsLit(int lightIndex)
        {
            int directionFactor =
                (this.spinDirectionValue == MarqueeSpinDirection.CW ? -1 : 1);

            return (
                (lightIndex + directionFactor * this.currentOffset) % this.lightPeriodValue == 0
                );
        }

        protected virtual void DrawLight(
            Graphics g,
            Brush brush,
            int xPos,
            int yPos)
        {
            switch (this.lightShapeValue)
            {
                case MarqueeLightShape.Square:
                    {
                        g.FillRectangle(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue);
                        break;
                    }
                case MarqueeLightShape.Circle:
                    {
                        g.FillEllipse(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue);
                        break;
                    }
                default:
                    {
                        Trace.Assert(false, "Unknown value for light shape.");
                        break;
                    }
            }
        }

        // This method is called in the worker thread's context,  
        // so it must not make any calls into the MarqueeBorder 
        // control. Instead, it communicates to the control using  
        // the ProgressChanged event. 
        // 
        // The only work done in this event handler is 
        // to sleep for the number of milliseconds specified  
        // by UpdatePeriod, then raise the ProgressChanged event. 
        private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            // This event handler will run until the client cancels 
            // the background task by calling CancelAsync. 
            while (!worker.CancellationPending)
            {
                // The Argument property of the DoWorkEventArgs 
                // object holds the value of UpdatePeriod, which  
                // was passed as the argument to the RunWorkerAsync 
                // method. 
                Thread.Sleep((int)e.Argument);

                // The DoWork eventhandler does not actually report 
                // progress; the ReportProgress event is used to  
                // periodically alert the control to update its state.
                worker.ReportProgress(0);
            }
        }

        // The ProgressChanged event is raised by the DoWork method. 
        // This event handler does work that is internal to the 
        // control. In this case, the currentOffset is incremented, 
        // and the control is told to repaint itself. 
        private void backgroundWorker1_ProgressChanged(
            object sender,
            System.ComponentModel.ProgressChangedEventArgs e)
        {
            this.currentOffset++;
            this.Refresh();
        }

        // This class demonstrates the use of a custom UITypeEditor.  
        // It allows the MarqueeBorder control's LightShape property 
        // to be changed at design time using a customized UI element 
        // that is invoked by the Properties window. The UI is provided 
        // by the LightShapeSelectionControl class. 
        internal class LightShapeEditor : UITypeEditor
        {

            private IWindowsFormsEditorService editorService = null;

            public override UITypeEditorEditStyle GetEditStyle(
            System.ComponentModel.ITypeDescriptorContext context)
            {
                return UITypeEditorEditStyle.DropDown;
            }

            public override object EditValue(
                ITypeDescriptorContext context,
                IServiceProvider provider,
                object value)
            {
                if (provider != null)
                {
                    editorService =
                        provider.GetService(
                        typeof(IWindowsFormsEditorService))
                        as IWindowsFormsEditorService;
                }

                if (editorService != null)
                {
                    LightShapeSelectionControl selectionControl =
                        new LightShapeSelectionControl(
                        (MarqueeLightShape)value,
                        editorService);

                    editorService.DropDownControl(selectionControl);

                    value = selectionControl.LightShape;
                }

                return value;
            }

            // This method indicates to the design environment that 
            // the type editor will paint additional content in the 
            // LightShape entry in the PropertyGrid. 
            public override bool GetPaintValueSupported(
                ITypeDescriptorContext context)
            {  
                return true;
            }

            // This method paints a graphical representation of the  
            // selected value of the LightShpae property. 
            public override void PaintValue(PaintValueEventArgs e)
            {   
                MarqueeLightShape shape = (MarqueeLightShape)e.Value;
                using (Pen p = Pens.Black)
                {
                    if (shape == MarqueeLightShape.Square)
                    {
                        e.Graphics.DrawRectangle(p, e.Bounds);
                    }
                    else
                    {
                        e.Graphics.DrawEllipse(p, e.Bounds);
                    }
                }   
            }
        }

        private void InitializeComponent()
        {
            this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();

//  
// backgroundWorker1 
//  
            this.backgroundWorker1.WorkerReportsProgress = true;
            this.backgroundWorker1.WorkerSupportsCancellation = true;
            this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged);
            this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
        }

        #endregion
    }
}
Imports System
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Diagnostics
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms
Imports System.Windows.Forms.Design

<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
ToolboxItemFilterType.Require)> _
Partial Public Class MarqueeText
    Inherits Label
    Implements IMarqueeWidget

    ' When isLit is true, the text is painted in the light color; 
    ' When isLit is false, the text is painted in the dark color. 
    ' This value changes whenever the BackgroundWorker component 
    ' raises the ProgressChanged event. 
    Private isLit As Boolean = True 

    ' These fields back the public properties. 
    Private updatePeriodValue As Integer = 50
    Private lightColorValue As Color
    Private darkColorValue As Color

    ' These brushes are used to paint the light and dark 
    ' colors of the text. 
    Private lightBrush As Brush
    Private darkBrush As Brush

    ' This component updates the control asynchronously. 
    Private WithEvents backgroundWorker1 As BackgroundWorker


    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()

        ' Initialize light and dark colors  
        ' to the control's default values. 
        Me.lightColorValue = Me.ForeColor
        Me.darkColorValue = Me.BackColor
        Me.lightBrush = New SolidBrush(Me.lightColorValue)
        Me.darkBrush = New SolidBrush(Me.darkColorValue)
    End Sub 'New 

    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "IMarqueeWidget implementation" 


    Public Overridable Sub StartMarquee() _
    Implements IMarqueeWidget.StartMarquee
        ' Start the updating thread and pass it the UpdatePeriod. 
        Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod)
    End Sub 

    Public Overridable Sub StopMarquee() _
    Implements IMarqueeWidget.StopMarquee
        ' Stop the updating thread. 
        Me.backgroundWorker1.CancelAsync()
    End Sub


    <Category("Marquee"), Browsable(True)> _
    Public Property UpdatePeriod() As Integer _
    Implements IMarqueeWidget.UpdatePeriod

        Get 
            Return Me.updatePeriodValue
        End Get 

        Set(ByVal Value As Integer)
            If Value > 0 Then 
                Me.updatePeriodValue = Value
            Else 
                Throw New ArgumentOutOfRangeException("UpdatePeriod", "must be > 0")
            End If 
        End Set 

    End Property

#End Region

    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "Public Properties"

    <Category("Marquee"), Browsable(True)> _
    Public Property LightColor() As Color

        Get 
            Return Me.lightColorValue
        End Get 

        Set(ByVal Value As Color)
            ' The LightColor property is only changed if the  
            ' client provides a different value. Comparing values  
            ' from the ToArgb method is the recommended test for 
            ' equality between Color structs. 
            If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then 
                Me.lightColorValue = Value
                Me.lightBrush = New SolidBrush(Value)
            End If 
        End Set 

    End Property


    <Category("Marquee"), Browsable(True)> _
    Public Property DarkColor() As Color

        Get 
            Return Me.darkColorValue
        End Get 

        Set(ByVal Value As Color)
            ' The DarkColor property is only changed if the  
            ' client provides a different value. Comparing values  
            ' from the ToArgb method is the recommended test for 
            ' equality between Color structs. 
            If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then 
                Me.darkColorValue = Value
                Me.darkBrush = New SolidBrush(Value)
            End If 
        End Set 

    End Property
#End Region

    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#Region "Implementation" 

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        ' The text is painted in the light or dark color, 
        ' depending on the current value of isLit. 
        Me.ForeColor = IIf(Me.isLit, Me.lightColorValue, Me.darkColorValue)

        MyBase.OnPaint(e)
    End Sub 

    ' This method is called in the worker thread's context,  
    ' so it must not make any calls into the MarqueeText control. 
    ' Instead, it communicates to the control using the  
    ' ProgressChanged event. 
    ' 
    ' The only work done in this event handler is 
    ' to sleep for the number of milliseconds specified  
    ' by UpdatePeriod, then raise the ProgressChanged event. 
    Private Sub backgroundWorker1_DoWork( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) _
    Handles backgroundWorker1.DoWork
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)

        ' This event handler will run until the client cancels 
        ' the background task by calling CancelAsync. 
        While Not worker.CancellationPending
            ' The Argument property of the DoWorkEventArgs 
            ' object holds the value of UpdatePeriod, which  
            ' was passed as the argument to the RunWorkerAsync 
            ' method. 
            Thread.Sleep(Fix(e.Argument))

            ' The DoWork eventhandler does not actually report 
            ' progress; the ReportProgress event is used to  
            ' periodically alert the control to update its state.
            worker.ReportProgress(0)
        End While 
    End Sub 


    ' The ProgressChanged event is raised by the DoWork method. 
    ' This event handler does work that is internal to the 
    ' control. In this case, the text is toggled between its 
    ' light and dark state, and the control is told to  
    ' repaint itself. 
    Private Sub backgroundWorker1_ProgressChanged( _
    ByVal sender As Object, _
    ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
    Handles backgroundWorker1.ProgressChanged
        Me.isLit = Not Me.isLit
        Me.Refresh()
    End Sub 

    Private Sub InitializeComponent()
        Me.backgroundWorker1 = New System.ComponentModel.BackgroundWorker

        '  
        ' backgroundWorker1 
        '  
        Me.backgroundWorker1.WorkerReportsProgress = True 
        Me.backgroundWorker1.WorkerSupportsCancellation = True 
    End Sub

#End Region

End Class
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace MarqueeControlLibrary
{
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    public partial class MarqueeText : Label, IMarqueeWidget
    {

        // When isLit is true, the text is painted in the light color; 
        // When isLit is false, the text is painted in the dark color. 
        // This value changes whenever the BackgroundWorker component 
        // raises the ProgressChanged event. 
        private bool isLit = true;

        // These fields back the public properties. 
        private int updatePeriodValue = 50;
        private Color lightColorValue;
        private Color darkColorValue;

        // These brushes are used to paint the light and dark 
        // colors of the text. 
        private Brush lightBrush;
        private Brush darkBrush;

        // This component updates the control asynchronously. 
        private BackgroundWorker backgroundWorker1;

        public MarqueeText()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            // Initialize light and dark colors  
            // to the control's default values. 
            this.lightColorValue = this.ForeColor;
            this.darkColorValue = this.BackColor;
            this.lightBrush = new SolidBrush(this.lightColorValue);
            this.darkBrush = new SolidBrush(this.darkColorValue);
        }

        ///////////////////////////////////////////////////////////////////////
        #region IMarqueeWidget implementation

        public virtual void StartMarquee()
        {
            // Start the updating thread and pass it the UpdatePeriod. 
            this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod);
        }

        public virtual void StopMarquee()
        {
            // Stop the updating thread. 
            this.backgroundWorker1.CancelAsync();
        }

        [Category("Marquee")]
        [Browsable(true)]
        public int UpdatePeriod
        {
            get
            {
                return this.updatePeriodValue;
            }

            set
            {
                if (value > 0)
                {
                    this.updatePeriodValue = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0");
                }
            }
        }

        #endregion

        ///////////////////////////////////////////////////////////////////////
        #region Public Properties

        [Category("Marquee")]
        [Browsable(true)]
        public Color LightColor
        {
            get
            {
                return this.lightColorValue;
            }
            set
            {
                // The LightColor property is only changed if the  
                // client provides a different value. Comparing values  
                // from the ToArgb method is the recommended test for 
                // equality between Color structs. 
                if (this.lightColorValue.ToArgb() != value.ToArgb())
                {
                    this.lightColorValue = value;
                    this.lightBrush = new SolidBrush(value);
                }
            }
        }

        [Category("Marquee")]
        [Browsable(true)]
        public Color DarkColor
        {
            get
            {
                return this.darkColorValue;
            }
            set
            {
                // The DarkColor property is only changed if the  
                // client provides a different value. Comparing values  
                // from the ToArgb method is the recommended test for 
                // equality between Color structs. 
                if (this.darkColorValue.ToArgb() != value.ToArgb())
                {
                    this.darkColorValue = value;
                    this.darkBrush = new SolidBrush(value);
                }
            }
        }

        #endregion

        ///////////////////////////////////////////////////////////////////////
        #region Implementation

        protected override void OnPaint(PaintEventArgs e)
        {
            // The text is painted in the light or dark color, 
            // depending on the current value of isLit. 
            this.ForeColor =
                this.isLit ? this.lightColorValue : this.darkColorValue;

            base.OnPaint(e);
        }

        // This method is called in the worker thread's context,  
        // so it must not make any calls into the MarqueeText control. 
        // Instead, it communicates to the control using the  
        // ProgressChanged event. 
        // 
        // The only work done in this event handler is 
        // to sleep for the number of milliseconds specified  
        // by UpdatePeriod, then raise the ProgressChanged event. 
        private void backgroundWorker1_DoWork(
            object sender,
            System.ComponentModel.DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            // This event handler will run until the client cancels 
            // the background task by calling CancelAsync. 
            while (!worker.CancellationPending)
            {
                // The Argument property of the DoWorkEventArgs 
                // object holds the value of UpdatePeriod, which  
                // was passed as the argument to the RunWorkerAsync 
                // method. 
                Thread.Sleep((int)e.Argument);

                // The DoWork eventhandler does not actually report 
                // progress; the ReportProgress event is used to  
                // periodically alert the control to update its state.
                worker.ReportProgress(0);
            }
        }

        // The ProgressChanged event is raised by the DoWork method. 
        // This event handler does work that is internal to the 
        // control. In this case, the text is toggled between its 
        // light and dark state, and the control is told to  
        // repaint itself. 
        private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            this.isLit = !this.isLit;
            this.Refresh();
        }


        private void InitializeComponent()
        {
            this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();

//  
// backgroundWorker1 
//  
            this.backgroundWorker1.WorkerReportsProgress = true;
            this.backgroundWorker1.WorkerSupportsCancellation = true;
            this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged);
            this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
        }

        #endregion
    }
}
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Windows.Forms.Design

<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
 GetType(IRootDesigner))> _
Public Class MarqueeControl
    Inherits UserControl

    ' Required designer variable. 
    Private components As System.ComponentModel.Container = Nothing 

    Public Sub New()
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()

        ' Minimize flickering during animation by enabling  
        ' double buffering.
        SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
    End Sub 

    ' <summary>  
    ' Clean up any resources being used. 
    ' </summary> 
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then 
            If (components IsNot Nothing) Then
                components.Dispose()
            End If 
        End If 
        MyBase.Dispose(disposing)
    End Sub 


    Public Sub Start()
        ' The MarqueeControl may contain any number of  
        ' controls that implement IMarqueeWidget, so  
        ' find each IMarqueeWidget child and call its 
        ' StartMarquee method. 
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then 
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)

                widget.StartMarquee()
            End If 
        Next cntrl
    End Sub 


    Public Sub [Stop]()
        ' The MarqueeControl may contain any number of  
        ' controls that implement IMarqueeWidget, so find 
        ' each IMarqueeWidget child and call its StopMarquee 
        ' method. 
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then 
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)

                widget.StopMarquee()
            End If 
        Next cntrl
    End Sub 

    Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs)
        MyBase.OnLayout(levent)

        ' Repaint all IMarqueeWidget children if the layout  
        ' has changed. 
        Dim cntrl As Control
        For Each cntrl In Me.Controls
            If TypeOf cntrl Is IMarqueeWidget Then 
                Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget)

                cntrl.PerformLayout()
            End If 
        Next cntrl
    End Sub

#Region "Component Designer generated code" 

    ' <summary>  
    ' Required method for Designer support - do not modify  
    ' the contents of this method with the code editor. 
    ' </summary> 
    Private Sub InitializeComponent()
    End Sub

#End Region

End Class
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace MarqueeControlLibrary
{
    [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
    public class MarqueeControl : UserControl
    {

        // Required designer variable. 
        private System.ComponentModel.Container components = null;

        public MarqueeControl()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            // Minimize flickering during animation by enabling  
            // double buffering.
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        }

        /// <summary>  
        /// Clean up any resources being used. 
        /// </summary> 
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        public void Start()
        {
            // The MarqueeControl may contain any number of  
            // controls that implement IMarqueeWidget, so  
            // find each IMarqueeWidget child and call its 
            // StartMarquee method. 
            foreach( Control cntrl in this.Controls )
            {
                if( cntrl is IMarqueeWidget )
                {
                    IMarqueeWidget widget = cntrl as IMarqueeWidget;
                    widget.StartMarquee();
                }
            }
        }

        public void Stop()
        {
            // The MarqueeControl may contain any number of  
            // controls that implement IMarqueeWidget, so find 
            // each IMarqueeWidget child and call its StopMarquee 
            // method. 
            foreach( Control cntrl in this.Controls )
            {
                if( cntrl is IMarqueeWidget )
                {
                    IMarqueeWidget widget = cntrl as IMarqueeWidget;
                    widget.StopMarquee();
                }
            }
        }

        protected override void OnLayout(LayoutEventArgs levent)
        {
            base.OnLayout (levent);

            // Repaint all IMarqueeWidget children if the layout  
            // has changed. 
            foreach( Control cntrl in this.Controls )
            {
                if( cntrl is IMarqueeWidget )
                {
                    Control control = cntrl as Control; 

                    control.PerformLayout();
                }
            }
        }

        #region Component Designer generated code
        /// <summary>  
        /// Required method for Designer support - do not modify  
        /// the contents of this method with the code editor. 
        /// </summary> 
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }
        #endregion
    }
}
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Windows.Forms.Design

' This control provides the custom UI for the LightShape property 
' of the MarqueeBorder. It is used by the LightShapeEditor. 
Public Class LightShapeSelectionControl
    Inherits System.Windows.Forms.UserControl

   Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square

    Private editorService As IWindowsFormsEditorService
   Private squarePanel As System.Windows.Forms.Panel
   Private circlePanel As System.Windows.Forms.Panel

   ' Required designer variable. 
   Private components As System.ComponentModel.Container = Nothing 


   ' This constructor takes a MarqueeLightShape value from the 
   ' design-time environment, which will be used to display 
   ' the initial state. 
    Public Sub New( _
    ByVal lightShape As MarqueeLightShape, _
    ByVal editorService As IWindowsFormsEditorService)
        ' This call is required by the Windows.Forms Form Designer.
        InitializeComponent()

        ' Cache the light shape value provided by the  
        ' design-time environment. 
        Me.lightShapeValue = lightShape

        ' Cache the reference to the editor service. 
        Me.editorService = editorService

        ' Handle the Click event for the two panels.  
        AddHandler Me.squarePanel.Click, AddressOf squarePanel_Click
        AddHandler Me.circlePanel.Click, AddressOf circlePanel_Click
    End Sub 

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then 

            ' Be sure to unhook event handlers 
            ' to prevent "lapsed listener" leaks. 
            RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click
            RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click

            If (components IsNot Nothing) Then
                components.Dispose()
            End If 

        End If 
        MyBase.Dispose(disposing)
    End Sub 

    ' LightShape is the property for which this control provides 
    ' a custom user interface in the Properties window. 
    Public Property LightShape() As MarqueeLightShape

        Get 
            Return Me.lightShapeValue
        End Get 

        Set(ByVal Value As MarqueeLightShape)
            If Me.lightShapeValue <> Value Then 
                Me.lightShapeValue = Value
            End If 
        End Set 

    End Property 

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        MyBase.OnPaint(e)

        Dim gCircle As Graphics = Me.circlePanel.CreateGraphics()
        Try 
            Dim gSquare As Graphics = Me.squarePanel.CreateGraphics()
            Try 
                ' Draw a filled square in the client area of 
                ' the squarePanel control.
                gSquare.FillRectangle( _
                Brushes.Red, _
                0, _
                0, _
                Me.squarePanel.Width, _
                Me.squarePanel.Height)

                ' If the Square option has been selected, draw a  
                ' border inside the squarePanel. 
                If Me.lightShapeValue = MarqueeLightShape.Square Then
                    gSquare.DrawRectangle( _
                    Pens.Black, _
                    0, _
                    0, _
                    Me.squarePanel.Width - 1, _
                    Me.squarePanel.Height - 1)
                End If 

                ' Draw a filled circle in the client area of 
                ' the circlePanel control.
                gCircle.Clear(Me.circlePanel.BackColor)
                gCircle.FillEllipse( _
                Brushes.Blue, _
                0, _
                0, _
                Me.circlePanel.Width, _
                Me.circlePanel.Height)

                ' If the Circle option has been selected, draw a  
                ' border inside the circlePanel. 
                If Me.lightShapeValue = MarqueeLightShape.Circle Then
                    gCircle.DrawRectangle( _
                    Pens.Black, _
                    0, _
                    0, _
                    Me.circlePanel.Width - 1, _
                    Me.circlePanel.Height - 1)
                End If 
            Finally
                gSquare.Dispose()
            End Try 
        Finally
            gCircle.Dispose()
        End Try 
    End Sub 

    Private Sub squarePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)

        Me.lightShapeValue = MarqueeLightShape.Square
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()

    End Sub 


    Private Sub circlePanel_Click( _
    ByVal sender As Object, _
    ByVal e As EventArgs)

        Me.lightShapeValue = MarqueeLightShape.Circle
        Me.Invalidate(False)
        Me.editorService.CloseDropDown()

    End Sub

#Region "Component Designer generated code" 

    '/ <summary>  
    '/ Required method for Designer support - do not modify  
    '/ the contents of this method with the code editor. 
    '/ </summary> 
    Private Sub InitializeComponent()
        Me.squarePanel = New System.Windows.Forms.Panel
        Me.circlePanel = New System.Windows.Forms.Panel
        Me.SuspendLayout()
        '  
        ' squarePanel 
        '  
        Me.squarePanel.Location = New System.Drawing.Point(8, 10)
        Me.squarePanel.Name = "squarePanel" 
        Me.squarePanel.Size = New System.Drawing.Size(60, 60)
        Me.squarePanel.TabIndex = 2
        '  
        ' circlePanel 
        '  
        Me.circlePanel.Location = New System.Drawing.Point(80, 10)
        Me.circlePanel.Name = "circlePanel" 
        Me.circlePanel.Size = New System.Drawing.Size(60, 60)
        Me.circlePanel.TabIndex = 3
        '  
        ' LightShapeSelectionControl 
        '  
        Me.Controls.Add(squarePanel)
        Me.Controls.Add(circlePanel)
        Me.Name = "LightShapeSelectionControl" 
        Me.Size = New System.Drawing.Size(150, 80)
        Me.ResumeLayout(False)
    End Sub

#End Region

End Class
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace MarqueeControlLibrary
{
    // This control provides the custom UI for the LightShape property 
    // of the MarqueeBorder. It is used by the LightShapeEditor. 
    public class LightShapeSelectionControl : System.Windows.Forms.UserControl
    {
        private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
        private IWindowsFormsEditorService editorService = null;
        private System.Windows.Forms.Panel squarePanel;
        private System.Windows.Forms.Panel circlePanel;
        
        // Required designer variable. 
        private System.ComponentModel.Container components = null;

        // This constructor takes a MarqueeLightShape value from the 
        // design-time environment, which will be used to display 
        // the initial state. 
        public LightShapeSelectionControl( 
            MarqueeLightShape lightShape,
            IWindowsFormsEditorService editorService )
        {
            // This call is required by the designer.
            InitializeComponent();

            // Cache the light shape value provided by the  
            // design-time environment. 
            this.lightShapeValue = lightShape;

            // Cache the reference to the editor service. 
            this.editorService = editorService;

            // Handle the Click event for the two panels.  
            this.squarePanel.Click += new EventHandler(squarePanel_Click);
            this.circlePanel.Click += new EventHandler(circlePanel_Click);
        }

        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                // Be sure to unhook event handlers 
                // to prevent "lapsed listener" leaks.
                this.squarePanel.Click -= 
                    new EventHandler(squarePanel_Click);
                this.circlePanel.Click -= 
                    new EventHandler(circlePanel_Click);

                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        // LightShape is the property for which this control provides 
        // a custom user interface in the Properties window. 
        public MarqueeLightShape LightShape
        {
            get
            {
                return this.lightShapeValue;
            }
            
            set
            {
                if( this.lightShapeValue != value )
                {
                    this.lightShapeValue = value;
                }
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint (e);

            using( 
                Graphics gSquare = this.squarePanel.CreateGraphics(),
                gCircle = this.circlePanel.CreateGraphics() )
            {   
                // Draw a filled square in the client area of 
                // the squarePanel control.
                gSquare.FillRectangle(
                    Brushes.Red, 
                    0,
                    0,
                    this.squarePanel.Width,
                    this.squarePanel.Height
                    );

                // If the Square option has been selected, draw a  
                // border inside the squarePanel. 
                if( this.lightShapeValue == MarqueeLightShape.Square )
                {
                    gSquare.DrawRectangle( 
                        Pens.Black,
                        0,
                        0,
                        this.squarePanel.Width-1,
                        this.squarePanel.Height-1);
                }

                // Draw a filled circle in the client area of 
                // the circlePanel control.
                gCircle.Clear( this.circlePanel.BackColor );
                gCircle.FillEllipse( 
                    Brushes.Blue, 
                    0,
                    0,
                    this.circlePanel.Width, 
                    this.circlePanel.Height
                    );

                // If the Circle option has been selected, draw a  
                // border inside the circlePanel. 
                if( this.lightShapeValue == MarqueeLightShape.Circle )
                {
                    gCircle.DrawRectangle( 
                        Pens.Black,
                        0,
                        0,
                        this.circlePanel.Width-1,
                        this.circlePanel.Height-1);
                }
            }   
        }

        private void squarePanel_Click(object sender, EventArgs e)
        {
            this.lightShapeValue = MarqueeLightShape.Square;
            
            this.Invalidate( false );

            this.editorService.CloseDropDown();
        }

        private void circlePanel_Click(object sender, EventArgs e)
        {
            this.lightShapeValue = MarqueeLightShape.Circle;

            this.Invalidate( false );

            this.editorService.CloseDropDown();
        }

        #region Component Designer generated code
        /// <summary>  
        /// Required method for Designer support - do not modify  
        /// the contents of this method with the code editor. 
        /// </summary> 
        private void InitializeComponent()
        {
            this.squarePanel = new System.Windows.Forms.Panel();
            this.circlePanel = new System.Windows.Forms.Panel();
            this.SuspendLayout();
//  
// squarePanel 
//  
            this.squarePanel.Location = new System.Drawing.Point(8, 10);
            this.squarePanel.Name = "squarePanel";
            this.squarePanel.Size = new System.Drawing.Size(60, 60);
            this.squarePanel.TabIndex = 2;
//  
// circlePanel 
//  
            this.circlePanel.Location = new System.Drawing.Point(80, 10);
            this.circlePanel.Name = "circlePanel";
            this.circlePanel.Size = new System.Drawing.Size(60, 60);
            this.circlePanel.TabIndex = 3;
//  
// LightShapeSelectionControl 
//  
            this.Controls.Add(this.squarePanel);
            this.Controls.Add(this.circlePanel);
            this.Name = "LightShapeSelectionControl";
            this.Size = new System.Drawing.Size(150, 80);
            this.ResumeLayout(false);

        }
        #endregion

        
    }
}
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Diagnostics
Imports System.Windows.Forms
Imports System.Windows.Forms.Design

Namespace MarqueeControlLibrary.Design

    <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
    Public Class MarqueeBorderDesigner
        Inherits ParentControlDesigner

        Public Sub New()
            Trace.WriteLine("MarqueeBorderDesigner")
        End Sub 

        Public Property Visible() As Boolean 
            Get 
                Return CBool(ShadowProperties("Visible"))
            End Get 
            Set(ByVal Value As Boolean)
                Me.ShadowProperties("Visible") = Value
            End Set 
        End Property 


        Public Property Enabled() As Boolean 
            Get 
                Return CBool(ShadowProperties("Enabled"))
            End Get 
            Set(ByVal Value As Boolean)
                Me.ShadowProperties("Enabled") = Value
            End Set 
        End Property 

        Protected Overrides Sub PreFilterProperties( _
        ByVal properties As IDictionary)

            MyBase.PreFilterProperties(properties)

            If properties.Contains("Padding") Then
                properties.Remove("Padding")
            End If

            properties("Visible") = _
            TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
            CType(properties("Visible"), PropertyDescriptor), _
            New Attribute(-1) {})

            properties("Enabled") = _
            TypeDescriptor.CreateProperty(GetType(MarqueeBorderDesigner), _
            CType(properties("Enabled"), _
            PropertyDescriptor), _
            New Attribute(-1) {})

        End Sub 

        Private Sub OnVerbRunTest( _
        ByVal sender As Object, _
        ByVal e As EventArgs)

            Dim widget As IMarqueeWidget = CType(Me.Control, IMarqueeWidget)
            widget.StartMarquee()

        End Sub 


        Private Sub OnVerbStopTest( _
        ByVal sender As Object, _
        ByVal e As EventArgs)

            Dim widget As IMarqueeWidget = CType(Me.Control, IMarqueeWidget)
            widget.StopMarquee()

        End Sub 

    End Class 
End Namespace
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace MarqueeControlLibrary.Design
{
    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
    public class MarqueeBorderDesigner : ParentControlDesigner
    {

        public MarqueeBorderDesigner()
        {
            Trace.WriteLine("MarqueeBorderDesigner");
        }

        public bool Visible
        {
            get
            {
                return (bool)ShadowProperties["Visible"];
            }
            set
            {
                this.ShadowProperties["Visible"] = value;
            }
        }

        public bool Enabled
        {
            get
            {
                return (bool)ShadowProperties["Enabled"];
            }
            set
            {
                this.ShadowProperties["Enabled"] = value;
            }
        }

        protected override void PreFilterProperties(IDictionary properties)
        {
            base.PreFilterProperties(properties);

            if (properties.Contains("Padding"))
            {
                properties.Remove("Padding");
            }

            properties["Visible"] = TypeDescriptor.CreateProperty(
                typeof(MarqueeBorderDesigner),
                (PropertyDescriptor)properties["Visible"],
                new Attribute[0]);

            properties["Enabled"] = TypeDescriptor.CreateProperty(
                typeof(MarqueeBorderDesigner),
                (PropertyDescriptor)properties["Enabled"],
                new Attribute[0]);
        }

        private void OnVerbRunTest(object sender, EventArgs e)
        {
            IMarqueeWidget widget = this.Control as IMarqueeWidget;

            widget.StartMarquee();
        }

        private void OnVerbStopTest(object sender, EventArgs e)
        {
            IMarqueeWidget widget = this.Control as IMarqueeWidget;

            widget.StopMarquee();
        }
    }
}
Imports System
Imports System.Collections
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Diagnostics
Imports System.Drawing.Design
Imports System.Windows.Forms
Imports System.Windows.Forms.Design

Namespace MarqueeControlLibrary.Design

    <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
    ToolboxItemFilterType.Require), _
    ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
    ToolboxItemFilterType.Require)> _
    <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
    Public Class MarqueeControlRootDesigner
        Inherits DocumentDesigner

        Public Sub New()
            Trace.WriteLine("MarqueeControlRootDesigner ctor")
        End Sub 

        Public Overrides Sub Initialize(ByVal component As IComponent)

            MyBase.Initialize(component)

            Dim cs As IComponentChangeService = _
            CType(GetService(GetType(IComponentChangeService)), _
            IComponentChangeService)

            If (cs IsNot Nothing) Then 
                AddHandler cs.ComponentChanged, AddressOf OnComponentChanged
            End If 

            Me.Verbs.Add(New DesignerVerb("Run Test", _
            New EventHandler(AddressOf OnVerbRunTest)))

            Me.Verbs.Add(New DesignerVerb("Stop Test", _
            New EventHandler(AddressOf OnVerbStopTest)))
        End Sub 

        Private Sub OnComponentChanged( _
        ByVal sender As Object, _
        ByVal e As ComponentChangedEventArgs)
            If TypeOf e.Component Is IMarqueeWidget Then 
                Me.Control.Refresh()
            End If 
        End Sub 

        Private Sub OnVerbRunTest( _
        ByVal sender As Object, _
        ByVal e As EventArgs)

            Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
            c.Start()

        End Sub 

        Private Sub OnVerbStopTest( _
        ByVal sender As Object, _
        ByVal e As EventArgs)

            Dim c As MarqueeControl = CType(Me.Control, MarqueeControl)
            c.Stop()

        End Sub 

    End Class 
End Namespace
using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace MarqueeControlLibrary.Design
{
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
    public class MarqueeControlRootDesigner : DocumentDesigner
    {

        public MarqueeControlRootDesigner()
        {
            Trace.WriteLine("MarqueeControlRootDesigner ctor");
        }

        public override void Initialize(IComponent component)
        {
            base.Initialize(component);

            IComponentChangeService cs =
                GetService(typeof(IComponentChangeService)) 
                as IComponentChangeService;

            if (cs != null)
            {
                cs.ComponentChanged +=
                    new ComponentChangedEventHandler(OnComponentChanged);
            }

            this.Verbs.Add(
                new DesignerVerb("Run Test",
                new EventHandler(OnVerbRunTest))
                );

            this.Verbs.Add(
                new DesignerVerb("Stop Test",
                new EventHandler(OnVerbStopTest))
                );
        }

        private void OnComponentChanged(
            object sender,
            ComponentChangedEventArgs e)
        {
            if (e.Component is IMarqueeWidget)
            {
                this.Control.Refresh();
            }
        }

        private void OnVerbRunTest(object sender, EventArgs e)
        {
            MarqueeControl c = this.Control as MarqueeControl;

            c.Start();
        }

        private void OnVerbStopTest(object sender, EventArgs e)
        {
            MarqueeControl c = this.Control as MarqueeControl;

            c.Stop();
        }
    }
}

Compiling the Code

Create a Windows Control Library project to host these files. Name the project "MarqueeControlLibrary".

Your MarqueeControlLibrary project will require references to the System.Design, System.Drawing, and System.Windows.Forms assemblies.