英語で読む

次の方法で共有


チュートリアル: デザイン時機能を活用したコントロールを作成する

カスタム コントロールのデザイン時エクスペリエンスは、関連するカスタム デザイナーを作成することによって拡張できます。

注意事項

このコンテンツは .NET Framework 用に作成しました。 .NET 6 以降のバージョンを使用している場合は、このコンテンツの使用にご注意ください。 Windows フォーム用のデザイナー システムが変更されたため、「.NET Framework 以降のデザイナーの変更」の記事をご確認ください (重要です)。

この記事では、カスタム コントロール用のカスタム デザイナーを作成する方法を示します。 MarqueeControl 型と、MarqueeControlRootDesigner という名前の関連付けられたデザイナー クラスを実装します。

MarqueeControl 型を使用して、アニメーション化されたライトと点滅するテキストを含む映画館のマーキーに似た表示を実装します。

このコントロールのデザイナーはデザイン環境と対話して、デザイン時のカスタム エクスペリエンスを提供します。 カスタム デザイナーを使用すると、アニメーション化されたライトと点滅するテキストのさまざまな組み合わせで、MarqueeControl のカスタム実装を組み立てることができます。 組み立てられたコントロールは、他の Windows フォーム コントロールと同様に、フォーム上で使用できます。

このチュートリアルを終了すると、カスタム コントロールは次のような表示になります。

The app showing a marquee saying Text and a Start and Stop buttons.

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

前提条件

このチュートリアルを完了するには、Visual Studio が必要です。

プロジェクトの作成

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

Visual Studio で新しい Windows フォーム アプリケーション プロジェクトを作成し、MarqueeControlTest という名前を付けます。

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

  1. Windows フォーム コントロール ライブラリ プロジェクトをソリューションに追加します。 プロジェクトの名前は MarqueeControlLibrary にします。

  2. ソリューション エクスプローラーを使用して、選択した言語に対応する "UserControl1.cs" または "UserControl1.vb" という名前のソース ファイルを削除することにより、プロジェクトの既定のコントロールを削除します。

  3. 新しい UserControl 項目を MarqueeControlLibrary プロジェクトに追加します。 新しいソース ファイルのベース名を MarqueeControl にします。

  4. ソリューション エクスプローラーを使用して、MarqueeControlLibrary プロジェクトに新しいフォルダーを作成します。

  5. [デザイン] フォルダーを右クリックして、新しいクラスを追加します。 MarqueeControlRootDesigner という名前を指定します。

  6. System.Design アセンブリの型を使用する必要があるため、この参照を MarqueeControlLibrary プロジェクトに追加します。

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

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

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

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

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

このカスタム コントロールに、カスタム デザイナーを関連付けます。 これにより、このカスタム コントロール専用にカスタマイズされた独自のデザイン エクスペリエンスを作成できます。

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

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

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

    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. DesignerAttributeMarqueeControl のクラス宣言に追加します。 これにより、カスタム コントロールがデザイナーに関連付けられます。

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

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

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

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

  1. 新しい UserControl 項目を MarqueeControlTest プロジェクトに追加します。 新しいソース ファイルのベース名を DemoMarqueeControl にします。

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

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

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

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

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

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

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

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

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

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

  3. [開始動作] セクションで [外部プログラムを起動する] を選択します。 Visual Studio の別のインスタンスをデバッグするので、省略記号 (The Ellipsis button (...) in the Properties window of Visual Studio) ボタンをクリックして、Visual Studio IDE を参照します。 実行可能ファイルの名前は devenv.exe であり、既定の場所にインストールした場合、そのパスは %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe になります。

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

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

Checkpoint

これで、カスタム コントロールのデザイン時動作をデバッグする準備ができました。 デバッグ環境が正しく設定されていることを確認した後、カスタム コントロールとカスタム デザイナーの関連付けをテストします。

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

  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 インターフェイスを実装する子コントロールが含まれているため、StartStop により、各子コントロールが列挙され、IMarqueeWidget が実装されている子コントロールごとに、それぞれ StartMarquee メソッドと StopMarquee メソッドが呼び出されます。

MarqueeBorder および MarqueeText コントロールの外観はレイアウトに依存しているため、MarqueeControl によって OnLayout メソッドがオーバーライドされ、この型の子コントロールでは PerformLayout が呼び出されます。

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

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

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

    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 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 というプロパティによって制御されます。 他のいくつかのカスタム プロパティにより、コントロールの外観に関する他の側面が決まります。 StartMarqueeStopMarquee という名前の 2 つのメソッドにより、アニメーションの開始と停止のタイミングが制御されます。

  • MarqueeText: このコントロールにより、点滅する文字列が描画されます。 MarqueeBorder コントロールと同様に、テキストが点滅する速さは、UpdatePeriod プロパティによって制御されます。 MarqueeText コントロールにも、MarqueeBorder コントロールと同じように、StartMarquee および StopMarquee メソッドがあります。

