逐步解說:建立利用設計階段功能的控制項
藉由撰寫相關聯的自訂設計工具,可以增強自訂控制項的設計階段體驗。
警告
此內容是針對 .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
在程式
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
將 的宣告
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 專案新增至
MarqueeControlTest
專案。 為新的原始程式檔提供 DemoMarqueeControl 的 基底名稱。在程式
DemoMarqueeControl
代碼編輯器 中 開啟檔案。 在檔案頂端,匯入MarqueeControlLibrary
命名空間:Imports MarqueeControlLibrary
using MarqueeControlLibrary;
將 的宣告
DemoMarqueeControl
變更為繼承自MarqueeControl
類別。組建專案。
在 Windows Forms 設計工具中開啟 Form1。
在 [工具箱 ] 中 尋找 [MarqueeControlTest 元件 ] 索引標籤,然後開啟它。
DemoMarqueeControl
將 從 [工具箱] 拖曳到您的表單。組建專案。
設定專案以進行設計階段偵錯
當您開發自訂設計階段體驗時,必須偵錯控制項和元件。 有一個簡單的方法可以設定您的專案,以允許在設計階段進行偵錯。 如需詳細資訊,請參閱 逐步解說:在設計階段 偵錯自訂 Windows Forms 控制項。
以滑鼠右鍵按一下
MarqueeControlLibrary
專案,然後選取 [ 屬性 ]。在 [ MarqueeControlLibrary 屬性頁] 對話方塊中,選取 [ 偵錯 ] 頁面。
在 [ 啟動動作] 區段中,選取 [ 啟動外部程式 ]。 您將偵錯個別的 Visual Studio 實例,因此按一下省略號 ( ) 按鈕以流覽 Visual Studio IDE。 可執行檔的名稱為 devenv.exe,如果您安裝到預設位置,其路徑為 %ProgramFiles(x86)%\Microsoft Visual Studio\2019\ < edition > \Common7\IDE\devenv.exe 。
選取 [確定] 關閉對話方塊。
以滑鼠右鍵按一下 MarqueeControlLibrary 專案,然後選取 [ 設定為啟始專案 ] 以啟用此偵錯組態。
Checkpoint
您現在已準備好偵錯自訂控制項的設計階段行為。 一旦您判斷偵錯環境已正確設定,您將測試自訂控制項與自訂設計工具之間的關聯。
測試偵錯環境和設計工具關聯
在程式碼編輯器 中 開啟 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
取決於配置,因此 MarqueeControl
會覆寫 OnLayout 方法,並呼叫 PerformLayout 此類型的子 MarqueeText
控制項。
這是自訂的範圍 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
也有StartMarquee
與 控制項通用的MarqueeBorder
和StopMarquee
方法。
在設計階段, MarqueeControlRootDesigner
允許將這兩個控制項類型加入至 MarqueeControl
任何組合的 。
這兩個控制項的常見功能會納入稱為 IMarqueeWidget
的介面。 這可讓 探索 MarqueeControl
任何與 Marquee 相關的子控制項,並給予他們特殊待遇。
若要實作定期動畫功能,您將使用 BackgroundWorker 命名空間中的 System.ComponentModel 物件。 您可以使用 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 元件 。
在程式
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
將 的宣告
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 屬性會套用至 屬性,
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
。 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事件處理常式會睡眠指定的毫秒
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 子控制項
控制項 MarqueeBorder
比 MarqueeText
控制項稍微複雜一點。 其具有更多屬性,而且方法中的 OnPaint 動畫更涉及。 原則上,它與 控制項相當類似 MarqueeText
。
MarqueeBorder
因為控制項可以有子控制項,所以它必須注意 Layout 事件。
若要建立 MarqueeBorder 控制項
將新的 自訂控制項 專案新增至
MarqueeControlLibrary
專案。 為新的來源檔案提供 「MarqueeBorder」 的基底名稱。將元件從 [工具箱 ] BackgroundWorker 拖曳至控制項
MarqueeBorder
。 此元件可讓MarqueeBorder
控制項以非同步方式更新本身。在 [ 屬性] 視窗中,將 BackgroundWorker 元件的
WorkerReportsProgress
和 WorkerSupportsCancellation 屬性設定為 true 。 這些設定可讓 BackgroundWorker 元件定期引發 ProgressChanged 事件,並取消非同步更新。 如需詳細資訊,請參閱 BackgroundWorker 元件 。在 [ 屬性] 視窗中,選取 [ 事件] 按鈕。 附加 和 ProgressChanged 事件的處理常式 DoWork 。
在程式
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
將 的宣告
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
特定屬性,並變更其與設計環境的互動。
攔截對元件屬性存取子的呼叫稱為「陰影」。它可讓設計工具追蹤使用者所設定的值,並選擇性地將該值傳遞至所設計的元件。
在此範例中 Visible ,和 Enabled 屬性會受到 MarqueeBorderDesigner
遮蔽,這可防止使用者在 MarqueeBorder
設計階段讓控制項不可見或停用。
設計工具也可以新增和移除屬性。 在此範例中, Padding 屬性會在設計階段移除,因為 MarqueeBorder
控制項會根據 屬性所 LightSize
指定的燈光大小,以程式設計方式設定邊框間距。
的基類 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
將 的宣告
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 方法。 這會導致 IMarqueeWidget
物件在變更其父系 Size 等屬性時適當地重新貼上本身。
處理元件變更
在程式
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
方法。 和 StopMarquee
方法的實作會在實 IMarqueeWidget
作 的 StartMarquee
自主控制項上呼叫這些方法,因此任何自主 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 類型編輯器
在程式
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
IWindowsFormsEditorService宣告名為
editorService
的執行個體變數。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
。 叫 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
。 並排排列它們。 Size將這兩 Panel 個控制項的 屬性設定為 (60, 60) 。 將 Location 控制項的squarePanel
屬性設定為 (8, 10) 。 將 Location 控制項的circlePanel
屬性設定為 (80, 10) 。 最後,將 的LightShapeSelectionControl
屬性設定 Size 為 (150, 80)。在程式
LightShapeSelectionControl
代碼編輯器 中 開啟原始程式檔。 在檔案頂端,匯入 System.Windows.Forms.Design 命名空間:Imports System.Windows.Forms.Design
using System.Windows.Forms.Design;
實 Click 作 和
circlePanel
控制項的squarePanel
事件處理常式。 這些方法會叫 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
IWindowsFormsEditorService宣告名為
editorService
的執行個體變數。Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
MarqueeLightShape
宣告名為lightShapeValue
的執行個體變數。private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
在建構函式中
LightShapeSelectionControl
,將 Click 事件處理常式附加至squarePanel
和circlePanel
控制項 Click 的事件。 此外,定義建構函式多載,以將設計環境的值指派MarqueeLightShape
給lightShapeValue
欄位。// This constructor takes a MarqueeLightShape value from the // design-time environment, which will be used to display // the initial state. public LightShapeSelectionControl( MarqueeLightShape lightShape, IWindowsFormsEditorService editorService ) { // This call is required by the designer. InitializeComponent(); // Cache the light shape value provided by the // design-time environment. this.lightShapeValue = lightShape; // Cache the reference to the editor service. this.editorService = editorService; // Handle the Click event for the two panels. this.squarePanel.Click += new EventHandler(squarePanel_Click); this.circlePanel.Click += new EventHandler(circlePanel_Click); }
' This constructor takes a MarqueeLightShape value from the ' design-time environment, which will be used to display ' the initial state. Public Sub New( _ ByVal lightShape As MarqueeLightShape, _ ByVal editorService As IWindowsFormsEditorService) ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Cache the light shape value provided by the ' design-time environment. Me.lightShapeValue = lightShape ' Cache the reference to the editor service. Me.editorService = editorService ' Handle the Click event for the two panels. AddHandler Me.squarePanel.Click, AddressOf squarePanel_Click AddHandler Me.circlePanel.Click, AddressOf circlePanel_Click End Sub
在 方法中 Dispose ,卸離 Click 事件處理常式。
protected override void Dispose( bool disposing ) { if( disposing ) { // Be sure to unhook event handlers // to prevent "lapsed listener" leaks. this.squarePanel.Click -= new EventHandler(squarePanel_Click); this.circlePanel.Click -= new EventHandler(circlePanel_Click); if(components != null) { components.Dispose(); } } base.Dispose( disposing ); }
Protected Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then ' Be sure to unhook event handlers ' to prevent "lapsed listener" leaks. RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click If (components IsNot Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub
在方案總管中,按一下 [顯示所有檔案] 按鈕。 開啟 LightShapeSelectionControl.Designer.cs 或 LightShapeSelectionControl.Designer.vb 檔案,然後移除方法的預設定義 Dispose 。
實作
LightShape
屬性。// LightShape is the property for which this control provides // a custom user interface in the Properties window. public MarqueeLightShape LightShape { get { return this.lightShapeValue; } set { if( this.lightShapeValue != value ) { this.lightShapeValue = value; } } }
' LightShape is the property for which this control provides ' a custom user interface in the Properties window. Public Property LightShape() As MarqueeLightShape Get Return Me.lightShapeValue End Get Set(ByVal Value As MarqueeLightShape) If Me.lightShapeValue <> Value Then Me.lightShapeValue = Value End If End Set End Property
覆寫 OnPaint 方法。 此實作會繪製填滿的正方形和圓形。 它也會藉由在一個圖案或另一個圖案周圍繪製框線來反白顯示選取的值。
protected override void OnPaint(PaintEventArgs e) { base.OnPaint (e); using( Graphics gSquare = this.squarePanel.CreateGraphics(), gCircle = this.circlePanel.CreateGraphics() ) { // Draw a filled square in the client area of // the squarePanel control. gSquare.FillRectangle( Brushes.Red, 0, 0, this.squarePanel.Width, this.squarePanel.Height ); // If the Square option has been selected, draw a // border inside the squarePanel. if( this.lightShapeValue == MarqueeLightShape.Square ) { gSquare.DrawRectangle( Pens.Black, 0, 0, this.squarePanel.Width-1, this.squarePanel.Height-1); } // Draw a filled circle in the client area of // the circlePanel control. gCircle.Clear( this.circlePanel.BackColor ); gCircle.FillEllipse( Brushes.Blue, 0, 0, this.circlePanel.Width, this.circlePanel.Height ); // If the Circle option has been selected, draw a // border inside the circlePanel. if( this.lightShapeValue == MarqueeLightShape.Circle ) { gCircle.DrawRectangle( Pens.Black, 0, 0, this.circlePanel.Width-1, this.circlePanel.Height-1); } } }
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) MyBase.OnPaint(e) Dim gCircle As Graphics = Me.circlePanel.CreateGraphics() Try Dim gSquare As Graphics = Me.squarePanel.CreateGraphics() Try ' Draw a filled square in the client area of ' the squarePanel control. gSquare.FillRectangle( _ Brushes.Red, _ 0, _ 0, _ Me.squarePanel.Width, _ Me.squarePanel.Height) ' If the Square option has been selected, draw a ' border inside the squarePanel. If Me.lightShapeValue = MarqueeLightShape.Square Then gSquare.DrawRectangle( _ Pens.Black, _ 0, _ 0, _ Me.squarePanel.Width - 1, _ Me.squarePanel.Height - 1) End If ' Draw a filled circle in the client area of ' the circlePanel control. gCircle.Clear(Me.circlePanel.BackColor) gCircle.FillEllipse( _ Brushes.Blue, _ 0, _ 0, _ Me.circlePanel.Width, _ Me.circlePanel.Height) ' If the Circle option has been selected, draw a ' border inside the circlePanel. If Me.lightShapeValue = MarqueeLightShape.Circle Then gCircle.DrawRectangle( _ Pens.Black, _ 0, _ 0, _ Me.circlePanel.Width - 1, _ Me.circlePanel.Height - 1) End If Finally gSquare.Dispose() End Try Finally gCircle.Dispose() End Try End Sub
在設計工具中測試您的自訂控制項
此時,您可以建置 MarqueeControlLibrary
專案。 建立繼承自 類別的控制項, MarqueeControl
並在表單上使用,以測試您的實作。
建立自訂 MarqueeControl 實作
在 Windows Form 設計工具中開啟
DemoMarqueeControl
。 這會建立 型別的實例,DemoMarqueeControl
並將其顯示在型別的實例中MarqueeControlRootDesigner
。在 [工具箱 ] 中 ,開啟 [ MarqueeControlLibrary 元件] 索引 標籤。您會看到
MarqueeBorder
和MarqueeText
控制項可供選取。將 控制項的
MarqueeBorder
實例拖曳到DemoMarqueeControl
設計介面上。 將此MarqueeBorder
控制項停駐至父控制項。將 控制項的
MarqueeText
實例拖曳到DemoMarqueeControl
設計介面上。建置方案。
以滑鼠右鍵按一下 ,
DemoMarqueeControl
然後從快捷方式功能表中選取 [ 執行測試] 選項以啟動動畫。 按一下 [ 停止測試 ] 以停止動畫。在設計檢視中開啟 [Form1]。
將兩個 Button 控制項放在表單上。 將它們
startButton
命名為 和stopButton
,並將屬性值分別變更 Text 為 [開始 ] 和 [停止 ]。在 [工具箱 ] 中 ,開啟 [ MarqueeControlTest 元件] 索引 標籤。您會看到
DemoMarqueeControl
可供選取的專案。將 的
DemoMarqueeControl
實例拖曳至 Form1 設計介面。在事件處理常式中 Click ,叫
Start
用 上的DemoMarqueeControl
和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
。 例如,您可以建立閃爍的「霓虹燈」或具有多個影像的動畫標誌。進一步自訂設計階段體驗。 您可以嘗試陰影超過 和 Visible 的屬性 Enabled ,而且您可以新增屬性。 新增設計工具動詞,以簡化一般工作,例如停駐子控制項。
MarqueeControl
授權 。控制控制項的序列化方式,以及為其產生程式碼的方式。 如需詳細資訊,請參閱 動態原始程式碼產生和編譯 。
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應