Поделиться через


Практическое руководство. Создание элемента управления Windows Forms, в котором используются преимущества функций, применяемых во время разработки

Обновлен: Ноябрь 2007

В следующем примере демонстрируется, как создать пользовательский элемент управления и связанный с ним пользовательский конструктор. После создания библиотеки можно будет построить пользовательские реализации MarqueeControl, выполняемые в форме.

В Visual Studio предусмотрена расширенная поддержка этой задачи.

Пример

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

Компиляция кода

Создайте проект библиотеки элементов управления Windows для размещения этих файлов. Назовите проект "MarqueeControlLibrary".

Для проекта "MarqueeControlLibrary" потребуются ссылки на сборки "System.Design", "System.Drawing" и "System.Windows.Forms".