デザイン時には、MarqueeControlRootDesigner を使用することで、これら 2 つのコントロール型を任意の組み合わせで MarqueeControl に追加できます。

2 つのコントロールの共通機能は、IMarqueeWidget という名前のインターフェイスにまとめられています。 これにより、MarqueeControl でマーキー関連の子コントロールを検出し、特別な処理を行うことができます。

周期的なアニメーション機能を実装するには、System.ComponentModel 名前空間の BackgroundWorker オブジェクトを使用します。 Timer オブジェクトを使用することもできますが、多くの IMarqueeWidget オブジェクトが存在すると、1 つの 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
    {
    
  3. 次のコードを IMarqueeWidget インターフェイスに追加して、マーキー アニメーションを操作する 2 つのメソッドと 1 つのプロパティを公開します。

    // 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. ツールボックスから MarqueeText コントロールに BackgroundWorker コンポーネントをドラッグします。 このコンポーネントにより、MarqueeText コントロールでそれ自体を非同期に更新できるようになります。

  6. [プロパティ] ウィンドウで、BackgroundWorker コンポーネントの WorkerReportsProgress および WorkerSupportsCancellation プロパティを true に設定します。 これらの設定により、BackgroundWorker コンポーネントで、定期的に ProgressChanged イベントを発生させ、非同期更新を取り消すことができます。

    詳細については、「BackgroundWorker コンポーネント」を参照してください。

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

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

    [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 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 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. プロパティのアクセサーを実装します。 LightColorDarkColor の 2 つのプロパティをクライアントに公開します。 Category および Browsable 属性はこれらのプロパティに適用されるため、プロパティ ウィンドウの "Marquee" という名前のカスタム セクションにプロパティが表示されます。

    [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 イベント ハンドラーは、コードで CancelAsync が呼び出されてアニメーションが停止されるまで、UpdatePeriod によって指定されているミリ秒数だけスリープ状態になった後、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 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 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 コントロールと非常によく似ています。

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

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

  1. 新しいカスタム コントロール項目を MarqueeControlLibrary プロジェクトに追加します。 新しいソース ファイルのベース名を "MarqueeBorder" にします。

  2. ツールボックスから MarqueeBorder コントロールに BackgroundWorker コンポーネントをドラッグします。 このコンポーネントにより、MarqueeBorder コントロールでそれ自体を非同期に更新できるようになります。

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

  4. [プロパティ] ウィンドウで、 [イベント] ボタンを選択します。 DoWork および ProgressChanged イベントのハンドラーをアタッチします。

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

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

    [Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))]
    [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)]
    public partial class MarqueeBorder : Panel, IMarqueeWidget
    {
    
  7. MarqueeBorder コントロールの状態を管理するための 2 つの列挙型を宣言します。MarqueeSpinDirection により、ライトが境界の周りを "スピン" する方向が決まります。MarqueeLightShape により、ライトの形状 (正方形または円形) が決まります。 これらの宣言を、MarqueeBorder クラスの宣言の前に配置します。

    // 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 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 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 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 イベント ハンドラーは、コードで CancelAsync が呼び出されてアニメーションが停止されるまで、UpdatePeriod によって指定されているミリ秒数だけスリープ状態になった後、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 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. ヘルパー メソッド IsLitDrawLight を実装します。

    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 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 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 によってシャドウ処理されます。これにより、デザイン時にユーザーは MarqueeBorder コントロールを非表示にしたり無効にしたりすることができなくなります。

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

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

これらのメソッドを使用してコンポーネントのパブリック インターフェイスを変更する場合は、次の規則に従います。

  • 項目の追加または削除は、PreFilter メソッドにおいてのみ行います

  • 既存の項目の変更は、PostFilter メソッドにおいてのみ行います

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

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

これらの規則に従うことにより、デザイン時環境のすべてのデザイナーで、デザイン対象のすべてのコンポーネントのビューが、一貫したものになります。

ComponentDesigner クラスにより、シャドウ処理されるプロパティの値を管理するためのディクショナリが提供されます。これにより、特定のインスタンス変数を作成する必要がなくなります。

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

  1. [デザイン] フォルダーを右クリックして、新しいクラスを追加します。 ソース ファイルのベース名を MarqueeBorderDesigner にします。

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

    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
    {
        public class MarqueeBorderDesigner : ParentControlDesigner
        {
    
  4. PreFilterProperties の基本実装をオーバーライドします。

    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 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 のクエリを実行します。

    base.Initialize(component);
    
    IComponentChangeService cs =
        GetService(typeof(IComponentChangeService))
        as IComponentChangeService;
    
    if (cs != null)
    {
        cs.ComponentChanged +=
            new ComponentChangedEventHandler(OnComponentChanged);
    }
    
  2. OnComponentChanged イベント ハンドラーを実装します。 送信コンポーネントの型をテストし、それが IMarqueeWidget である場合は、その Refresh メソッドを呼び出します。

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

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

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

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

"テストを実行する" が呼び出されると、動詞イベント ハンドラーによって MarqueeControlStartMarquee メソッドが呼び出されます。 "テストを停止する" が呼び出されると、動詞イベント ハンドラーによって MarqueeControlStopMarquee メソッドが呼び出されます。 StartMarquee および StopMarquee メソッドの実装により、IMarqueeWidget が実装されている含まれるコントロールでこれらのメソッドが呼び出されるため、含まれる IMarqueeWidget コントロールもテストに参加します。

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

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

    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 メソッドでそれらをこのコレクションに追加します。

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

カスタム UITypeEditor を作成する

ユーザーのためのカスタム デザイン時エクスペリエンスを作成するときは、多くの場合、プロパティ ウィンドウとのカスタム対話を作成することをお勧めします。 これは、UITypeEditor を作成することによって実現できます。

MarqueeBorder コントロールにより、いくつかのプロパティがプロパティ ウィンドウに公開されます。 これらのプロパティのうちの 2 つである MarqueeSpinDirectionMarqueeLightShape は、列挙型によって表されます。 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.
    internal class LightShapeEditor : UITypeEditor
    {
    
  3. editorService という名前の IWindowsFormsEditorService インスタンス変数を宣言します。

    private IWindowsFormsEditorService editorService = null;
    
  4. GetEditStyle メソッドをオーバーライドします。 この実装から返される DropDown により、LightShapeEditor を表示する方法がデザイン環境に指示されます。

    public override UITypeEditorEditStyle GetEditStyle(
    System.ComponentModel.ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    
  5. EditValue メソッドをオーバーライドします。 この実装により、デザイン環境で IWindowsFormsEditorService オブジェクトのクエリが行われます。 成功した場合は、LightShapeSelectionControl が作成されます。 LightShapeEditor を開始するために、DropDownControl メソッドが呼び出されます。 この呼び出しからの戻り値が、デザイン環境に返されます。

    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 のビュー コントロールを作成する

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

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

  1. 新しい UserControl 項目を MarqueeControlLibrary プロジェクトに追加します。 新しいソース ファイルのベース名として LightShapeSelectionControl を指定します。

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

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

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

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

    private IWindowsFormsEditorService editorService;
    
  6. lightShapeValue という名前の MarqueeLightShape インスタンス変数を宣言します。

    private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
    
  7. LightShapeSelectionControl コンストラクターで、Click イベント ハンドラーを squarePanel および circlePanel コントロールの Click イベントにアタッチします。 また、デザイン環境の MarqueeLightShape の値を lightShapeValue フィールドに割り当てるコンストラクターのオーバーロードを定義します。

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

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            // Be sure to unhook event handlers
            // to prevent "lapsed listener" leaks.
            this.squarePanel.Click -=
                new EventHandler(squarePanel_Click);
            this.circlePanel.Click -=
                new EventHandler(circlePanel_Click);
    
            if(components != null)
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }
    
  9. ソリューション エクスプローラーで、 [すべてのファイルを表示] ボタンをクリックします。 LightShapeSelectionControl.Designer.cs または LightShapeSelectionControl.Designer.vb ファイルを開き、Dispose メソッドの既定の定義を削除します。

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

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

    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 イベント ハンドラーで、DemoMarqueeControlStart および Stop メソッドを呼び出し ます。

    private void startButton_Click(object sender, System.EventArgs e)
    {
        this.demoMarqueeControl1.Start();
    }
    
    private void stopButton_Click(object sender, System.EventArgs e)
    {
        this.demoMarqueeControl1.Stop();
    }
    
  13. MarqueeControlTest プロジェクトをスタートアップ プロジェクトとして設定し、実行します。 フォームに DemoMarqueeControl が表示されます。 [開始] ボタンを選択して、アニメーションを開始します。 テキストが点滅し、ライトが境界線内を移動するはずです。

次のステップ

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

  • DemoMarqueeControl のプロパティの値をデザイナーで変更します。 MarqueBorder コントロールをさらに追加し、親インスタンス内にドッキングして、入れ子になった効果を作成します。 UpdatePeriod およびライト関連のプロパティについて、さまざまな設定を調べます。

  • IMarqueeWidget の独自の実装を作成します。 たとえば、点滅する "ネオン サイン" や、複数の画像が含まれるアニメーション化されたサインを作成してみます。

  • デザイン時エクスペリエンスをさらにカスタマイズします。 EnabledVisible 以外のプロパティをシャドウ処理してみたり、新しいプロパティを追加します。 新しいデザイナー動詞を追加して、子コントロールのドッキングなどの一般的なタスクを簡素化します。

  • MarqueeControl にライセンスを設定します。

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

関連項目