チュートリアル : Visual Studio のデザイン時機能を活用した Windows フォーム コントロールの作成

更新 : 2007 年 11 月

カスタム コントロールのデザイン時の操作性は、関連付けられたカスタム デザイナを作成することで向上させることができます。

このチュートリアルでは、カスタム コントロールのカスタム デザイナを作成する方法について説明します。ここでは、MarqueeControl 型と、それに関連付けられた MarqueeControlRootDesigner というデザイナ クラスを実装します。

MarqueeControl 型は、光のアニメーションを表示し、テキストが点滅する、劇場の看板のようなディスプレイを実装します。

このコントロールのデザイナは、デザイン環境と対話して、独自のデザイン時操作を提供します。カスタム デザイナを使用すると、光のアニメーションと点滅するテキストを多彩に組み合わせたカスタム MarqueeControl 実装をアセンブルできます。アセンブルされたコントロールは、他の Windows フォーム コントロールと同じようにフォーム上で使用できます。

このチュートリアルでは、以下のタスクを行います。

  • プロジェクトの作成

  • コントロール ライブラリ プロジェクトの作成

  • カスタム コントロール プロジェクトの参照

  • カスタム コントロールとそのカスタム デザイナの定義

  • カスタム コントロールのインスタンスの作成

  • デザイン時デバッグのためのプロジェクト設定

  • カスタム コントロールの実装

  • カスタム コントロールの子コントロールの作成

  • MarqueeBorder の子コントロールの作成

  • プロパティをシャドウおよびフィルタ処理するカスタム デザイナの作成

  • コンポーネントの変更処理

  • カスタム デザイナへのデザイナ動詞の追加

  • カスタム UITypeEditor の作成

  • デザイナでのカスタム コントロールのテスト

完成したカスタム コントロールは次のようになります。

可能な MarqueeControl 配置

完全なコードの一覧については、「方法 : デザイン時機能を活用した Windows フォーム コントロールを作成する」を参照してください。

35ea88wb.alert_note(ja-jp,VS.90).gifメモ :

使用している設定またはエディションによっては、表示されるダイアログ ボックスやメニュー コマンドがヘルプに記載されている内容と異なる場合があります。設定を変更するには、[ツール] メニューの [設定のインポートとエクスポート] をクリックします。詳細については、「Visual Studio の設定」を参照してください。

前提条件

このチュートリアルを実行するための要件は次のとおりです。

  • Visual Studio がインストールされているコンピュータで、Windows フォーム アプリケーション プロジェクトを作成および実行するための十分なアクセス許可が付与されていること。

プロジェクトの作成

最初にアプリケーション プロジェクトを作成します。このプロジェクトを使用して、カスタム コントロールをホストするアプリケーションをビルドします。

プロジェクトを作成するには

コントロール ライブラリ プロジェクトの作成

次に、コントロール ライブラリ プロジェクトを作成します。新しいカスタム コントロールとそれに対応するカスタム デザイナを作成します。

