逐步解說:建立採用設計階段功能的控制項
藉由製作相關聯的自訂設計工具,可以增強自訂控制項的設計階段體驗。
警告
此內容是針對 .NET Framework 所撰寫。 如果您使用 .NET 6 或更新版本,請謹慎使用此內容。 Windows Forms 的設計工具系統已變更,請務必檢閱自 .NET Framework 以來的設計工具變更一文。
此文章說明如何針對自訂控制項建立自訂設計工具。 您將實作 MarqueeControl
類型和稱為 MarqueeControlRootDesigner
的相關聯設計工具類別。
MarqueeControl
類型所實作的顯示器類似具有動畫風格燈光和閃爍文字的劇場霓虹燈。
適用於此控制項的設計工具會與設計環境互動,以提供自訂設計階段體驗。 透過自訂設計工具,您可以透過多種組合來組合具有動畫風格燈光和閃爍文字的自訂 MarqueeControl
實作。 您可以在表單上使用組合的控制項,就像任何其他 Windows Forms 控制項一樣。
完成此逐步解說之後,您的自訂控制項將看起來如下:
此應用程式顯示寫著「文字」的霓虹燈,以及 [開始] 和 [停止] 按鈕。
如需完整的程式碼清單,請參閱操作說明:建立採用設計階段功能的 Windows Forms 控制項 (英文)。
必要條件
為了完成此逐步解說,您將需要 Visual Studio。
建立專案
第一個步驟是建立應用程式專案。 您將使用此專案來建置裝載自訂控制項的應用程式。
在 Visual Studio 中,建立新的 Windows Forms 應用程式專案,並將其命名為 MarqueeControlTest。
建立控制項程式庫專案
將 Windows Forms 控制項程式庫專案新增至方案。 將專案命名為 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
在 [程式碼編輯器] 中,開啟
MarqueeControl
來源檔案。 在檔案頂端,匯入下列命名空間: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
將
MarqueeControlRootDesigner
的宣告變更為繼承自 DocumentDesigner 類別 (部分機器翻譯)。 套用 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 項目 (部分機器翻譯) 新增至
MarqueeControlLibrary
專案。 為新的來源檔案提供 DemoMarqueeControl 的基底名稱。在 [程式碼編輯器] 中,開啟
DemoMarqueeControl
檔案。 在檔案的頂端,匯入MarqueeControlLibrary
命名空間:Imports MarqueeControlLibrary
using MarqueeControlLibrary;
將
MarqueeControlRootDesigner
的宣告變更為繼承自 DocumentDesigner 類別 (部分機器翻譯)。組建專案。
在 Windows Forms 設計工具中,開啟 Form1。
在 [工具箱] 中尋找 [MarqueeControlTest 元件] 索引標籤並加以開啟。 將
DemoMarqueeControl
從 [工具箱] 拖曳到您的表單。組建專案。
設定專案以進行設計階段偵錯
當您開發自訂設計階段體驗時,必須對控制項和元件進行偵錯。 有一個簡單的方式可設定專案,以允許在設計階段進行偵錯。 如需詳細資訊,請參閱逐步解說:在設計階段偵錯自訂的 Windows Forms 控制項。
以滑鼠右鍵按一下
MarqueeControlLibrary
專案,然後選取 [屬性]。在 [MarqueeControlLibrary 屬性頁] 對話方塊中,選取 [偵錯] 頁面。
在 [開始動作] 區段中,選取 [啟動外部程式]。 您將針對個別的 Visual Studio 執行個體進行偵錯,因此,請按下省略符號 (Visual Studio [屬性] 視窗中的省略符號按鈕 (...)) 按鈕來瀏覽 Visual Studio IDE。 可執行檔的名稱是 devenv.exe,如果您已安裝至預設位置,則其路徑為 %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<版本>\Common7\IDE\devenv.exe。
選取 [確定] 關閉對話方塊。
以滑鼠右鍵按一下 MarqueeControlLibrary 專案,然後選取 [設定為啟始專案] 以啟用此偵錯設定。
檢查點
您現在已經準備好對自訂控制項的設計階段行為進行偵錯。 一旦您判斷偵錯環境已正確設定之後,您將測試自訂控制項與自訂設計工具之間的關聯。
測試偵錯環境和設計工具關聯
在 [程式碼編輯器] 中開啟 MarqueeControlRootDesigner 來源檔案,並在 WriteLine 陳述式上放置中斷點。
按 F5 以啟動偵錯工作階段。
Visual Studio 的新執行個體隨即建立。
在 Visual Studio 的新執行個體中,開啟 MarqueeControlTest 方案。 您可以從 [檔案] 功能表中選取 [最近使用的專案],輕鬆找到此方案。 MarqueeControlTest.sln 方案檔將列為最近使用的檔案。
在設計工具中,開啟
DemoMarqueeControl
。Visual Studio 的偵錯執行個體會在中斷點取得焦點並停止執行。 按 F5 以繼續偵錯工作階段。
此時,一切都已就緒,可供您開發並偵錯自訂控制項和其相關聯的自訂設計工具。 此文章的其餘部分將著重於實作控制項和設計工具功能的詳細資料。
實作自訂控制項
MarqueeControl
是帶有一點點自訂的 UserControl (部分機器翻譯)。 其會公開兩個方法:Start
(這會啟動霓虹燈動畫) 和 Stop
(這會停止動畫)。 由於 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
控制項。
MarqueeBorder
:此控制項會在其邊緣周圍繪製「燈光」的框線。 燈光會依序閃爍,使其看起來像是在框線周圍移動。 燈光閃爍的速度是由稱為UpdatePeriod
的屬性所控制。 其他數個自訂屬性會決定控制項外觀的其他層面。 有兩個稱為StartMarquee
和StopMarquee
的方法,可控制動畫的啟動和停止時間。MarqueeText
:此控制項會繪製閃爍的字串。 如同MarqueeBorder
控制項,文字閃爍的速度是由UpdatePeriod
屬性所控制。MarqueeText
控制項也具有與MarqueeBorder
控制項通用的StartMarquee
和StopMarquee
方法。
在設計階段,MarqueeControlRootDesigner
允許這兩個控制項類型以任意組合新增至 MarqueeControl
。
這兩個控制項的常見功能會被納入稱為 IMarqueeWidget
的介面中。 這可讓 MarqueeControl
探索任何與 Marquee 相關的子控制項,並給予其特殊待遇。
若要實作週期性動畫功能,您將使用來自 System.ComponentModel 命名空間 (部分機器翻譯) 的 BackgroundWorker 物件 (部分機器翻譯)。 您可以使用 Timer 物件 (部分機器翻譯),但若有許多 IMarqueeWidget
物件存在,單一 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
介面,以公開操作霓虹燈動畫的兩個方法和一個屬性:// 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" 的基底名稱。將 BackgroundWorker 元件從 [工具箱] 拖曳到您的
MarqueeText
控制項。 此元件將允許MarqueeText
控制項以非同步方式自我更新。在 [屬性] 視窗中,將 BackgroundWorker 元件 (部分機器翻譯) 的
WorkerReportsProgress
和 WorkerSupportsCancellation 屬性 (部分機器翻譯) 設定為 true。 這些設定可讓 BackgroundWorker 元件 (部分機器翻譯) 定期引發 ProgressChanged 事件 (部分機器翻譯) 和取消非同步更新。如需詳細資訊,請參閱 BackgroundWorker 元件 (部分機器翻譯)。
在 [程式碼編輯器] 中,開啟
MarqueeControl
來源檔案。 在檔案頂端,匯入下列命名空間: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
將
MarqueeText
的宣告變更為繼承自 Label (部分機器翻譯),並實作IMarqueeWidget
介面:[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 屬性 (Attribute) (部分機器翻譯) 會套用至
UpdatePeriod
屬性 (Property),使其出現在名為 "Marquee" 的 [屬性] (Property) 視窗自訂區段中。public virtual void StartMarquee() { // Start the updating thread and pass it the UpdatePeriod. this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod); } public virtual void StopMarquee() { // Stop the updating thread. this.backgroundWorker1.CancelAsync(); } [Category("Marquee")] [Browsable(true)] public int UpdatePeriod { get { return this.updatePeriodValue; } set { if (value > 0) { this.updatePeriodValue = value; } else { throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0"); } } }
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
。 Category (部分機器翻譯) 和 Browsable 屬性 (Attribute) (部分機器翻譯) 會套用至這些屬性 (Property),因此,屬性 (Property) 會出現在稱為 "Marquee" 的 [屬性] (Property) 視窗自訂區段中。[Category("Marquee")] [Browsable(true)] public Color LightColor { get { return this.lightColorValue; } set { // The LightColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.lightColorValue.ToArgb() != value.ToArgb()) { this.lightColorValue = value; this.lightBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public Color DarkColor { get { return this.darkColorValue; } set { // The DarkColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.darkColorValue.ToArgb() != value.ToArgb()) { this.darkColorValue = value; this.darkBrush = new SolidBrush(value); } } }
<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 事件處理常式 (部分機器翻譯) 會進入睡眠狀態
UpdatePeriod
所指定的毫秒數,然後引發 ProgressChanged 事件 (部分機器翻譯),直到您的程式碼呼叫 CancelAsync (部分機器翻譯) 來停止動畫為止。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
控制項,MarqueeBorder
控制項會稍微複雜一點。 其具有更多屬性,且 OnPaint 方法中的動畫更為複雜。 原則上,其與 MarqueeText
控制項相當類似。
因為 MarqueeBorder
控制項可以有子控制項,所以必須注意 Layout 事件 (部分機器翻譯)。
建立 MarqueeBorder 控制項
將新的自訂控制項項目新增至
MarqueeControlLibrary
專案。 為新的來源檔案提供 "MarqueeBorder" 的基底名稱。將 BackgroundWorker 元件從 [工具箱] 拖曳到您的
MarqueeText
控制項。 此元件將允許MarqueeText
控制項以非同步方式自我更新。在 [屬性] 視窗中,將 BackgroundWorker 元件 (部分機器翻譯) 的
WorkerReportsProgress
和 WorkerSupportsCancellation 屬性 (部分機器翻譯) 設定為 true。 這些設定可讓 BackgroundWorker 元件 (部分機器翻譯) 定期引發 ProgressChanged 事件 (部分機器翻譯) 和取消非同步更新。 如需詳細資訊,請參閱 BackgroundWorker 元件 (部分機器翻譯)。在 [屬性] 視窗中,選取 [事件] 按鈕。 針對 DoWork (部分機器翻譯) 和 ProgressChanged 事件 (部分機器翻譯) 附加處理常式。
在 [程式碼編輯器] 中,開啟
MarqueeControl
來源檔案。 在檔案頂端,匯入下列命名空間: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
將
MarqueeBorder
的宣告變更為繼承自 Panel (部分機器翻譯),並實作IMarqueeWidget
介面。[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
控制項的狀態: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 事件處理常式 (部分機器翻譯) 會進入睡眠狀態
UpdatePeriod
所指定的毫秒數,然後引發 ProgressChanged 事件 (部分機器翻譯),直到您的程式碼呼叫 CancelAsync (部分機器翻譯) 來停止動畫為止。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
-
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
控制項上的特定屬性「製作陰影」並進行篩選,變更其與設計環境的互動。
攔截對元件屬性存取子的呼叫稱為「製作陰影」。其可讓設計工具追蹤使用者所設定的值,並選擇性地將該值傳遞到正在設計的元件。
針對此範例,MarqueeBorderDesigner
將為 Visible (部分機器翻譯) 和 Enabled 屬性 (英文) 製作陰影,這可防止使用者在設計階段使 MarqueeBorder
控制項無法顯示或停用。
設計工具也可以新增和移除屬性。 針對此範例,Padding 屬性 (英文) 將在設計階段移除,因為 MarqueeBorder
控制項會根據 LightSize
屬性所指定的燈光大小,以程式設計方式設定邊框間距。
MarqueeBorderDesigner
的基底類別是 ComponentDesigner (部分機器翻譯),其有方法可以變更控制項在設計階段公開的屬性 (Attribute)、屬性 (Property) 和事件:
使用這些方法變更元件的公用介面時,請遵循下列規則:
僅新增或移除
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
將
MarqueeBorderDesigner
的宣告變更為繼承自 ParentControlDesigner (部分機器翻譯)。由於
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 類別 (部分機器翻譯)。 您的程式碼將實作兩個特定的自訂:處理元件變更,以及新增設計工具動詞命令。
當使用者設計其 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。
您會將兩個設計工具動詞命令新增至設計工具:執行測試與停止測試。 這些動詞命令將可讓您在設計階段檢視 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 (部分機器翻譯)。 您將建立兩個新的 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
控制項會在 [屬性] 視窗中公開數個屬性。 這其中兩個屬性 (MarqueeSpinDirection
和 MarqueeLightShape
) 會由列舉表示。 為了說明 UI 類型編輯器的用法,MarqueeLightShape
屬性將有相關聯的 UITypeEditor 類別 (部分機器翻譯)。
建立自訂 UI 類型編輯器
在 [程式碼編輯器] 中,開啟
MarqueeControl
來源檔案。在
MarqueeBorder
類別的定義中,宣告稱為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. 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
覆寫 OnLayout 方法。 此實作會傳回 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
覆寫 OnLayout 方法。 此實作會查詢 IWindowsFormsEditorService 物件 (部分機器翻譯) 的設計環境。 如果成功,其會建立
LightShapeSelectionControl
。 會叫用 DropDownControl 方法 (英文) 來啟動LightShapeEditor
。 此叫用的傳回值會傳回到設計環境。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
。 您將建立自訂控制項,僅用於以圖形方式在 [屬性] 視窗中顯示這些值。 您的 UITypeEditor (部分機器翻譯) 將使用此自訂控制項來與 [屬性] 視窗互動。
建立自訂 UI 類型編輯器的檢視控制項
將新的 UserControl 項目 (部分機器翻譯) 新增至
MarqueeControlLibrary
專案。 為新的來源檔案提供 LightShapeSelectionControl 的基底名稱。從 [工具箱] 將 Panel 控制項拖曳到
LightShapeSelectionControl
。 將其命名為squarePanel
和circlePanel
。 並排排列它們。 將這兩個 Panel 控制項 (部分機器翻譯) 的 Size 屬性 (部分機器翻譯) 設定為 [(60, 60)]。 將squarePanel
控制項的 Location 屬性 (部分機器翻譯) 設定為 [(8, 10)]。 將circlePanel
控制項的 Location 屬性設定為 [(80, 10)]。 最後,將LightShapeSelectionControl
的 Size 屬性 (部分機器翻譯) 設定為 [(150, 80)]。在 [程式碼編輯器] 中,開啟
MarqueeControl
來源檔案。 在檔案的頂端,匯入MarqueeControlLibrary
命名空間:Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
為
squarePanel
和circlePanel
控制項實作 Click 事件處理常式 (部分機器翻譯)。 這些方法會叫用 CloseDropDown (英文) 來結束自訂 UITypeEditor 編輯工作階段 (部分機器翻譯)。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
覆寫 OnLayout 方法。 此實作將繪製填滿的正方形和圓形。 其也將藉由在一個圖形或另一個圖形周圍繪製框線來醒目提示選取的值。
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 Form 設計工具中開啟
DemoMarqueeControl
。 這會建立DemoMarqueeControl
類型的執行個體,並將其顯示在MarqueeControlRootDesigner
類型的執行個體中。在 [工具箱] 中,開啟 [MarqueeControlLibrary 元件] 索引標籤。您將看到
MarqueeBorder
和MarqueeText
控制項可供選取。將
MarqueeBorder
控制項的執行個體拖曳到DemoMarqueeControl
設計介面。 將此MarqueeBorder
控制項停駐至父控制項。將
MarqueeBorder
控制項的執行個體拖曳到DemoMarqueeControl
設計介面。建置方案。
以滑鼠右鍵按一下
DemoMarqueeControl
,然後從捷徑功能表中選取 [執行測試] 選項,以啟動動畫。 按一下 [停止測試] 以停止動畫。在設計檢視中開啟 [Form1]。
將兩個 Button 控制項放置於表單上。 將其命名為
startButton
和stopButton
,並將 Text 屬性值 (部分機器翻譯) 分別變更為 [啟動] 和 [停止]。在 [工具箱] 中,開啟 [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
。控制將控制項序列化的方式,以及為其產生程式碼的方式。 如需詳細資訊,請參閱產生和編譯動態原始程式碼 (部分機器翻譯)。