Compartir a través de


Cómo: Crear un control de formularios Windows Forms que aproveche las características en tiempo de diseño

Actualización: noviembre 2007

En el siguiente ejemplo se muestra cómo crear a un control personalizado y un diseñador personalizado asociado. Una vez generada la biblioteca, será posible generar implementaciones MarqueeControl personalizadas que se ejecuten en un formulario.

Visual Studio ofrece una amplia compatibilidad para esta tarea.

Ejemplo

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();
        }
    }
}

Compilar el código

Cree un proyecto Biblioteca de controles de Windows para alojar estos archivos. Denomine al proyecto "MarqueeControlLibrary".

El proyecto MarqueeControlLibrary requerirá referencias a los ensamblados System.Design, System.Drawing y System.Windows.Forms.