コントロール ライブラリ プロジェクトを作成するには

  1. Windows コントロール ライブラリ プロジェクトをソリューションに追加します。詳細については、「[新しいプロジェクトの追加] ダイアログ ボックス」を参照してください。プロジェクトに "MarqueeControlLibrary" という名前を付けます。

  2. ソリューション エクスプローラを使用して、選択した言語に応じて "UserControl1.cs" または "UserControl1.vb" という名前のソース ファイルを削除して、プロジェクトの既定のコントロールを削除します。詳細については、「方法 : 項目を除去、削除、除外する」を参照してください。

  3. 新しい UserControl アイテムを MarqueeControlLibrary プロジェクトに追加します。新しいソース ファイルに、"MarqueeControl" という基本名を付けます。

  4. ソリューション エクスプローラを使用して、MarqueeControlLibrary プロジェクト内に新しいフォルダを作成します。詳細については、「方法 : 新しいプロジェクト項目を追加する」を参照してください。新しいフォルダに "Design" という名前を付けます。

  5. Design フォルダを右クリックして、新しいクラスを追加します。ソース ファイルに、"MarqueeControlRootDesigner" という基本名を付けます。

  6. System.Design アセンブリの型を使用して、この参照を MarqueeControlLibrary プロジェクトに追加する必要があります。詳細については、「方法 : Visual Studio で参照を追加および削除する (C#)」を参照してください。

カスタム コントロール プロジェクトの参照

MarqueeControlTest プロジェクトを使用して、カスタム コントロールをテストします。テスト プロジェクトは、MarqueeControlLibrary アセンブリにプロジェクト参照を追加すると、カスタム コントロールを認識します。

カスタム コントロール プロジェクトを参照するには

  • MarqueeControlTest プロジェクトで、プロジェクト参照を MarqueeControlLibrary アセンブリに追加します。MarqueeControlLibrary アセンブリを直接参照する代わりに、必ず、[参照の追加] ダイアログ ボックスの [プロジェクト] タブを使用してください。

カスタム コントロールとそのカスタム デザイナの定義

カスタム コントロールは、UserControl クラスから派生します。これにより、カスタム コントロールに他のコントロールを含めることができ、既定の機能を豊富に割り当てることができます。

カスタム コントロールには、カスタム デザイナが関連付けられます。これにより、カスタム コントロールに特化された、独自のデザイン操作を実現できます。

カスタム コントロールをデザイナに関連付けるには、DesignerAttribute クラスを使用します。カスタム コントロールのデザイン時動作全体を開発するため、カスタム デザイナは IRootDesigner インターフェイスを実装します。

カスタム コントロールとそのカスタム デザイナを定義するには

  1. MarqueeControl ソース ファイルをコード エディタで開きます。ファイルの先頭に次の名前空間をインポートします。

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Drawing
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  2. DesignerAttribute を MarqueeControl クラス宣言に追加します。これで、カスタム コントロールがデザイナに関連付けられます。

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _
     GetType(IRootDesigner))> _
    Public Class MarqueeControl
        Inherits UserControl
    
     [Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )]
        public class MarqueeControl : UserControl
        {
    
  3. MarqueeControlRootDesigner ソース ファイルをコード エディタで開きます。ファイルの先頭に次の名前空間をインポートします。

    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
    
    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;
    
  4. DocumentDesigner クラスを継承するように MarqueeControlRootDesigner の宣言を変更します。ToolboxItemFilterAttribute を適用して、デザイナとツールボックスとの対話処理を指定します。

    メモ   MarqueeControlRootDesigner クラスの定義を、"MarqueeControlLibrary.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
    
    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
        {
    
  5. MarqueeControlRootDesigner クラスのコンストラクタを定義します。WriteLine ステートメントをコンストラクタ本体に挿入します。これはデバッグに役立ちます。

    Public Sub New()
        Trace.WriteLine("MarqueeControlRootDesigner ctor")
    End Sub
    
    public MarqueeControlRootDesigner()
    {
        Trace.WriteLine("MarqueeControlRootDesigner ctor");
    }
    

カスタム コントロールのインスタンスの作成

コントロールのデザイン時のカスタム動作を確認するために、コントロールのインスタンスを MarqueeControlTest プロジェクトのフォームに配置します。

カスタム コントロールのインスタンスを作成するには

  1. 新しい UserControl アイテムを MarqueeControlTest プロジェクトに追加します。新しいソース ファイルに、"DemoMarqueeControl" という基本名を付けます。

  2. DemoMarqueeControl ファイルをコード エディタで開きます。ファイルの先頭に、次のように MarqueeControlLibrary 名前空間をインポートします。

Imports MarqueeControlLibrary
using MarqueeControlLibrary;
  1. MarqueeControl クラスを継承するように DemoMarqueeControl の宣言を変更します。

  2. プロジェクトをビルドします。

  3. Windows フォーム デザイナで Form1 を開きます。

  4. ツールボックスの [MarqueeControlTest コンポーネント] タブを見つけて開きます。ツールボックスの DemoMarqueeControl コントロールをフォームにドラッグします。

  5. プロジェクトをビルドします。

デザイン時デバッグのためのプロジェクト設定

デザイン時のカスタム操作を開発するときは、コントロールとコンポーネントのデバッグが必要になります。プロジェクトでは、簡単な方法でデザイン時のデバッグを有効に設定できます。詳細については、「チュートリアル : カスタム Windows フォーム コントロールのデザイン時のデバッグ」を参照してください。

デザイン時デバッグのためにプロジェクトを設定するには

  1. MarqueeControlLibrary プロジェクトを右クリックし、[プロパティ] をクリックします。

  2. [MarqueeControlLibrary プロパティ ページ] ダイアログ ボックスで、[構成プロパティ] ページを選択します。

  3. [開始動作] セクションで、[外部プログラムの開始] を選択します。Visual Studio の別個のインスタンスをデバッグするので、省略記号 (VisualStudioEllipsesButton スクリーンショット) ボタンをクリックして、Visual Studio IDE を参照します。実行可能ファイルの名前は devenv.exe で、既定の場所にインストールした場合、ファイルのパスは "%programfiles%\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe" になります。

  4. [OK] をクリックし、ダイアログ ボックスを閉じます。

  5. MarqueeControlLibrary プロジェクトを右クリックし、[スタートアップ プロジェクトに設定] をクリックして、このデバッグ構成を有効にします。

チェックポイント

これで、カスタム コントロールのデザイン時動作をデバッグできるようになりました。デバッグ環境が適切に設定されていることを確認したら、カスタム コントロールとカスタム デザイナの関連付けをテストします。

デバッグ環境およびデザイナとの関連付けをテストするには

  1. コード エディタで MarqueeControlRootDesigner ソース ファイルを開き、WriteLine ステートメントにブレークポイントを配置します。

  2. F5 キーを押してデバッグ セッションを開始します。Visual Studio の新しいインスタンスが作成されることを確認してください。

  3. Visual Studio の新しいインスタンスで、"MarqueeControlTest" ソリューションを開きます。[ファイル] メニューの [最近使ったプロジェクト] をクリックすると、ソリューションが簡単に見つかります。"MarqueeControlTest.sln" ソリューション ファイルが、最後に使用したファイルとして表示されます。

  4. デザイナで DemoMarqueeControl を開きます。Visual Studio のデバッグ インスタンスがフォーカスを取得し、ブレークポイントで実行が停止します。F5 キーを押して、デバッグ セッションを継続します。

この時点で、カスタム コントロールとそれに関連付けられたカスタム デザイナを開発し、デバッグする準備がすべて整いました。このチュートリアルの残りの部分では、コントロールとデザイナの機能の実装について詳しく説明します。

カスタム コントロールの実装

MarqueeControl は、わずかにカスタマイズされた UserControl コントロールです。このコントロールは、マーキー アニメーションを開始する Start と、アニメーションを停止する Stop の 2 つのメソッドを公開します。MarqueeControl は、IMarqueeWidget インターフェイスを実装する子コントロールを含むため、Start と Stop は各子コントロールを列挙し、IMarqueeWidget を実装する子コントロールごとに StartMarquee メソッドと StopMarquee メソッドをそれぞれ呼び出します。

MarqueeBorder コントロールと MarqueeText コントロールの外観はレイアウトによって異なるため、MarqueeControl は OnLayout メソッドをオーバーライドし、同じ型の子コントロールで PerformLayout を呼び出します。

これが MarqueeControl のカスタマイズの範囲です。実行時の機能は、MarqueeBorder コントロールと MarqueeText コントロールによって実装され、デザイン時の機能は、MarqueeBorderDesigner クラスと MarqueeControlRootDesigner クラスによって実装されます。

カスタム コントロールを実装するには

  1. MarqueeControl ソース ファイルをコード エディタで開きます。Start メソッドと Stop メソッドを実装します。

    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
    
    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();
            }
        }
    }
    
  2. OnLayout メソッドをオーバーライドします。

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

