チュートリアル: デザイン時機能を活用したコントロールを作成する
カスタム コントロールのデザイン時エクスペリエンスは、関連するカスタム デザイナーを作成することによって拡張できます。
注意事項
このコンテンツは .NET Framework 用に作成しました。 .NET 6 以降のバージョンを使用している場合は、このコンテンツの使用にご注意ください。 Windows フォーム用のデザイナー システムが変更されたため、「.NET Framework 以降のデザイナーの変更」の記事をご確認ください (重要です)。
この記事では、カスタム コントロール用のカスタム デザイナーを作成する方法を示します。 MarqueeControl
型と、MarqueeControlRootDesigner
という名前の関連付けられたデザイナー クラスを実装します。
MarqueeControl
型を使用して、アニメーション化されたライトと点滅するテキストを含む映画館のマーキーに似た表示を実装します。
このコントロールのデザイナーはデザイン環境と対話して、デザイン時のカスタム エクスペリエンスを提供します。 カスタム デザイナーを使用すると、アニメーション化されたライトと点滅するテキストのさまざまな組み合わせで、MarqueeControl
のカスタム実装を組み立てることができます。 組み立てられたコントロールは、他の Windows フォーム コントロールと同様に、フォーム上で使用できます。
このチュートリアルを終了すると、カスタム コントロールは次のような表示になります。
完全なコード リストについては、「方法: デザイン時機能を活用した Windows フォーム コントロールを作成する」を参照してください。
前提条件
このチュートリアルを完了するには、Visual Studio が必要です。
プロジェクトの作成
最初にアプリケーションのプロジェクトを作成します。 このプロジェクトを使用して、カスタム コントロールをホストするアプリケーションをビルドします。
Visual Studio で新しい Windows フォーム アプリケーション プロジェクトを作成し、MarqueeControlTest という名前を付けます。
コントロール ライブラリ プロジェクトを作成する
Windows フォーム コントロール ライブラリ プロジェクトをソリューションに追加します。 プロジェクトの名前は MarqueeControlLibrary にします。
ソリューション エクスプローラーを使用して、選択した言語に対応する "UserControl1.cs" または "UserControl1.vb" という名前のソース ファイルを削除することにより、プロジェクトの既定のコントロールを削除します。
新しい UserControl 項目を
MarqueeControlLibrary
プロジェクトに追加します。 新しいソース ファイルのベース名を MarqueeControl にします。ソリューション エクスプローラーを使用して、
MarqueeControlLibrary
プロジェクトに新しいフォルダーを作成します。[デザイン] フォルダーを右クリックして、新しいクラスを追加します。 MarqueeControlRootDesigner という名前を指定します。
System.Design アセンブリの型を使用する必要があるため、この参照を
MarqueeControlLibrary
プロジェクトに追加します。
カスタム コントロール プロジェクトを参照する
カスタム コントロールをテストするには、MarqueeControlTest
プロジェクトを使用します。 MarqueeControlLibrary
アセンブリにプロジェクト参照を追加すると、テスト プロジェクトによってカスタム コントロールが認識されるようになります。
MarqueeControlTest
プロジェクトで、プロジェクト参照を MarqueeControlLibrary
アセンブリに追加します。 MarqueeControlLibrary
アセンブリを直接参照するのではなく、 [参照の追加] ダイアログ ボックスの [プロジェクト] タブを使用するようにしてください。
カスタム コントロールとそのカスタム デザイナーを定義する
このカスタム コントロールは UserControl クラスから派生します。 これにより、コントロールに他のコントロールを含めることができ、コントロールに多数の既定の機能が提供されます。
このカスタム コントロールに、カスタム デザイナーを関連付けます。 これにより、このカスタム コントロール専用にカスタマイズされた独自のデザイン エクスペリエンスを作成できます。
コントロールをデザイナーに関連付けるには、DesignerAttribute クラスを使用します。 カスタム コントロールのデザイン時動作全体を開発しているため、カスタム デザイナーで IRootDesigner インターフェイスを実装します。
カスタム コントロールとそのカスタム デザイナーを定義するには
コード エディターで
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;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Drawing Imports System.Windows.Forms Imports System.Windows.Forms.Design
DesignerAttribute を
MarqueeControl
のクラス宣言に追加します。 これにより、カスタム コントロールがデザイナーに関連付けられます。[Designer( typeof( MarqueeControlLibrary.Design.MarqueeControlRootDesigner ), typeof( IRootDesigner ) )] public class MarqueeControl : UserControl {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeControlRootDesigner), _ GetType(IRootDesigner))> _ Public Class MarqueeControl Inherits UserControl
コード エディターで
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;
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
DocumentDesigner クラスを継承するように
MarqueeControlRootDesigner
の宣言を変更します。 ToolboxItemFilterAttribute を適用して、デザイナーとツールボックスの対話を指定します。注意
MarqueeControlRootDesigner
クラスの定義は、MarqueeControlLibrary.Design という名前空間で囲まれています。 この宣言により、デザイン関連の型用に予約された特別な名前空間にデザイナーが配置されます。namespace MarqueeControlLibrary.Design { [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public class MarqueeControlRootDesigner : DocumentDesigner {
Namespace MarqueeControlLibrary.Design <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Public Class MarqueeControlRootDesigner Inherits DocumentDesigner
MarqueeControlRootDesigner
クラスのコンストラクターを定義します。 コンストラクターの本体に WriteLine ステートメントを挿入します。 これはデバッグに役立ちます。public MarqueeControlRootDesigner() { Trace.WriteLine("MarqueeControlRootDesigner ctor"); }
Public Sub New() Trace.WriteLine("MarqueeControlRootDesigner ctor") End Sub
カスタム コントロールのインスタンスを作成する
新しい UserControl 項目を
MarqueeControlTest
プロジェクトに追加します。 新しいソース ファイルのベース名を DemoMarqueeControl にします。DemoMarqueeControl
ファイルをコード エディターで開きます。 ファイルの先頭でMarqueeControlLibrary
名前空間をインポートします。Imports MarqueeControlLibrary
using MarqueeControlLibrary;
MarqueeControl
クラスを継承するようにDemoMarqueeControl
の宣言を変更します。プロジェクトをビルドします。
Windows フォーム デザイナーで Form1 を開きます。
ツールボックスの [MarqueeControlTest コンポーネント] タブを見つけて開きます。 ツールボックスからフォームに
DemoMarqueeControl
をドラッグします。プロジェクトをビルドします。
デザイン時デバッグ用にプロジェクトを設定する
カスタム デザイン時エクスペリエンスを開発している場合は、コントロールとコンポーネントをデバッグする必要があります。 デザイン時にデバッグできるようにプロジェクトを設定する簡単な方法があります。 詳細については、「チュートリアル: カスタム Windows フォーム コントロールのデザイン時のデバッグ」を参照してください。
MarqueeControlLibrary
プロジェクトを右クリックして、 [プロパティ] を選択します。[MarqueeControlLibrary プロパティページ] ダイアログ ボックスで、 [デバッグ] ページを選択します。
[開始動作] セクションで [外部プログラムを起動する] を選択します。 Visual Studio の別のインスタンスをデバッグするので、省略記号 () ボタンをクリックして、Visual Studio IDE を参照します。 実行可能ファイルの名前は devenv.exe であり、既定の場所にインストールした場合、そのパスは %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe になります。
[OK] を選択してダイアログ ボックスを閉じます。
MarqueeControlLibrary プロジェクトを右クリックし、 [スタートアップ プロジェクトに設定] を選択して、このデバッグ構成を有効にします。
Checkpoint
これで、カスタム コントロールのデザイン時動作をデバッグする準備ができました。 デバッグ環境が正しく設定されていることを確認した後、カスタム コントロールとカスタム デザイナーの関連付けをテストします。
デバッグ環境とデザイナーの関連付けをテストするには
コード エディターで MarqueeControlRootDesigner ソース ファイルを開き、WriteLine ステートメントにブレークポイントを設定します。
F5 キーを押してデバッグ セッションを開始します。
Visual Studio の新しいインスタンスが作成されます。
Visual Studio の新しいインスタンスで、MarqueeControlTest ソリューションを開きます。 [ファイル] メニューの [最近使ったプロジェクト] を選択することで、ソリューションを簡単に見つけることができます。 MarqueeControlTest.sln ソリューション ファイルが、最近使用したファイルとして一覧に表示されます。
デザイナーで
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
クラスによって実装されています。
カスタム コントロールを実装するには
コード エディターで
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(); } } }
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
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(); } } }
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
カスタム コントロールの子コントロールを作成する
MarqueeControl
により、MarqueeBorder
コントロールと MarqueeText
コントロールという 2 種類の子コントロールがホストされます。
MarqueeBorder
: このコントロールにより、端の周りに "ライト" の境界線が描画されます。 ライトは規則的に点滅するため、境界線が移動しているように見えます。 ライトが点滅する速さは、UpdatePeriod
というプロパティによって制御されます。 他のいくつかのカスタム プロパティにより、コントロールの外観に関する他の側面が決まります。StartMarquee
とStopMarquee
という名前の 2 つのメソッドにより、アニメーションの開始と停止のタイミングが制御されます。MarqueeText
: このコントロールにより、点滅する文字列が描画されます。MarqueeBorder
コントロールと同様に、テキストが点滅する速さは、UpdatePeriod
プロパティによって制御されます。MarqueeText
コントロールにも、MarqueeBorder
コントロールと同じように、StartMarquee
およびStopMarquee
メソッドがあります。
デザイン時には、MarqueeControlRootDesigner
を使用することで、これら 2 つのコントロール型を任意の組み合わせで MarqueeControl
に追加できます。
2 つのコントロールの共通機能は、IMarqueeWidget
という名前のインターフェイスにまとめられています。 これにより、MarqueeControl
でマーキー関連の子コントロールを検出し、特別な処理を行うことができます。
周期的なアニメーション機能を実装するには、System.ComponentModel 名前空間の BackgroundWorker オブジェクトを使用します。 Timer オブジェクトを使用することもできますが、多くの IMarqueeWidget
オブジェクトが存在すると、1 つの UI スレッドではアニメーションを維持できないことがあります。
カスタム コントロールの子コントロールを作成するには
新しいクラス項目を
MarqueeControlLibrary
プロジェクトに追加します。 新しいソース ファイルのベース名を "IMarqueeWidget" にします。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
次のコードを
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; } }
' 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
新しいカスタム コントロール項目を
MarqueeControlLibrary
プロジェクトに追加します。 新しいソース ファイルのベース名を "MarqueeText" にします。ツールボックスから
MarqueeText
コントロールに BackgroundWorker コンポーネントをドラッグします。 このコンポーネントにより、MarqueeText
コントロールでそれ自体を非同期に更新できるようになります。[プロパティ] ウィンドウで、BackgroundWorker コンポーネントの
WorkerReportsProgress
および WorkerSupportsCancellation プロパティを true に設定します。 これらの設定により、BackgroundWorker コンポーネントで、定期的に ProgressChanged イベントを発生させ、非同期更新を取り消すことができます。詳細については、「BackgroundWorker コンポーネント」を参照してください。
コード エディターで
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;
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
Label を継承し、
IMarqueeWidget
インターフェイスを実装するように、MarqueeText
の宣言を変更します。[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {
<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
公開されたプロパティに対応するインスタンス変数を宣言し、コンストラクターでそれらを初期化します。
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); }
' 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
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"); } } }
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
プロパティのアクセサーを実装します。
LightColor
とDarkColor
の 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); } } }
<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
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(); }
' 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
アニメーションを有効にするように、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); }
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
F6 キーを押してソリューションをビルドします。
MarqueeBorder 子コントロールを作成する
MarqueeBorder
コントロールは、MarqueeText
コントロールより少し高度です。 さらに多くのプロパティがあり、OnPaint メソッドのアニメーションも複雑になります。 原則としては、MarqueeText
コントロールと非常によく似ています。
MarqueeBorder
コントロールは子コントロールを持つことができるため、Layout イベントを認識する必要があります。
MarqueeBorder コントロールを作成するには
新しいカスタム コントロール項目を
MarqueeControlLibrary
プロジェクトに追加します。 新しいソース ファイルのベース名を "MarqueeBorder" にします。ツールボックスから
MarqueeBorder
コントロールに BackgroundWorker コンポーネントをドラッグします。 このコンポーネントにより、MarqueeBorder
コントロールでそれ自体を非同期に更新できるようになります。[プロパティ] ウィンドウで、BackgroundWorker コンポーネントの
WorkerReportsProgress
および WorkerSupportsCancellation プロパティを true に設定します。 これらの設定により、BackgroundWorker コンポーネントで、定期的に ProgressChanged イベントを発生させ、非同期更新を取り消すことができます。 詳細については、「BackgroundWorker コンポーネント」を参照してください。[プロパティ] ウィンドウで、 [イベント] ボタンを選択します。 DoWork および ProgressChanged イベントのハンドラーをアタッチします。
コード エディターで
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;
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
Panel を継承し、
IMarqueeWidget
インターフェイスを実装するように、MarqueeBorder
の宣言を変更します。[Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] public partial class MarqueeBorder : Panel, IMarqueeWidget {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeBorder Inherits Panel Implements IMarqueeWidget
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 }
' 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
公開されたプロパティに対応するインスタンス変数を宣言し、コンストラクターでそれらを初期化します。
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); }
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
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"); } } }
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
プロパティのアクセサーを実装します。
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; } }
<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
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(); }
' 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
ヘルパー メソッド
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 virtual bool IsLit(int lightIndex) { int directionFactor = (this.spinDirectionValue == MarqueeSpinDirection.CW ? -1 : 1); return ( (lightIndex + directionFactor * this.currentOffset) % this.lightPeriodValue == 0 ); } protected virtual void DrawLight( Graphics g, Brush brush, int xPos, int yPos) { switch (this.lightShapeValue) { case MarqueeLightShape.Square: { g.FillRectangle(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue); break; } case MarqueeLightShape.Circle: { g.FillEllipse(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue); break; } default: { Trace.Assert(false, "Unknown value for light shape."); break; } } }
' This method 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
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++; } } }
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
プロパティのシャドウおよびフィルター処理を行うカスタム デザイナーを作成する
MarqueeControlRootDesigner
クラスにより、ルート デザイナーの実装が提供されます。 MarqueeControl
で動作するこのデザイナーに加えて、MarqueeBorder
コントロールに特に関連付けられたカスタム デザイナーが必要です。 このデザイナーにより、カスタム ルート デザイナーのコンテキストに適したカスタム動作が提供されます。
具体的には、MarqueeBorderDesigner
により MarqueeBorder
コントロールの特定のプロパティの "シャドウ" およびフィルター処理が行われ、デザイン環境との相互作用が変更されます。
コンポーネントのプロパティ アクセサーの呼び出しをインターセプトすることは、"シャドウ" と呼ばれます。これにより、デザイナーはユーザーによって設定された値を追跡し、必要に応じてその値を設計されているコンポーネントに渡すことができます。
この例では、Visible および Enabled プロパティが MarqueeBorderDesigner
によってシャドウ処理されます。これにより、デザイン時にユーザーは MarqueeBorder
コントロールを非表示にしたり無効にしたりすることができなくなります。
デザイナーでプロパティを追加および削除することもできます。 この例では、パディングは LightSize
プロパティによって指定されているライトのサイズに基づいて MarqueeBorder
コントロールでプログラムにより設定されるため、Padding プロパティはデザイン時には削除されます。
MarqueeBorderDesigner
の基底クラスは ComponentDesigner であり、デザイン時にコントロールによって公開される属性、プロパティ、イベントを変更できるメソッドがあります。
これらのメソッドを使用してコンポーネントのパブリック インターフェイスを変更する場合は、次の規則に従います。
項目の追加または削除は、
PreFilter
メソッドにおいてのみ行います既存の項目の変更は、
PostFilter
メソッドにおいてのみ行いますPreFilter
メソッドでは常に基本実装を最初に呼び出しますPostFilter
メソッドでは常に基本実装を最後に呼び出します
これらの規則に従うことにより、デザイン時環境のすべてのデザイナーで、デザイン対象のすべてのコンポーネントのビューが、一貫したものになります。
ComponentDesigner クラスにより、シャドウ処理されるプロパティの値を管理するためのディクショナリが提供されます。これにより、特定のインスタンス変数を作成する必要がなくなります。
プロパティのシャドウおよびフィルター処理を行うカスタム デザイナーを作成するには
[デザイン] フォルダーを右クリックして、新しいクラスを追加します。 ソース ファイルのベース名を MarqueeBorderDesigner にします。
コード エディターで 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;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Windows.Forms Imports System.Windows.Forms.Design
ParentControlDesigner を継承するように
MarqueeBorderDesigner
の宣言を変更します。MarqueeBorder
コントロールは子コントロールを含むことができるため、MarqueeBorderDesigner
は、親子の相互作用を処理する ParentControlDesigner を継承します。namespace MarqueeControlLibrary.Design { public class MarqueeBorderDesigner : ParentControlDesigner {
Namespace MarqueeControlLibrary.Design Public Class MarqueeBorderDesigner Inherits ParentControlDesigner
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]); }
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
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; } }
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
コンポーネントの変更を処理する
MarqueeControlRootDesigner
クラスにより、MarqueeControl
インスタンスのカスタム デザイン時エクスペリエンスが提供されます。 ほとんどのデザイン時機能は、DocumentDesigner クラスから継承されます。 コードでは、コンポーネントの変更の処理とデザイナー動詞の追加という 2 つの特定のカスタマイズを実装します。
ユーザーが MarqueeControl
のインスタンスをデザインすると、ルート デザイナーにより MarqueeControl
とその子コントロールまで変更が追跡されます。 デザイン時環境により、コンポーネントの状態に対する変更を追跡するための便利なサービスである IComponentChangeService が提供されます。
このサービスへの参照を取得するには、GetService メソッドを使用して環境のクエリを実行します。 クエリが成功した場合は、デザイナーで ComponentChanged イベントのハンドラーをアタッチし、デザイン時に一貫した状態を維持するために必要なすべてのタスクを実行できます。
MarqueeControlRootDesigner
クラスの場合は、MarqueeControl
に格納されている各 IMarqueeWidget
オブジェクトで Refresh メソッドを呼び出します。 これにより、親の Size ようなプロパティが変更されたときは、IMarqueeWidget
オブジェクトによって適切に再描画されます。
コンポーネントの変更を処理するには
コード エディターで
MarqueeControlRootDesigner
ソース ファイルを開き、Initialize メソッドをオーバーライドします。 Initialize の基本実装を呼び出し、IComponentChangeService のクエリを実行します。base.Initialize(component); IComponentChangeService cs = GetService(typeof(IComponentChangeService)) as IComponentChangeService; if (cs != null) { cs.ComponentChanged += new ComponentChangedEventHandler(OnComponentChanged); }
MyBase.Initialize(component) Dim cs As IComponentChangeService = _ CType(GetService(GetType(IComponentChangeService)), _ IComponentChangeService) If (cs IsNot Nothing) Then AddHandler cs.ComponentChanged, AddressOf OnComponentChanged End If
OnComponentChanged イベント ハンドラーを実装します。 送信コンポーネントの型をテストし、それが
IMarqueeWidget
である場合は、その Refresh メソッドを呼び出します。private void OnComponentChanged( object sender, ComponentChangedEventArgs e) { if (e.Component is IMarqueeWidget) { this.Control.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
デザイナー動詞をカスタム デザイナーに追加する
デザイナー動詞は、イベント ハンドラーにリンクされたメニュー コマンドです。 デザイナー動詞は、デザイン時にコンポーネントのショートカット メニューに追加されます。 詳細については、「DesignerVerb」を参照してください。
"テストを実行する" と "テストを停止する" という 2 つのデザイナー動詞をデザイナーに追加します。 これらの動詞を使用すると、デザイン時に MarqueeControl
の実行時の動作を表示できます。 これらの動詞は MarqueeControlRootDesigner
に追加されます。
"テストを実行する" が呼び出されると、動詞イベント ハンドラーによって MarqueeControl
の StartMarquee
メソッドが呼び出されます。 "テストを停止する" が呼び出されると、動詞イベント ハンドラーによって MarqueeControl
の StopMarquee
メソッドが呼び出されます。 StartMarquee
および StopMarquee
メソッドの実装により、IMarqueeWidget
が実装されている含まれるコントロールでこれらのメソッドが呼び出されるため、含まれる IMarqueeWidget
コントロールもテストに参加します。
デザイナー動詞をカスタム デザイナーに追加するには
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(); }
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
これらのイベント ハンドラーを、対応するデザイナー動詞に接続します。
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)) );
Me.Verbs.Add(New DesignerVerb("Run Test", _ New EventHandler(AddressOf OnVerbRunTest))) Me.Verbs.Add(New DesignerVerb("Stop Test", _ New EventHandler(AddressOf OnVerbStopTest)))
カスタム UITypeEditor を作成する
ユーザーのためのカスタム デザイン時エクスペリエンスを作成するときは、多くの場合、プロパティ ウィンドウとのカスタム対話を作成することをお勧めします。 これは、UITypeEditor を作成することによって実現できます。
MarqueeBorder
コントロールにより、いくつかのプロパティがプロパティ ウィンドウに公開されます。 これらのプロパティのうちの 2 つである MarqueeSpinDirection
と MarqueeLightShape
は、列挙型によって表されます。 UI 型エディターの使用方法を示すため、MarqueeLightShape
プロパティには UITypeEditor クラスが関連付けられています。
カスタム UI 型エディターを作成するには
コード エディターで
MarqueeBorder
ソース ファイルを開きます。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 {
' 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
editorService
という名前の IWindowsFormsEditorService インスタンス変数を宣言します。private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
GetEditStyle メソッドをオーバーライドします。 この実装から返される DropDown により、
LightShapeEditor
を表示する方法がデザイン環境に指示されます。public override UITypeEditorEditStyle GetEditStyle( System.ComponentModel.ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; }
Public Overrides Function GetEditStyle( _ ByVal context As System.ComponentModel.ITypeDescriptorContext) _ As UITypeEditorEditStyle Return UITypeEditorEditStyle.DropDown End Function
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; }
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
カスタム UITypeEditor のビュー コントロールを作成する
MarqueeLightShape
プロパティにより、Square
と Circle
の 2 種類のライト形状がサポートされます。 プロパティ ウィンドウでこれらの値をグラフィカルに表示するためだけに使用されるカスタム コントロールを作成します。 このカスタム コントロールは、プロパティ ウィンドウと対話するために UITypeEditor によって使用されます。
カスタム UI 型エディターのビュー コントロールを作成するには
新しい UserControl 項目を
MarqueeControlLibrary
プロジェクトに追加します。 新しいソース ファイルのベース名として LightShapeSelectionControl を指定します。ツールボックスから
LightShapeSelectionControl
に 2 つの Panel コントロールをドラッグします。squarePanel
とcirclePanel
という名前を指定します。 それらを並べて配置します。 両方の Panel コントロールの Size プロパティを (60, 60) に設定します。squarePanel
コントロールの Location プロパティを (8, 10) に設定します。circlePanel
コントロールの Location プロパティを (80, 10) に設定します。 最後に、LightShapeSelectionControl
の Size プロパティを (150, 80) に設定します。コード エディターで
LightShapeSelectionControl
ソース ファイルを開きます。 ファイルの先頭で System.Windows.Forms.Design 名前空間をインポートします。Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
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(); }
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
editorService
という名前の IWindowsFormsEditorService インスタンス変数を宣言します。Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
lightShapeValue
という名前のMarqueeLightShape
インスタンス変数を宣言します。private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
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); }
' 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
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 ); }
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
ソリューション エクスプローラーで、 [すべてのファイルを表示] ボタンをクリックします。 LightShapeSelectionControl.Designer.cs または LightShapeSelectionControl.Designer.vb ファイルを開き、Dispose メソッドの既定の定義を削除します。
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; } } }
' 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
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); } } }
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
デザイナーでカスタム コントロールをテストする
この段階で、MarqueeControlLibrary
プロジェクトをビルドすることができます。 MarqueeControl
クラスを継承するコントロールを作成し、それをフォームで使用することにより、実装をテストします。
カスタム MarqueeControl の実装を作成するには
Windows フォーム デザイナーで
DemoMarqueeControl
を開きます。 これにより、DemoMarqueeControl
型のインスタンスが作成され、MarqueeControlRootDesigner
型のインスタンス内に表示されます。ツールボックスで、 [MarqueeControlLibrary コンポーネント] タブを開きます。選択できる
MarqueeBorder
およびMarqueeText
コントロールが表示されます。MarqueeBorder
コントロールのインスタンスをDemoMarqueeControl
のデザイン画面にドラッグします。 このMarqueeBorder
コントロールを親コントロールにドッキングします。MarqueeText
コントロールのインスタンスをDemoMarqueeControl
のデザイン画面にドラッグします。ソリューションをビルドします。
DemoMarqueeControl
を右クリックし、ショートカット メニューから [テストを実行する] オプションを選択して、アニメーションを開始します。 [テストを停止する] をクリックして、アニメーションを停止します。デザイン ビューで [Form1] を開きます。
フォームに 2 つの Button コントロールを配置します。 それらに
startButton
およびstopButton
という名前を設定し、Text プロパティの値をそれぞれ Start および Stop に変更します。ツールボックスで、 [MarqueeControlTest コンポーネント] タブを開きます。選択できる
DemoMarqueeControl
が表示されます。DemoMarqueeControl
のインスタンスを Form1 のデザイン画面にドラッグします。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(); }
MarqueeControlTest
プロジェクトをスタートアップ プロジェクトとして設定し、実行します。 フォームにDemoMarqueeControl
が表示されます。 [開始] ボタンを選択して、アニメーションを開始します。 テキストが点滅し、ライトが境界線内を移動するはずです。
次のステップ
MarqueeControlLibrary
では、カスタム コントロールとそれに関連付けられたデザイナーの簡単な実装が示されています。 いくつかの方法で、このサンプルをより高度なものにすることができます。
DemoMarqueeControl
のプロパティの値をデザイナーで変更します。MarqueBorder
コントロールをさらに追加し、親インスタンス内にドッキングして、入れ子になった効果を作成します。UpdatePeriod
およびライト関連のプロパティについて、さまざまな設定を調べます。IMarqueeWidget
の独自の実装を作成します。 たとえば、点滅する "ネオン サイン" や、複数の画像が含まれるアニメーション化されたサインを作成してみます。デザイン時エクスペリエンスをさらにカスタマイズします。 Enabled と Visible 以外のプロパティをシャドウ処理してみたり、新しいプロパティを追加します。 新しいデザイナー動詞を追加して、子コントロールのドッキングなどの一般的なタスクを簡素化します。
MarqueeControl
にライセンスを設定します。コントロールのシリアル化方法とコードの生成方法を制御します。 詳細については、「動的なソース コードの生成とコンパイル」を参照してください。
関連項目
.NET Desktop feedback