Export (0) Print
Expand All

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

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

There is extensive support for this task in Visual Studio.

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
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
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
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
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
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
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

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

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

Note Note

You must add a reference to the design-time assembly, System.Design.dll. This assembly is not included in the .NET Framework 4 Client Profile. To add a reference to System.Design.dll, you must change the project's Target Framework to .NET Framework 4.

Show:
© 2015 Microsoft