カスタム コントロールの子コントロールの作成

MarqueeControl は、MarqueeBorder コントロールと MarqueeText コントロールという 2 種類の子コントロールをホストします。

  • MarqueeBorder: このコントロールは、その端に "光" の境界線を描画します。光は連続して点滅するため、境界線を移動するように見えます。光の点滅速度は、UpdatePeriod というプロパティで制御します。コントロールのその他の外観は、これ以外のいくつかのカスタム プロパティによって設定します。アニメーションの開始と停止は、StartMarquee および StopMarquee という 2 つのメソッドで制御します。

  • MarqueeText: このコントロールは、点滅する文字列を描画します。テキストの点滅速度は、MarqueeBorder コントロールと同様に UpdatePeriod プロパティで制御します。また、MarqueeText コントロールには、MarqueeBorder コントロールと同様に StartMarquee メソッドと StopMarquee メソッドもあります。

デザイン時に MarqueeControlRootDesigner では、これら 2 種類のコントロールを自由に組み合わせて MarqueeControl に追加できます。

2 つのコントロールの共通機能は、IMarqueeWidget というインターフェイスに組み込まれます。これにより、MarqueeControl が Marquee 関連の子コントロールを探索し、特別に処理できます。

周期的なアニメーション機能を実装するには、System.ComponentModel 名前空間の BackgroundWorker オブジェクトを使用します。Timer オブジェクトを使用することもできますが、多くの IMarqueeWidget オブジェクトが存在するときは、単一の UI スレッドではアニメーションに対応できなくなることがあります。

