トレーニング
ラーニング パス
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization
このブラウザーはサポートされなくなりました。
Microsoft Edge にアップグレードすると、最新の機能、セキュリティ更新プログラム、およびテクニカル サポートを利用できます。
カスタム コントロールのデザイン時エクスペリエンスは、関連するカスタム デザイナーを作成することによって拡張できます。
注意事項
このコンテンツは .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 プロジェクトを右クリックし、 [スタートアップ プロジェクトに設定] を選択して、このデバッグ構成を有効にします。
これで、カスタム コントロールのデザイン時動作をデバッグする準備ができました。 デバッグ環境が正しく設定されていることを確認した後、カスタム コントロールとカスタム デザイナーの関連付けをテストします。
コード エディターで 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
コントロールは、MarqueeText
コントロールより少し高度です。 さらに多くのプロパティがあり、OnPaint メソッドのアニメーションも複雑になります。 原則としては、MarqueeText
コントロールと非常によく似ています。
MarqueeBorder
コントロールは子コントロールを持つことができるため、Layout イベントを認識する必要があります。
新しいカスタム コントロール項目を 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 を作成することによって実現できます。
MarqueeBorder
コントロールにより、いくつかのプロパティがプロパティ ウィンドウに公開されます。 これらのプロパティのうちの 2 つである MarqueeSpinDirection
と MarqueeLightShape
は、列挙型によって表されます。 UI 型エディターの使用方法を示すため、MarqueeLightShape
プロパティには UITypeEditor クラスが関連付けられています。
コード エディターで 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
MarqueeLightShape
プロパティにより、Square
と Circle
の 2 種類のライト形状がサポートされます。 プロパティ ウィンドウでこれらの値をグラフィカルに表示するためだけに使用されるカスタム コントロールを作成します。 このカスタム コントロールは、プロパティ ウィンドウと対話するために UITypeEditor によって使用されます。
新しい 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
クラスを継承するコントロールを作成し、それをフォームで使用することにより、実装をテストします。
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 に関するフィードバック
.NET Desktop feedback はオープンソース プロジェクトです。 フィードバックを提供するにはリンクを選択します。
トレーニング
ラーニング パス
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization
ドキュメント
方法: UserControl の実行時の動作をテストする - Windows Forms .NET Framework
この 10 ステップの手順を通じ、Windows フォームでユーザー コントロールの実行時の動作をテストする方法について説明します。
Visual C によって Windows フォームにコントロールを追加する# - C#
この記事では、Visual C# を使用して実行時に Windows フォームにコントロールをプログラムで追加する方法について説明します。また、メソッドを説明するコード サンプルも含まれています。
位置コントロール - Windows Forms .NET Framework
Visual Studio の Windows フォーム デザイナーまたは Location プロパティを使用してコントロールを配置する方法について説明します。