カスタム コントロールの子コントロールを作成するには

  1. 新しいクラス アイテムを MarqueeControlLibrary プロジェクトに追加します。新しいソース ファイルに、"IMarqueeWidget" という基本名を付けます。

  2. コード エディタで IMarqueeWidget ソース ファイルを開き、次のように class から interface に宣言を変更します。

    ' This interface defines the contract for any class that is to
    ' be used in constructing a MarqueeControl.
    Public Interface IMarqueeWidget
    
    // This interface defines the contract for any class that is to
    // be used in constructing a MarqueeControl.
    public interface IMarqueeWidget
    {
    
  3. 次のコードを IMarqueeWidget インターフェイスに追加し、マーキー アニメーションを操作する 2 つのメソッドとプロパティを公開します。

    ' 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
    
    // 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;
        }
    }
    
  4. 新しいカスタム コントロール アイテムを MarqueeControlLibrary プロジェクトに追加します。新しいソース ファイルに、"MarqueeText" という基本名を付けます。

  5. ツールボックスの BackgroundWorker コンポーネントを MarqueeText コントロールにドラッグします。このコンポーネントにより、MarqueeText コントロールが非同期的に更新されます。

  6. [プロパティ] ウィンドウで、BackgroundWorker コンポーネントの WorkerReportsProgess プロパティと WorkerSupportsCancellation プロパティを true に設定します。これらの設定により、BackgroundWorker コンポーネントが周期的に ProgressChanged イベントを発生させたり、非同期の更新をキャンセルしたりできます。詳細については、「BackgroundWorker コンポーネント」を参照してください。

  7. MarqueeText ソース ファイルをコード エディタで開きます。ファイルの先頭に次の名前空間をインポートします。

    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
    
    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;
    
  8. MarqueeText の宣言を次のように変更し、Label を継承すると共に、IMarqueeWidget インターフェイスを実装します。

    <ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeText
        Inherits Label
        Implements IMarqueeWidget
    
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)]
    public partial class MarqueeText : Label, IMarqueeWidget
    {
    
  9. 公開されたプロパティに対応するインスタンス変数を宣言し、コンストラクタで初期化します。isLit フィールドで、LightColor プロパティで指定された色でテキストを描画するかどうかを指定します。

    ' 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
    
    // 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);
    }
    
  10. IMarqueeWidget インターフェイスを実装します。

    StartMarquee メソッドと StopMarquee メソッドが、BackgroundWorker コンポーネントの RunWorkerAsync メソッドと CancelAsync メソッドを呼び出して、アニメーションを開始および停止します。

    Category 属性と Browsable 属性が UpdatePeriod プロパティに適用され、その結果、このプロパティが [プロパティ] ウィンドウの "Marquee" というカスタム セクションに表示されます。

    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
    
    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");
            }
        }
    }
    
  11. プロパティのアクセサを実装します。LightColor と DarkColor という 2 つのプロパティをクライアントに公開します。Category 属性と Browsable 属性がこれらのプロパティに適用され、その結果、これらのプロパティが [プロパティ] ウィンドウの "Marquee" というカスタム セクションに表示されます。

    <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 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);
            }
        }
    }
    
  12. BackgroundWorker コンポーネントの DoWork イベントと ProgressChanged イベントのハンドラを実装します。

    DoWork イベント ハンドラは、UpdatePeriod で指定したミリ秒数の間スリープした後、コードが CancelAsync を呼び出してアニメーションを停止するまで ProgressChanged イベントを発生させます。

    ProgressChanged イベント ハンドラは、テキストを明るい状態と暗い状態の間で切り替えて、点滅しているように表示します。

    ' 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
    
    // 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();
    }
    
    
  13. OnPaint メソッドをオーバーライドして、アニメーションを有効にします。

    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
    
    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);
    }
    
  14. F6 キーを押してソリューションをビルドします。

MarqueeBorder の子コントロールの作成

MarqueeBorder コントロールは、MarqueeText コントロールよりもわずかに洗練されています。このコントロールにはより多くのプロパティがあり、OnPaint メソッドのアニメーションはより複雑になっています。ただし、MarqueeText コントロールは基本的には MarqueeText コントロールと同じです。

MarqueeBorder コントロールは子コントロールを含むことができるので、Layout イベントを認識する必要があります。

MarqueeBorder コントロールを作成するには

  1. 新しいカスタム コントロール アイテムを MarqueeControlLibrary プロジェクトに追加します。新しいソース ファイルに、"MarqueeBorder" という基本名を付けます。

  2. ツールボックスから BackgroundWorker コンポーネントを MarqueeBorder コントロールにドラッグします。このコンポーネントにより、MarqueeBorder コントロールが非同期的に更新されます。

  3. [プロパティ] ウィンドウで、BackgroundWorker コンポーネントの WorkerReportsProgess プロパティと WorkerSupportsCancellation プロパティを true に設定します。これらの設定により、BackgroundWorker コンポーネントが周期的に ProgressChanged イベントを発生させたり、非同期の更新をキャンセルしたりできます。詳細については、「BackgroundWorker コンポーネント」を参照してください。

  4. [プロパティ] ウィンドウの [イベント] をクリックします。DoWork イベントと ProgressChanged イベントのハンドラをアタッチします。

  5. MarqueeBorder ソース ファイルをコード エディタで開きます。ファイルの先頭に次の名前空間をインポートします。

    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
    
    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;
    
  6. MarqueeBorder の宣言を次のように変更し、Panel を継承すると共に、IMarqueeWidget インターフェイスを実装します。

    <Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _
    ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _
    ToolboxItemFilterType.Require)> _
    Partial Public Class MarqueeBorder
        Inherits Panel
        Implements IMarqueeWidget
    
    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {
    
  7. MarqueeBorder コントロールの状態を管理する MarqueeSpinDirection と MarqueeLightShape の 2 つの列挙型を宣言します。前者は、光が境界線を "回る" 方向を指定し、後者は、光の形状 (四角形または円形) を指定します。これらの宣言を、MarqueeBorder クラス宣言の前に配置します。

    ' 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
    
    // 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
    }
    
  8. 公開されたプロパティに対応するインスタンス変数を宣言し、コンストラクタで初期化します。

    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
    
    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);
    }
    
  9. IMarqueeWidget インターフェイスを実装します。

    StartMarquee メソッドと StopMarquee メソッドが、BackgroundWorker コンポーネントの RunWorkerAsync メソッドと CancelAsync メソッドを呼び出して、アニメーションを開始および停止します。

    MarqueeBorder コントロールは子コントロールを含むことができるので、StartMarquee メソッドは、すべての子コントロールを列挙し、IMarqueeWidget を実装する子コントロールで StartMarquee を呼び出します。StopMarquee メソッドも同じような実装になります。

    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
    
    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");
            }
        }
    }
    
    
  10. プロパティのアクセサを実装します。MarqueeBorder コントロールには、その外観を制御するプロパティがいくつかあります。

    <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
    
    [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;
        }
    }
    
    
  11. BackgroundWorker コンポーネントの DoWork イベントと ProgressChanged イベントのハンドラを実装します。

    DoWork イベント ハンドラは、UpdatePeriod で指定したミリ秒数の間スリープした後、コードが CancelAsync を呼び出してアニメーションを停止するまで ProgressChanged イベントを発生させます。

    ProgressChanged イベント ハンドラは、他の光の明暗の状態を決定するための基準になる "基本" 光の位置をインクリメントし、Refresh メソッドを呼び出して、コントロール自体を再描画させます。

    ' 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 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();
    }
    
  12. IsLit ヘルパー メソッドと DrawLight ヘルパー メソッドを実装します。

    IsLit メソッドは、特定の位置での光の色を指定します。"点灯中" の光は、LightColor プロパティで指定された色で描画され、"暗い" 光は、DarkColor プロパティで指定された色で描画されます。

    DrawLight メソッドは、適切な色、形状、および位置を使用して光を描画します。

    ' 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 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;
                }
        }
    }
    
  13. OnLayout メソッドと OnPaint メソッドをオーバーライドします。

    OnPaint メソッドは、MarqueeBorder コントロールの端に沿って光を描画します。

    OnPaint メソッドは、MarqueeBorder コントロールの寸法に依存するため、レイアウトを変更したときには必ず呼び出す必要があります。このために、OnLayout をオーバーライドして、Refresh を呼び出します。

    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
    
    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++;
            }
        }
    }
    

プロパティをシャドウおよびフィルタ処理するカスタム デザイナの作成

MarqueeControlRootDesigner クラスは、ルート デザイナの実装を提供します。MarqueeControl で動作するこのデザイナに加えて、MarqueeBorder コントロールに特別に関連付けられたカスタム デザイナも必要です。このカスタム デザイナは、カスタム ルート デザイナのコンテキストに応じたカスタム動作を実現します。

具体的には、MarqueeBorderDesigner は、MarqueeBorder コントロールの特定のプロパティを "シャドウ" およびフィルタ処理して、デザイン環境との対話方法を変更します。

コンポーネントのプロパティ アクセサの呼び出しを受け取ることを "シャドウ" と言います。これにより、ユーザーによって設定された値をデザイナが追跡でき、デザインされるコンポーネントにその値を渡すこともできます。

たとえば、Visible プロパティと Enabled プロパティを MarqueeBorderDesigner で Shadows' を実行すると、デザイン時にユーザーが MarqueeBorder コントロールを非表示にしたり、無効にしたりできなくなります。

また、デザイナはプロパティを追加したり削除したりできます。たとえば、MarqueeBorder コントロールでは、LightSize プロパティで指定された光のサイズに基づいて埋め込みがプログラムによって設定されるため、デザイン時に Padding プロパティが削除されます。

MarqueeBorderDesigner の基本クラスは ComponentDesigner で、デザイン時にコントロールによって公開される属性、プロパティ、およびイベントを変更できる以下のメソッドがあります。

これらのメソッドを使用してコンポーネントのパブリック インターフェイスを変更するときは、次の規則に従う必要があります。

  • PreFilter メソッドのみで、アイテムを追加または削除します。

  • PostFilter メソッドのみで、既存のアイテムを変更します。

  • PreFilter メソッドでは、必ず最初に基本実装を呼び出します。

  • PostFilter メソッドでは、必ず最後に基本実装を呼び出します。

これらの規則に従うことにより、デザイン時環境のすべてのデザイナが、デザインされるすべてのコンポーネントで一貫したビューを持つことができます。

ComponentDesigner クラスは、Shadows' を実行されたプロパティの値を管理するためのディクショナリを提供するため、特定のインスタンス変数を作成する必要がありません。

プロパティをシャドウおよびフィルタ処理するカスタム デザイナを作成するには

  1. Design フォルダを右クリックして、新しいクラスを追加します。ソース ファイルに、"MarqueeBorderDesigner" という基本名を付けます。

  2. MarqueeBorderDesigner ソース ファイルをコード エディタで開きます。ファイルの先頭に次の名前空間をインポートします。

    Imports System
    Imports System.Collections
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Diagnostics
    Imports System.Windows.Forms
    Imports System.Windows.Forms.Design
    
    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Windows.Forms.Design;
    
  3. ParentControlDesigner を継承するように MarqueeBorderDesigner の宣言を変更します。

    MarqueeBorder コントロールは子コントロールを含むことができるので、MarqueeBorderDesigner は、親子の対話を処理する ParentControlDesigner を継承します。

    Namespace MarqueeControlLibrary.Design
    
        <System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name:="FullTrust")> _
        Public Class MarqueeBorderDesigner
            Inherits ParentControlDesigner
    
    namespace MarqueeControlLibrary.Design
    {
        [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] 
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
  4. PreFilterProperties の基本実装をオーバーライドします。

    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
    
    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]);
    }
    
  5. Enabled プロパティと Visible プロパティを実装します。これらの実装によってコントロールのプロパティがシャドウされます。

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

コンポーネントの変更処理

MarqueeControlRootDesigner クラスは、MarqueeControl インスタンスのデザイン時のカスタム操作を提供します。デザイン時機能のほとんどが DocumentDesigner クラスから継承されるので、コンポーネントの変更処理とデザイナ動詞の追加という 2 つの固有のカスタマイズをコードで実装します。

ユーザーが各自の MarqueeControl インスタンスをデザインするときに、ルート デザイナは、MarqueeControl とその子コントロールに対する変更を追跡します。デザイン時環境は、コンポーネント状態の変更を追跡するための IComponentChangeService という便利なサービスを提供します。

このサービスへの参照を取得するには、GetService メソッドを使用してデザイン時環境を照会します。照会が成功すると、デザイナは、ComponentChanged イベントのハンドラをアタッチし、デザイン時に一貫した状態を保持する上で必要なタスクを実行できます。

MarqueeControlRootDesigner クラスの場合は、MarqueeControl に含まれている各 IMarqueeWidget オブジェクトで Refresh メソッドを呼び出します。これにより、親の Size などのプロパティが変更されたときに IMarqueeWidget オブジェクト自体が適切に再描画されます。

コンポーネントの変更を処理するには

  1. コード エディタで MarqueeControlRootDesigner ソース ファイルを開き、Initialize メソッドをオーバーライドします。Initialize の基本実装を呼び出し、IComponentChangeService を照会します。

    MyBase.Initialize(component)
    
    Dim cs As IComponentChangeService = _
    CType(GetService(GetType(IComponentChangeService)), _
    IComponentChangeService)
    
    If (cs IsNot Nothing) Then
        AddHandler cs.ComponentChanged, AddressOf OnComponentChanged
    End If
    
    base.Initialize(component);
    
    IComponentChangeService cs =
        GetService(typeof(IComponentChangeService)) 
        as IComponentChangeService;
    
    if (cs != null)
    {
        cs.ComponentChanged +=
            new ComponentChangedEventHandler(OnComponentChanged);
    }
    
  2. OnComponentChanged イベント ハンドラを実装します。送信側のコンポーネントの型をテストし、それが IMarqueeWidget の場合は、その Refresh メソッドを呼び出します。

    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 void OnComponentChanged(
        object sender,
        ComponentChangedEventArgs e)
    {
        if (e.Component is IMarqueeWidget)
        {
            this.Control.Refresh();
        }
    }
    

カスタム デザイナへのデザイナ動詞の追加

デザイナ動詞は、イベント ハンドラにリンクされたメニュー コマンドです。デザイナ動詞は、デザイン時にコンポーネントのショートカット メニューに追加されます。詳細については、「DesignerVerb」を参照してください。

[テストの実行] と [テストの停止] の 2 つのデザイナ動詞をデザイナに追加します。これらの動詞を使用すると、MarqueeControl の実行時の動作をデザイン時に確認できます。これらの動詞は、MarqueeControlRootDesigner に追加されます。

[テストの実行] を起動すると、動詞イベント ハンドラが MarqueeControl で StartMarquee メソッドを呼び出します。また、[テストの停止] を起動すると、動詞イベント ハンドラが MarqueeControl で StopMarquee メソッドを呼び出します。StartMarquee メソッドと StopMarquee メソッドの実装は、IMarqueeWidget を実装する、包含される側のコントロールでこれらのメソッドを呼び出すため、包含される IMarqueeWidget コントロールもすべてテストに加わります。

デザイナ動詞をカスタム デザイナに追加するには

  1. MarqueeControlRootDesigner クラスに、OnVerbRunTest および OnVerbStopTest という名前のイベント ハンドラを追加します。

    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
    
    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();
    }
    
  2. これらのイベント ハンドラを対応するデザイナ動詞に接続します。MarqueeControlRootDesigner が、基本クラスから DesignerVerbCollection を継承します。新たに 2 つの DesignerVerb オブジェクトを作成し、Initialize メソッドのこのコレクションに追加します。

    Me.Verbs.Add(New DesignerVerb("Run Test", _
    New EventHandler(AddressOf OnVerbRunTest)))
    
    Me.Verbs.Add(New DesignerVerb("Stop Test", _
    New EventHandler(AddressOf OnVerbStopTest)))
    
    this.Verbs.Add(
        new DesignerVerb("Run Test",
        new EventHandler(OnVerbRunTest))
        );
    
    this.Verbs.Add(
        new DesignerVerb("Stop Test",
        new EventHandler(OnVerbStopTest))
        );
    

カスタム UITypeEditor の作成

ユーザーに対しデザイン時のカスタム操作を作成するときは、一般に [プロパティ] ウィンドウを使用してカスタム対話を作成することが望まれます。これを行うには、UITypeEditor を作成します。詳細については、「方法 : UI 型エディタを作成する」を参照してください。

MarqueeBorder コントロールは、[プロパティ] ウィンドウにいくつかのプロパティを公開します。これらのプロパティのうち、MarqueeSpinDirection と MarqueeLightShape の 2 つは列挙型で表されます。UI 型エディタの使い方を示すために、MarqueeLightShape プロパティには、UITypeEditor クラスが関連付けられます。

カスタム UI 型エディタを作成するには

  1. MarqueeBorder ソース ファイルをコード エディタで開きます。

  2. MarqueeBorder クラスの定義で、UITypeEditor から派生する、LightShapeEditor という名前のクラスを宣言します。

    ' 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
    
    // 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
    {
    
  3. editorService という IWindowsFormsEditorService インスタンス変数を宣言します。

    Private editorService As IWindowsFormsEditorService = Nothing
    
    private IWindowsFormsEditorService editorService = null;
    
  4. GetEditStyle メソッドをオーバーライドします。この実装は、LightShapeEditor の表示方法をデザイン環境に指示する DropDown を返します。

    Public Overrides Function GetEditStyle( _
    ByVal context As System.ComponentModel.ITypeDescriptorContext) _
    As UITypeEditorEditStyle
        Return UITypeEditorEditStyle.DropDown
    End Function
    
    
    public override UITypeEditorEditStyle GetEditStyle(
    System.ComponentModel.ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    
  5. EditValue メソッドをオーバーライドします。この実装は、デザイン環境に IWindowsFormsEditorService オブジェクトを照会します。照会が成功すると、LightShapeSelectionControl が作成されます。DropDownControl メソッドを呼び出して、LightShapeEditor を起動します。この呼び出しの戻り値がデザイン環境に返されます。

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

カスタム UITypeEditor のビュー コントロールの作成

  1. MarqueeLightShape プロパティは、Square と Circle の 2 種類の光の形状をサポートします。ここでは、これらの値を [プロパティ] ウィンドウにグラフィカルに表示するためだけに使用するカスタム コントロールを作成します。このカスタム コントロールは、UITypeEditor が [プロパティ] ウィンドウと対話するために使用します。

カスタム UI 型エディタのビュー コントロールを作成するには

  1. 新しい UserControl アイテムを MarqueeControlLibrary プロジェクトに追加します。新しいソース ファイルに、"LightShapeSelectionControl" という基本名を付けます。

  2. [ツールボックス] から 2 つの Panel コントロールを LightShapeSelectionControl にドラッグします。これらのコントロールに squarePanel および circlePanel という名前を付けます。これらを左右に並べて配置します。両方の Panel コントロールの Size プロパティを (60, 60) に設定します。squarePanel コントロールの Location プロパティを (8, 10) に設定します。circlePanel コントロールの Location プロパティを (80, 10) に設定します。最後に、LightShapeSelectionControl の Size プロパティを (150, 80) に設定します。

  3. LightShapeSelectionControl ソース ファイルをコード エディタで開きます。ファイルの先頭に、次のように System.Windows.Forms.Design 名前空間をインポートします。

Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
  1. squarePanel コントロールおよび circlePanel コントロールの Click イベント ハンドラを実装します。これらのメソッドは CloseDropDown を呼び出して、カスタム UITypeEditor 編集セッションを終了させます。

    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
    
            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();
            }
    
  2. editorService という IWindowsFormsEditorService インスタンス変数を宣言します。

Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
  1. lightShapeValue という MarqueeLightShape インスタンス変数を宣言します。

    Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
    
    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
  2. LightShapeSelectionControl コンストラクタで、squarePanel コントロールと circlePanel コントロールの Click イベントに Click イベント ハンドラを追加します。また、デザイン環境から lightShapeValue フィールドに MarqueeLightShape 値を割り当てる、コンストラクタ オーバーロードを定義します。

    ' 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
    
            // 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);
            }
    
  3. Dispose メソッドで、Click イベント ハンドラをデタッチします。

    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
    
            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 );
            }
    
  4. ソリューション エクスプローラの [すべてのファイルを表示] をクリックします。LightShapeSelectionControl.Designer.cs ファイルまたは LightShapeSelectionControl.Designer.vb ファイルを開いて、Dispose メソッドの既定の定義を削除します。

  5. LightShape プロパティを実装します。

    ' 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
    
            // 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;
                    }
                }
            }
    
  6. OnPaint メソッドをオーバーライドします。この実装によって、塗りつぶされた正方形と円が描画されます。また、いずれかの図形の周囲に境界線を描画して、選択した値を強調表示します。

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

デザイナでのカスタム コントロールのテスト

この時点で、MarqueeControlLibrary プロジェクトをビルドできます。MarqueeControl クラスから継承するコントロールを作成し、それをフォームで使用して、実装をテストします。

カスタム MarqueeControl 実装を作成するには

  1. Windows フォーム デザイナで DemoMarqueeControl を開きます。これにより、DemoMarqueeControl 型のインスタンスが作成され、MarqueeControlRootDesigner 型のインスタンス内に表示されます。

  2. ツールボックスの [MarqueeControlLibrary コンポーネント] タブを開きます。MarqueeBorder コントロールと MarqueeText コントロールが選択可能なアイテムとして表示されます。

  3. MarqueeBorder コントロールのインスタンスを DemoMarqueeControl のデザイン サーフェイスにドラッグします。この MarqueeBorder コントロールを親コントロールにドッキングします。

  4. MarqueeText コントロールのインスタンスを DemoMarqueeControl のデザイン サーフェイスにドラッグします。

  5. ソリューションをビルドします。

  6. DemoMarqueeControl を右クリックし、ショートカット メニューの [テストの実行] を選択すると、アニメーションが開始されます。[テストの停止] をクリックして、アニメーションを停止します。

  7. デザイン ビューで [Form1] を開きます。

  8. 2 つの Button コントロールをフォームに配置します。それらのコントロールに startButton および stopButton という名前を付け、Text プロパティ値をそれぞれ Start と Stop に変更します。

  9. 両方の Button コントロールの Click イベント ハンドラを実装します。

  10. ツールボックスの [MarqueeControlTest コンポーネント] タブを開きます。DemoMarqueeControl が選択可能なアイテムとして表示されます。

  11. DemoMarqueeControl のインスタンスを Form1 のデザイン サーフェイスにドラッグします。

  12. Click イベント ハンドラで、DemoMarqueeControl の Start メソッドと Stop メソッドを呼び出します。

Private Sub startButton_Click(sender As Object, e As System.EventArgs)
    Me.demoMarqueeControl1.Start()
End Sub 'startButton_Click

Private Sub stopButton_Click(sender As Object, e As System.EventArgs)
Me.demoMarqueeControl1.Stop()
End Sub 'stopButton_Click
private void startButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Start();
}

private void stopButton_Click(object sender, System.EventArgs e)
{
    this.demoMarqueeControl1.Stop();
}
  1. MarqueeControlTest プロジェクトをスタートアップ プロジェクトとして設定し、実行します。フォームに DemoMarqueeControl が表示されます。[開始] ボタンをクリックしてアニメーションを開始します。テキストが点滅し、光が境界線上を移動するように見えます。

次の手順

MarqueeControlLibrary は、カスタム コントロールとそれに関連付けられたデザイナの簡単な実装を示しています。このサンプルは、以下の方法でより洗練されたものにできます。

  • デザイナで DemoMarqueeControl のプロパティ値を変更します。さらに MarqueBorder コントロールを追加し、親インスタンス内でドッキングして、入れ子の状態にします。UpdatePeriod プロパティと光関連プロパティでさまざまな設定を試みます。

  • IMarqueeWidget の独自の実装を作成します。たとえば、点滅する "ネオン サイン" や、複数のイメージを表示するアニメーション サインを作成することもできます。

  • デザイン時の操作をさらにカスタマイズします。EnabledVisible よりも多くのプロパティをシャドウしたり、新しいプロパティを追加したりできます。新しいデザイナ動詞を追加して、子コントロールのドッキングなどの共通タスクを簡素化します。

  • MarqueeControl のライセンス処理を行います。詳細については、「方法 : コンポーネントおよびコントロールのライセンス処理を行う」を参照してください。

  • コントロールをシリアル化する方法と、コントロールのコードを生成する方法を制御します。詳細については、「動的なソース コードの生成とコンパイル」を参照してください。

参照

処理手順

方法 : デザイン時機能を活用した Windows フォーム コントロールを作成する

参照

UserControl

ParentControlDesigner

DocumentDesigner

IRootDesigner

DesignerVerb

UITypeEditor

BackgroundWorker

その他の技術情報

デザイン時サポートの拡張

カスタム デザイナ

.NET Shape Library: A Sample Designer