연습: 디자인 타임 기능을 활용하는 컨트롤 만들기
연결된 사용자 지정 디자이너를 작성하여 사용자 지정 컨트롤의 디자인 타임 환경을 향상시킬 수 있습니다.
주의
이 콘텐츠는 .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”라는 원본 파일을 삭제하여 프로젝트의 기본 컨트롤을 삭제합니다.
MarqueeControlLibrary
프로젝트에 새 UserControl 항목을 추가합니다. 새 원본 파일에 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
DocumentDesigner 클래스에서 상속할
MarqueeControlRootDesigner
의 선언을 변경합니다. 도구 상자와 디자이너 상호 작용을 지정하려면 ToolboxItemFilterAttribute을(를) 적용합니다.참고
MarqueeControlRootDesigner
클래스에 대한 정의는 MarqueeControlLibrary.Design 라는 네임스페이스에 묶였습니다. 이 선언은 디자이너를 디자인 관련 형식용으로 예약된 특수 네임스페이스에 배치합니다.namespace MarqueeControlLibrary.Design { [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public class MarqueeControlRootDesigner : DocumentDesigner {
Namespace MarqueeControlLibrary.Design <ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Public Class MarqueeControlRootDesigner Inherits DocumentDesigner
MarqueeControlRootDesigner
클래스의 생성자를 정의합니다. 생성자 본문에 WriteLine 문을 삽입합니다. 디버깅에 유용합니다.public MarqueeControlRootDesigner() { Trace.WriteLine("MarqueeControlRootDesigner ctor"); }
Public Sub New() Trace.WriteLine("MarqueeControlRootDesigner ctor") End Sub
사용자 지정 컨트롤의 인스턴스 만들기
MarqueeControlLibrary
프로젝트에 새 UserControl 항목을 추가합니다. 새 원본 파일에 DemoMarqueeControl이라는 기본 이름을 지정합니다.코드 편집기에서
DemoMarqueeControl
파일을 엽니다. 파일 맨 위에서MarqueeControlLibrary
네임스페이스를 가져옵니다.Imports MarqueeControlLibrary
using MarqueeControlLibrary;
DocumentDesigner 클래스에서 상속할
MarqueeControlRootDesigner
의 선언을 변경합니다.프로젝트를 빌드합니다.
Windows Form 디자이너에서 Form1을 엽니다.
도구 상자에서 MarqueeControlTest Components 구성 요소 탭을 찾아 엽니다. 도구 상자에서 폼으로
DemoMarqueeControl
을 끌어옵니다.프로젝트를 빌드합니다.
디자인 타임 디버깅을 위한 프로젝트 설정
사용자 지정 디자인 타임 환경을 개발하는 경우 컨트롤 및 구성 요소를 디버그해야 합니다. 디자인 타임에 디버깅을 허용하도록 프로젝트를 설정하는 간단한 방법이 있습니다. 자세한 내용은 연습: 디자인 타임에 사용자 지정 Windows Forms 컨트롤 디버깅을 참조하세요.
MarqueeControlLibrary
프로젝트를 마우스 오른쪽 단추로 클릭하고 속성을 선택합니다.MarqueeControlLibrary 속성 페이지 대화 상자에서 디버그 페이지를 선택합니다.
작업 시작 섹션에서 외부 프로그램 시작을 선택합니다. 별도 Visual Studio 인스턴스를 디버깅하게 되므로, 줄임표(Visual Studio 속성 창에 있는 줄임표 단추(...)) 단추를 클릭하여 Visual Studio IDE를 탐색합니다. 실행 파일의 이름이 devenv.exe 기본 위치에 설치한 경우 해당 경로는 %ProgramFiles(x86)%\Microsoft Visual Studio\2019\<edition>\Common7\IDE\devenv.exe입니다.
확인을 선택하여 대화 상자를 닫습니다.
MarqueeControlLibrary 프로젝트를 마우스 오른쪽 단추로 클릭하고 StartUp 프로젝트로 설정을 선택하여 이 디버깅 구성을 사용하도록 설정합니다.
검사점
이제 사용자 지정 컨트롤의 디자인 타임 동작을 디버그할 준비가 되었습니다. 디버깅 환경이 올바르게 설정되었는지 확인했으면 사용자 지정 컨트롤과 사용자 지정 디자이너 간의 연결을 테스트합니다.
디버깅 환경 및 디자이너 연결을 테스트하려면
코드 편집기에서 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
컨트롤에는StartMarquee
컨트롤과 공통적인StopMarquee
및MarqueeBorder
메서드도 있습니다.
디자인 타임에 MarqueeControlRootDesigner
은(는) 이러한 두 컨트롤 형식을 조합으로 MarqueeControl
에 추가할 수 있습니다.
두 컨트롤의 일반적인 기능은IMarqueeWidget
라는 인터페이스에 고려됩니다. 이렇게 하면 MarqueeControl
이(가) 선택 윤곽 관련 자식 컨트롤을 검색하고 특별한 대우를 받을 수 있습니다.
주기적인 애니메이션 기능을 구현하려면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
Label에서 상속하고
IMarqueeWidget
인터페이스를 구현할MarqueeText
선언을 변경합니다.[ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", ToolboxItemFilterType.Require)] public partial class MarqueeText : Label, IMarqueeWidget {
<ToolboxItemFilter("MarqueeControlLibrary.MarqueeText", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeText Inherits Label Implements IMarqueeWidget
노출된 속성에 해당하는 인스턴스 변수를 선언하고 생성자에서 초기화합니다. 이
isLit
필드는LightColor
속성에서 지정한 색으로 텍스트를 그릴지 여부를 결정합니다.// When isLit is true, the text is painted in the light color; // When isLit is false, the text is painted in the dark color. // This value changes whenever the BackgroundWorker component // raises the ProgressChanged event. private bool isLit = true; // These fields back the public properties. private int updatePeriodValue = 50; private Color lightColorValue; private Color darkColorValue; // These brushes are used to paint the light and dark // colors of the text. private Brush lightBrush; private Brush darkBrush; // This component updates the control asynchronously. private BackgroundWorker backgroundWorker1; public MarqueeText() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // Initialize light and dark colors // to the control's default values. this.lightColorValue = this.ForeColor; this.darkColorValue = this.BackColor; this.lightBrush = new SolidBrush(this.lightColorValue); this.darkBrush = new SolidBrush(this.darkColorValue); }
' When isLit is true, the text is painted in the light color; ' When isLit is false, the text is painted in the dark color. ' This value changes whenever the BackgroundWorker component ' raises the ProgressChanged event. Private isLit As Boolean = True ' These fields back the public properties. Private updatePeriodValue As Integer = 50 Private lightColorValue As Color Private darkColorValue As Color ' These brushes are used to paint the light and dark ' colors of the text. Private lightBrush As Brush Private darkBrush As Brush ' This component updates the control asynchronously. Private WithEvents backgroundWorker1 As BackgroundWorker Public Sub New() ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Initialize light and dark colors ' to the control's default values. Me.lightColorValue = Me.ForeColor Me.darkColorValue = Me.BackColor Me.lightBrush = New SolidBrush(Me.lightColorValue) Me.darkBrush = New SolidBrush(Me.darkColorValue) End Sub
IMarqueeWidget
인터페이스를 구현합니다.StartMarquee
및StopMarquee
메서드는 BackgroundWorker 구성 요소와 RunWorkerAsync 및 CancelAsync 메서드를 호출하여 애니메이션을 시작하고 중지합니다.Category 및 Browsable 특성이
UpdatePeriod
속성에 적용되므로 “선택 윤곽”이라는 속성 창 사용자 지정 섹션에 나타납니다.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 특성이 해당 속성에 적용되므로 “선택 윤곽”이라는 속성 창 사용자 지정 섹션에 나타납니다.[Category("Marquee")] [Browsable(true)] public Color LightColor { get { return this.lightColorValue; } set { // The LightColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.lightColorValue.ToArgb() != value.ToArgb()) { this.lightColorValue = value; this.lightBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public Color DarkColor { get { return this.darkColorValue; } set { // The DarkColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.darkColorValue.ToArgb() != value.ToArgb()) { this.darkColorValue = value; this.darkBrush = new SolidBrush(value); } } }
<Category("Marquee"), Browsable(True)> _ Public Property LightColor() As Color Get Return Me.lightColorValue End Get Set(ByVal Value As Color) ' The LightColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then Me.lightColorValue = Value Me.lightBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property DarkColor() As Color Get Return Me.darkColorValue End Get Set(ByVal Value As Color) ' The DarkColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then Me.darkColorValue = Value Me.darkBrush = New SolidBrush(Value) End If End Set End Property
BackgroundWorker 구성 요소와 DoWork 및 ProgressChanged 이벤트에 대한 처리기를 구현합니다.
DoWork 이벤트 처리기는 CancelAsync을(를) 호출하여 코드가 애니메이션을 중지할 때까지
UpdatePeriod
에서 지정된 시간(밀리초)에 대해 절전 모드로 설정한 다음 ProgressChanged 이벤트를 발생시킵니다.ProgressChanged 이벤트 처리기는 텍스트를 밝은 상태와 어두운 상태 사이로 전환하여 깜박이는 모양을 지정합니다.
// This method is called in the worker thread's context, // so it must not make any calls into the MarqueeText control. // Instead, it communicates to the control using the // ProgressChanged event. // // The only work done in this event handler is // to sleep for the number of milliseconds specified // by UpdatePeriod, then raise the ProgressChanged event. private void backgroundWorker1_DoWork( object sender, System.ComponentModel.DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; // This event handler will run until the client cancels // the background task by calling CancelAsync. while (!worker.CancellationPending) { // The Argument property of the DoWorkEventArgs // object holds the value of UpdatePeriod, which // was passed as the argument to the RunWorkerAsync // method. Thread.Sleep((int)e.Argument); // The DoWork eventhandler does not actually report // progress; the ReportProgress event is used to // periodically alert the control to update its state. worker.ReportProgress(0); } } // The ProgressChanged event is raised by the DoWork method. // This event handler does work that is internal to the // control. In this case, the text is toggled between its // light and dark state, and the control is told to // repaint itself. private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) { this.isLit = !this.isLit; this.Refresh(); }
' This method is called in the worker thread's context, ' so it must not make any calls into the MarqueeText control. ' Instead, it communicates to the control using the ' ProgressChanged event. ' ' The only work done in this event handler is ' to sleep for the number of milliseconds specified ' by UpdatePeriod, then raise the ProgressChanged event. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles backgroundWorker1.DoWork Dim worker As BackgroundWorker = CType(sender, BackgroundWorker) ' This event handler will run until the client cancels ' the background task by calling CancelAsync. While Not worker.CancellationPending ' The Argument property of the DoWorkEventArgs ' object holds the value of UpdatePeriod, which ' was passed as the argument to the RunWorkerAsync ' method. Thread.Sleep(Fix(e.Argument)) ' The DoWork eventhandler does not actually report ' progress; the ReportProgress event is used to ' periodically alert the control to update its state. worker.ReportProgress(0) End While End Sub ' The ProgressChanged event is raised by the DoWork method. ' This event handler does work that is internal to the ' control. In this case, the text is toggled between its ' light and dark state, and the control is told to ' repaint itself. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.isLit = Not Me.isLit Me.Refresh() End Sub
애니메이션을 사용하도록 OnPaint 메서드를 재정의합니다.
protected override void OnPaint(PaintEventArgs e) { // The text is painted in the light or dark color, // depending on the current value of isLit. this.ForeColor = this.isLit ? this.lightColorValue : this.darkColorValue; base.OnPaint(e); }
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) ' The text is painted in the light or dark color, ' depending on the current value of isLit. Me.ForeColor = IIf(Me.isLit, Me.lightColorValue, Me.darkColorValue) MyBase.OnPaint(e) End Sub
F6 키를 눌러 솔루션을 빌드합니다.
MarqueeBorder 자식 컨트롤 만들기
MarqueeBorder
컨트롤이 MarqueeText
컨트롤보다 약간 더 정교합니다. 더 많은 속성이 있으며 OnPaint 메서드의 애니메이션이 더 많이 관련됩니다. 원칙상 MarqueeText
컨트롤과 매우 유사합니다.
MarqueeBorder
컨트롤에는 자식 컨트롤이 있을 수 있으므로 Layout 이벤트를 인식해야 합니다.
MarqueeBorder 컨트롤을 만들려면
MarqueeControlLibrary
프로젝트에 새 사용자 지정 컨트롤 항목을 추가합니다. 새 소스 파일에 “MarqueeBorder”라는 기본 이름을 지정합니다.도구 상자에서 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
Panel에서 상속하고
IMarqueeWidget
인터페이스를 구현할MarqueeBorder
선언을 변경합니다.[Designer(typeof(MarqueeControlLibrary.Design.MarqueeBorderDesigner ))] [ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", ToolboxItemFilterType.Require)] public partial class MarqueeBorder : Panel, IMarqueeWidget {
<Designer(GetType(MarqueeControlLibrary.Design.MarqueeBorderDesigner)), _ ToolboxItemFilter("MarqueeControlLibrary.MarqueeBorder", _ ToolboxItemFilterType.Require)> _ Partial Public Class MarqueeBorder Inherits Panel Implements IMarqueeWidget
MarqueeBorder
컨트롤의 상태를 관리하기 위한 두 개의 열거형을 선언합니다. 즉,MarqueeSpinDirection
은(는) 광원이 테두리 주위에 “회전”하는 방향을 결정하고MarqueeLightShape
은(는) 광원의 모양(정사각형 또는 원형)을 결정합니다. 이러한 선언을MarqueeBorder
클래스 선언 앞에 배치합니다.// This defines the possible values for the MarqueeBorder // control's SpinDirection property. public enum MarqueeSpinDirection { CW, CCW } // This defines the possible values for the MarqueeBorder // control's LightShape property. public enum MarqueeLightShape { Square, Circle }
' This defines the possible values for the MarqueeBorder ' control's SpinDirection property. Public Enum MarqueeSpinDirection CW CCW End Enum ' This defines the possible values for the MarqueeBorder ' control's LightShape property. Public Enum MarqueeLightShape Square Circle End Enum
노출된 속성에 해당하는 인스턴스 변수를 선언하고 생성자에서 초기화합니다.
public static int MaxLightSize = 10; // These fields back the public properties. private int updatePeriodValue = 50; private int lightSizeValue = 5; private int lightPeriodValue = 3; private int lightSpacingValue = 1; private Color lightColorValue; private Color darkColorValue; private MarqueeSpinDirection spinDirectionValue = MarqueeSpinDirection.CW; private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square; // These brushes are used to paint the light and dark // colors of the marquee lights. private Brush lightBrush; private Brush darkBrush; // This field tracks the progress of the "first" light as it // "travels" around the marquee border. private int currentOffset = 0; // This component updates the control asynchronously. private System.ComponentModel.BackgroundWorker backgroundWorker1; public MarqueeBorder() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); // Initialize light and dark colors // to the control's default values. this.lightColorValue = this.ForeColor; this.darkColorValue = this.BackColor; this.lightBrush = new SolidBrush(this.lightColorValue); this.darkBrush = new SolidBrush(this.darkColorValue); // The MarqueeBorder control manages its own padding, // because it requires that any contained controls do // not overlap any of the marquee lights. int pad = 2 * (this.lightSizeValue + this.lightSpacingValue); this.Padding = new Padding(pad, pad, pad, pad); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); }
Public Shared MaxLightSize As Integer = 10 ' These fields back the public properties. Private updatePeriodValue As Integer = 50 Private lightSizeValue As Integer = 5 Private lightPeriodValue As Integer = 3 Private lightSpacingValue As Integer = 1 Private lightColorValue As Color Private darkColorValue As Color Private spinDirectionValue As MarqueeSpinDirection = MarqueeSpinDirection.CW Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square ' These brushes are used to paint the light and dark ' colors of the marquee lights. Private lightBrush As Brush Private darkBrush As Brush ' This field tracks the progress of the "first" light as it ' "travels" around the marquee border. Private currentOffset As Integer = 0 ' This component updates the control asynchronously. Private WithEvents backgroundWorker1 As System.ComponentModel.BackgroundWorker Public Sub New() ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Initialize light and dark colors ' to the control's default values. Me.lightColorValue = Me.ForeColor Me.darkColorValue = Me.BackColor Me.lightBrush = New SolidBrush(Me.lightColorValue) Me.darkBrush = New SolidBrush(Me.darkColorValue) ' The MarqueeBorder control manages its own padding, ' because it requires that any contained controls do ' not overlap any of the marquee lights. Dim pad As Integer = 2 * (Me.lightSizeValue + Me.lightSpacingValue) Me.Padding = New Padding(pad, pad, pad, pad) SetStyle(ControlStyles.OptimizedDoubleBuffer, True) End Sub
IMarqueeWidget
인터페이스를 구현합니다.StartMarquee
및StopMarquee
메서드는 BackgroundWorker 구성 요소와 RunWorkerAsync 및 CancelAsync 메서드를 호출하여 애니메이션을 시작하고 중지합니다.MarqueeBorder
컨트롤에 자식 컨트롤이 포함될 수 있으므로StartMarquee
메서드는 모든 자식 컨트롤을 열거하고IMarqueeWidget
을(를) 구현하는 자식StartMarquee
컨트롤을 호출합니다.StopMarquee
메서드의 구현이 비슷합니다.public virtual void StartMarquee() { // The MarqueeBorder control may contain any number of // controls that implement IMarqueeWidget, so find // each IMarqueeWidget child and call its StartMarquee // method. foreach (Control cntrl in this.Controls) { if (cntrl is IMarqueeWidget) { IMarqueeWidget widget = cntrl as IMarqueeWidget; widget.StartMarquee(); } } // Start the updating thread and pass it the UpdatePeriod. this.backgroundWorker1.RunWorkerAsync(this.UpdatePeriod); } public virtual void StopMarquee() { // The MarqueeBorder control may contain any number of // controls that implement IMarqueeWidget, so find // each IMarqueeWidget child and call its StopMarquee // method. foreach (Control cntrl in this.Controls) { if (cntrl is IMarqueeWidget) { IMarqueeWidget widget = cntrl as IMarqueeWidget; widget.StopMarquee(); } } // Stop the updating thread. this.backgroundWorker1.CancelAsync(); } [Category("Marquee")] [Browsable(true)] public virtual int UpdatePeriod { get { return this.updatePeriodValue; } set { if (value > 0) { this.updatePeriodValue = value; } else { throw new ArgumentOutOfRangeException("UpdatePeriod", "must be > 0"); } } }
Public Overridable Sub StartMarquee() _ Implements IMarqueeWidget.StartMarquee ' The MarqueeBorder control may contain any number of ' controls that implement IMarqueeWidget, so find ' each IMarqueeWidget child and call its StartMarquee ' method. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) widget.StartMarquee() End If Next cntrl ' Start the updating thread and pass it the UpdatePeriod. Me.backgroundWorker1.RunWorkerAsync(Me.UpdatePeriod) End Sub Public Overridable Sub StopMarquee() _ Implements IMarqueeWidget.StopMarquee ' The MarqueeBorder control may contain any number of ' controls that implement IMarqueeWidget, so find ' each IMarqueeWidget child and call its StopMarquee ' method. Dim cntrl As Control For Each cntrl In Me.Controls If TypeOf cntrl Is IMarqueeWidget Then Dim widget As IMarqueeWidget = CType(cntrl, IMarqueeWidget) widget.StopMarquee() End If Next cntrl ' Stop the updating thread. Me.backgroundWorker1.CancelAsync() End Sub <Category("Marquee"), Browsable(True)> _ Public Overridable Property UpdatePeriod() As Integer _ Implements IMarqueeWidget.UpdatePeriod Get Return Me.updatePeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.updatePeriodValue = Value Else Throw New ArgumentOutOfRangeException("UpdatePeriod", _ "must be > 0") End If End Set End Property
속성 접근자를 구현합니다.
MarqueeBorder
컨트롤에는 모양을 제어하기 위한 몇 가지 속성이 있습니다.[Category("Marquee")] [Browsable(true)] public int LightSize { get { return this.lightSizeValue; } set { if (value > 0 && value <= MaxLightSize) { this.lightSizeValue = value; this.DockPadding.All = 2 * value; } else { throw new ArgumentOutOfRangeException("LightSize", "must be > 0 and < MaxLightSize"); } } } [Category("Marquee")] [Browsable(true)] public int LightPeriod { get { return this.lightPeriodValue; } set { if (value > 0) { this.lightPeriodValue = value; } else { throw new ArgumentOutOfRangeException("LightPeriod", "must be > 0 "); } } } [Category("Marquee")] [Browsable(true)] public Color LightColor { get { return this.lightColorValue; } set { // The LightColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.lightColorValue.ToArgb() != value.ToArgb()) { this.lightColorValue = value; this.lightBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public Color DarkColor { get { return this.darkColorValue; } set { // The DarkColor property is only changed if the // client provides a different value. Comparing values // from the ToArgb method is the recommended test for // equality between Color structs. if (this.darkColorValue.ToArgb() != value.ToArgb()) { this.darkColorValue = value; this.darkBrush = new SolidBrush(value); } } } [Category("Marquee")] [Browsable(true)] public int LightSpacing { get { return this.lightSpacingValue; } set { if (value >= 0) { this.lightSpacingValue = value; } else { throw new ArgumentOutOfRangeException("LightSpacing", "must be >= 0"); } } } [Category("Marquee")] [Browsable(true)] [EditorAttribute(typeof(LightShapeEditor), typeof(System.Drawing.Design.UITypeEditor))] public MarqueeLightShape LightShape { get { return this.lightShapeValue; } set { this.lightShapeValue = value; } } [Category("Marquee")] [Browsable(true)] public MarqueeSpinDirection SpinDirection { get { return this.spinDirectionValue; } set { this.spinDirectionValue = value; } }
<Category("Marquee"), Browsable(True)> _ Public Property LightSize() As Integer Get Return Me.lightSizeValue End Get Set(ByVal Value As Integer) If Value > 0 AndAlso Value <= MaxLightSize Then Me.lightSizeValue = Value Me.DockPadding.All = 2 * Value Else Throw New ArgumentOutOfRangeException("LightSize", _ "must be > 0 and < MaxLightSize") End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightPeriod() As Integer Get Return Me.lightPeriodValue End Get Set(ByVal Value As Integer) If Value > 0 Then Me.lightPeriodValue = Value Else Throw New ArgumentOutOfRangeException("LightPeriod", _ "must be > 0 ") End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightColor() As Color Get Return Me.lightColorValue End Get Set(ByVal Value As Color) ' The LightColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.lightColorValue.ToArgb() <> Value.ToArgb() Then Me.lightColorValue = Value Me.lightBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property DarkColor() As Color Get Return Me.darkColorValue End Get Set(ByVal Value As Color) ' The DarkColor property is only changed if the ' client provides a different value. Comparing values ' from the ToArgb method is the recommended test for ' equality between Color structs. If Me.darkColorValue.ToArgb() <> Value.ToArgb() Then Me.darkColorValue = Value Me.darkBrush = New SolidBrush(Value) End If End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property LightSpacing() As Integer Get Return Me.lightSpacingValue End Get Set(ByVal Value As Integer) If Value >= 0 Then Me.lightSpacingValue = Value Else Throw New ArgumentOutOfRangeException("LightSpacing", _ "must be >= 0") End If End Set End Property <Category("Marquee"), Browsable(True), _ EditorAttribute(GetType(LightShapeEditor), _ GetType(System.Drawing.Design.UITypeEditor))> _ Public Property LightShape() As MarqueeLightShape Get Return Me.lightShapeValue End Get Set(ByVal Value As MarqueeLightShape) Me.lightShapeValue = Value End Set End Property <Category("Marquee"), Browsable(True)> _ Public Property SpinDirection() As MarqueeSpinDirection Get Return Me.spinDirectionValue End Get Set(ByVal Value As MarqueeSpinDirection) Me.spinDirectionValue = Value End Set End Property
BackgroundWorker 구성 요소와 DoWork 및 ProgressChanged 이벤트에 대한 처리기를 구현합니다.
DoWork 이벤트 처리기는 CancelAsync을(를) 호출하여 코드가 애니메이션을 중지할 때까지
UpdatePeriod
에서 지정된 시간(밀리초)에 대해 절전 모드로 설정한 다음 ProgressChanged 이벤트를 발생시킵니다.ProgressChanged 이벤트 처리기는 다른 조명의 밝기/어두운 상태가 결정되는 “기본” 조명의 위치를 증가시키고 컨트롤 자체를 다시 칠하도록 Refresh 메서드를 호출합니다.
// This method is called in the worker thread's context, // so it must not make any calls into the MarqueeBorder // control. Instead, it communicates to the control using // the ProgressChanged event. // // The only work done in this event handler is // to sleep for the number of milliseconds specified // by UpdatePeriod, then raise the ProgressChanged event. private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; // This event handler will run until the client cancels // the background task by calling CancelAsync. while (!worker.CancellationPending) { // The Argument property of the DoWorkEventArgs // object holds the value of UpdatePeriod, which // was passed as the argument to the RunWorkerAsync // method. Thread.Sleep((int)e.Argument); // The DoWork eventhandler does not actually report // progress; the ReportProgress event is used to // periodically alert the control to update its state. worker.ReportProgress(0); } } // The ProgressChanged event is raised by the DoWork method. // This event handler does work that is internal to the // control. In this case, the currentOffset is incremented, // and the control is told to repaint itself. private void backgroundWorker1_ProgressChanged( object sender, System.ComponentModel.ProgressChangedEventArgs e) { this.currentOffset++; this.Refresh(); }
' This method is called in the worker thread's context, ' so it must not make any calls into the MarqueeBorder ' control. Instead, it communicates to the control using ' the ProgressChanged event. ' ' The only work done in this event handler is ' to sleep for the number of milliseconds specified ' by UpdatePeriod, then raise the ProgressChanged event. Private Sub backgroundWorker1_DoWork( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.DoWorkEventArgs) _ Handles backgroundWorker1.DoWork Dim worker As BackgroundWorker = CType(sender, BackgroundWorker) ' This event handler will run until the client cancels ' the background task by calling CancelAsync. While Not worker.CancellationPending ' The Argument property of the DoWorkEventArgs ' object holds the value of UpdatePeriod, which ' was passed as the argument to the RunWorkerAsync ' method. Thread.Sleep(Fix(e.Argument)) ' The DoWork eventhandler does not actually report ' progress; the ReportProgress event is used to ' periodically alert the control to update its state. worker.ReportProgress(0) End While End Sub ' The ProgressChanged event is raised by the DoWork method. ' This event handler does work that is internal to the ' control. In this case, the currentOffset is incremented, ' and the control is told to repaint itself. Private Sub backgroundWorker1_ProgressChanged( _ ByVal sender As Object, _ ByVal e As System.ComponentModel.ProgressChangedEventArgs) _ Handles backgroundWorker1.ProgressChanged Me.currentOffset += 1 Me.Refresh() End Sub
도우미 메서드
IsLit
및DrawLight
를 구현합니다.IsLit
메서드는 지정된 위치에서 조명의 색을 결정합니다. “조명”인 조명은LightColor
속성에서 지정한 색으로 그려지고 “어둡게”인 조명은DarkColor
속성에서 지정한 색으로 그려집니다.DrawLight
메서드는 적절한 색, 모양 및 위치를 사용하여 빛을 그립니다.// This method determines if the marquee light at lightIndex // should be lit. The currentOffset field specifies where // the "first" light is located, and the "position" of the // light given by lightIndex is computed relative to this // offset. If this position modulo lightPeriodValue is zero, // the light is considered to be on, and it will be painted // with the control's lightBrush. protected virtual bool IsLit(int lightIndex) { int directionFactor = (this.spinDirectionValue == MarqueeSpinDirection.CW ? -1 : 1); return ( (lightIndex + directionFactor * this.currentOffset) % this.lightPeriodValue == 0 ); } protected virtual void DrawLight( Graphics g, Brush brush, int xPos, int yPos) { switch (this.lightShapeValue) { case MarqueeLightShape.Square: { g.FillRectangle(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue); break; } case MarqueeLightShape.Circle: { g.FillEllipse(brush, xPos, yPos, this.lightSizeValue, this.lightSizeValue); break; } default: { Trace.Assert(false, "Unknown value for light shape."); break; } } }
' This method determines if the marquee light at lightIndex ' should be lit. The currentOffset field specifies where ' the "first" light is located, and the "position" of the ' light given by lightIndex is computed relative to this ' offset. If this position modulo lightPeriodValue is zero, ' the light is considered to be on, and it will be painted ' with the control's lightBrush. Protected Overridable Function IsLit(ByVal lightIndex As Integer) As Boolean Dim directionFactor As Integer = _ IIf(Me.spinDirectionValue = MarqueeSpinDirection.CW, -1, 1) Return (lightIndex + directionFactor * Me.currentOffset) Mod Me.lightPeriodValue = 0 End Function Protected Overridable Sub DrawLight( _ ByVal g As Graphics, _ ByVal brush As Brush, _ ByVal xPos As Integer, _ ByVal yPos As Integer) Select Case Me.lightShapeValue Case MarqueeLightShape.Square g.FillRectangle( _ brush, _ xPos, _ yPos, _ Me.lightSizeValue, _ Me.lightSizeValue) Exit Select Case MarqueeLightShape.Circle g.FillEllipse( _ brush, _ xPos, _ yPos, _ Me.lightSizeValue, _ Me.lightSizeValue) Exit Select Case Else Trace.Assert(False, "Unknown value for light shape.") Exit Select End Select End Sub
OnLayout 및 OnPaint 메서드를 재정의합니다.
OnPaint 메서드는
MarqueeBorder
컨트롤의 가장자리를 따라 조명을 그립니다.OnPaint 메서드는
MarqueeBorder
컨트롤의 크기에 따라 달라지므로 레이아웃이 변경될 때마다 호출해야 합니다. 이를 위해 OnLayout을(를) 재정의하고 Refresh을(를) 호출합니다.protected override void OnLayout(LayoutEventArgs levent) { base.OnLayout(levent); // Repaint when the layout has changed. this.Refresh(); } // This method paints the lights around the border of the // control. It paints the top row first, followed by the // right side, the bottom row, and the left side. The color // of each light is determined by the IsLit method and // depends on the light's position relative to the value // of currentOffset. protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; g.Clear(this.BackColor); base.OnPaint(e); // If the control is large enough, draw some lights. if (this.Width > MaxLightSize && this.Height > MaxLightSize) { // The position of the next light will be incremented // by this value, which is equal to the sum of the // light size and the space between two lights. int increment = this.lightSizeValue + this.lightSpacingValue; // Compute the number of lights to be drawn along the // horizontal edges of the control. int horizontalLights = (this.Width - increment) / increment; // Compute the number of lights to be drawn along the // vertical edges of the control. int verticalLights = (this.Height - increment) / increment; // These local variables will be used to position and // paint each light. int xPos = 0; int yPos = 0; int lightCounter = 0; Brush brush; // Draw the top row of lights. for (int i = 0; i < horizontalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); xPos += increment; lightCounter++; } // Draw the lights flush with the right edge of the control. xPos = this.Width - this.lightSizeValue; // Draw the right column of lights. for (int i = 0; i < verticalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); yPos += increment; lightCounter++; } // Draw the lights flush with the bottom edge of the control. yPos = this.Height - this.lightSizeValue; // Draw the bottom row of lights. for (int i = 0; i < horizontalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); xPos -= increment; lightCounter++; } // Draw the lights flush with the left edge of the control. xPos = 0; // Draw the left column of lights. for (int i = 0; i < verticalLights; i++) { brush = IsLit(lightCounter) ? this.lightBrush : this.darkBrush; DrawLight(g, brush, xPos, yPos); yPos -= increment; lightCounter++; } } }
Protected Overrides Sub OnLayout(ByVal levent As LayoutEventArgs) MyBase.OnLayout(levent) ' Repaint when the layout has changed. Me.Refresh() End Sub ' This method paints the lights around the border of the ' control. It paints the top row first, followed by the ' right side, the bottom row, and the left side. The color ' of each light is determined by the IsLit method and ' depends on the light's position relative to the value ' of currentOffset. Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim g As Graphics = e.Graphics g.Clear(Me.BackColor) MyBase.OnPaint(e) ' If the control is large enough, draw some lights. If Me.Width > MaxLightSize AndAlso Me.Height > MaxLightSize Then ' The position of the next light will be incremented ' by this value, which is equal to the sum of the ' light size and the space between two lights. Dim increment As Integer = _ Me.lightSizeValue + Me.lightSpacingValue ' Compute the number of lights to be drawn along the ' horizontal edges of the control. Dim horizontalLights As Integer = _ (Me.Width - increment) / increment ' Compute the number of lights to be drawn along the ' vertical edges of the control. Dim verticalLights As Integer = _ (Me.Height - increment) / increment ' These local variables will be used to position and ' paint each light. Dim xPos As Integer = 0 Dim yPos As Integer = 0 Dim lightCounter As Integer = 0 Dim brush As Brush ' Draw the top row of lights. Dim i As Integer For i = 0 To horizontalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) xPos += increment lightCounter += 1 Next i ' Draw the lights flush with the right edge of the control. xPos = Me.Width - Me.lightSizeValue ' Draw the right column of lights. 'Dim i As Integer For i = 0 To verticalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) yPos += increment lightCounter += 1 Next i ' Draw the lights flush with the bottom edge of the control. yPos = Me.Height - Me.lightSizeValue ' Draw the bottom row of lights. 'Dim i As Integer For i = 0 To horizontalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) xPos -= increment lightCounter += 1 Next i ' Draw the lights flush with the left edge of the control. xPos = 0 ' Draw the left column of lights. 'Dim i As Integer For i = 0 To verticalLights - 1 brush = IIf(IsLit(lightCounter), Me.lightBrush, Me.darkBrush) DrawLight(g, brush, xPos, yPos) yPos -= increment lightCounter += 1 Next i End If End Sub
섀도 및 필터 속성에 대한 사용자 지정 디자이너 만들기
MarqueeControlRootDesigner
클래스는 루트 디자이너에 대한 구현을 제공합니다. 이 디자이너 외에도 MarqueeControl
에서 작동하는 MarqueeBorder
컨트롤과 특별히 연결된 사용자 지정 디자이너가 필요합니다. 이 디자이너는 사용자 지정 루트 디자이너의 컨텍스트에서 적절한 사용자 지정 동작을 제공합니다.
특히 MarqueeBorderDesigner
은(는) 특정 속성을 MarqueeBorder
컨트롤의 “그림자”로 지정하고 필터링하여 디자인 환경과의 상호 작용을 변경합니다.
구성 요소의 속성 접근자에 대한 호출을 가로채는 것을 “그림자”라고 합니다. 디자이너는 사용자가 설정한 값을 추적하고 필요에 따라 해당 값을 디자인 중인 구성 요소에 전달할 수 있습니다.
이 예제에서는 디자인 타임 동안 사용자가 MarqueeBorder
컨트롤을 보이지 않거나 사용하지 않도록 설정할 수 없으므로 MarqueeBorderDesigner
에 의해 Visible 속성과 Enabled 속성이 그림자로 표시됩니다.
디자이너는 속성을 추가 및 제거할 수도 있습니다. 이 예제에서는 MarqueeBorder
컨트롤이 LightSize
속성에 지정된 조명의 크기에 따라 패딩을 프로그래밍 방식으로 설정하기 때문에 디자인 타임에 Padding 속성이 제거됩니다.
MarqueeBorderDesigner
에 대한 기본 클래스는 디자인 타임에 컨트롤에 의해 노출되는 특성, 속성 및 이벤트를 변경할 수 있는 메서드가 있는 ComponentDesigner입니다.
이러한 메서드를 사용하여 구성 요소의 공용 인터페이스를 변경하는 경우 다음 규칙을 따릅니다.
PreFilter
메서드에서만 항목 추가 또는 제거PostFilter
메서드에서만 기존 항목 수정항상
PreFilter
메서드에서 기본 구현을 먼저 호출합니다.항상
PostFilter
메서드에서 기본 구현을 마지막으로 호출합니다.
이러한 규칙을 준수하면 디자인 타임 환경의 모든 디자이너가 디자인 중인 모든 구성 요소를 일관되게 볼 수 있습니다.
ComponentDesigner 클래스는 그림자 속성의 값을 관리하기 위한 사전을 제공하므로 특정 인스턴스 변수를 만들 필요가 없습니다.
속성을 숨기고 필터링하는 사용자 지정 디자이너를 만들려면
디자인 폴더를 마우스 오른쪽 단추로 클릭하고 새 클래스를 추가합니다. 원본 파일에 MarqueeBorderDesigner라는 기본 이름을 지정합니다.
코드 편집기에서 MarqueeBorderDesigner 원본 파일을 엽니다. 파일의 맨 위에서 다음 네임스페이스를 가져옵니다.
using System; using System.Collections; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics; using System.Windows.Forms; using System.Windows.Forms.Design;
Imports System.Collections Imports System.ComponentModel Imports System.ComponentModel.Design Imports System.Diagnostics Imports System.Windows.Forms Imports System.Windows.Forms.Design
ParentControlDesigner에서 상속할
MarqueeBorderDesigner
의 선언을 변경합니다.MarqueeBorder
컨트롤에는 자식 컨트롤이 포함될 수 있으므로 부모-자식 상호 작용을 처리하는 ParentControlDesigner에서MarqueeBorderDesigner
이(가) 상속됩니다.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
클래스 정의에서 UITypeEditor에서 파생되는LightShapeEditor
클래스를 선언합니다.// This class demonstrates the use of a custom UITypeEditor. // It allows the MarqueeBorder control's LightShape property // to be changed at design time using a customized UI element // that is invoked by the Properties window. The UI is provided // by the LightShapeSelectionControl class. internal class LightShapeEditor : UITypeEditor {
' This class demonstrates the use of a custom UITypeEditor. ' It allows the MarqueeBorder control's LightShape property ' to be changed at design time using a customized UI element ' that is invoked by the Properties window. The UI is provided ' by the LightShapeSelectionControl class. Friend Class LightShapeEditor Inherits UITypeEditor
editorService
라고 하는 IWindowsFormsEditorService인스턴스 변수를 선언합니다.private IWindowsFormsEditorService editorService = null;
Private editorService As IWindowsFormsEditorService = Nothing
OnLayout 메서드를 재정의합니다. 이 구현은 디자인 환경에
LightShapeEditor
표시 방법을 알려주는 DropDown을(를) 반환합니다.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 형식 편집기용 보기 컨트롤을 만들려면
MarqueeControlLibrary
프로젝트에 새 UserControl 항목을 추가합니다. 새 소스 파일에 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 이벤트 처리기를 구현합니다. 이러한 메서드는 사용자 지정 UITypeEditor 편집 세션을 종료하기 위해 CloseDropDown을(를) 호출합니다.private void squarePanel_Click(object sender, EventArgs e) { this.lightShapeValue = MarqueeLightShape.Square; this.Invalidate( false ); this.editorService.CloseDropDown(); } private void circlePanel_Click(object sender, EventArgs e) { this.lightShapeValue = MarqueeLightShape.Circle; this.Invalidate( false ); this.editorService.CloseDropDown(); }
Private Sub squarePanel_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Me.lightShapeValue = MarqueeLightShape.Square Me.Invalidate(False) Me.editorService.CloseDropDown() End Sub Private Sub circlePanel_Click( _ ByVal sender As Object, _ ByVal e As EventArgs) Me.lightShapeValue = MarqueeLightShape.Circle Me.Invalidate(False) Me.editorService.CloseDropDown() End Sub
editorService
라고 하는 IWindowsFormsEditorService인스턴스 변수를 선언합니다.Private editorService As IWindowsFormsEditorService
private IWindowsFormsEditorService editorService;
lightShapeValue
라고 하는MarqueeLightShape
인스턴스 변수를 선언합니다.private MarqueeLightShape lightShapeValue = MarqueeLightShape.Square;
Private lightShapeValue As MarqueeLightShape = MarqueeLightShape.Square
LightShapeSelectionControl
생성자에서 Click 이벤트 처리기를squarePanel
및circlePanel
컨트롤의 Click 이벤트에 연결합니다. 또한 디자인 환경의MarqueeLightShape
값을lightShapeValue
필드에 할당하는 생성자 오버로드를 정의합니다.// This constructor takes a MarqueeLightShape value from the // design-time environment, which will be used to display // the initial state. public LightShapeSelectionControl( MarqueeLightShape lightShape, IWindowsFormsEditorService editorService ) { // This call is required by the designer. InitializeComponent(); // Cache the light shape value provided by the // design-time environment. this.lightShapeValue = lightShape; // Cache the reference to the editor service. this.editorService = editorService; // Handle the Click event for the two panels. this.squarePanel.Click += new EventHandler(squarePanel_Click); this.circlePanel.Click += new EventHandler(circlePanel_Click); }
' This constructor takes a MarqueeLightShape value from the ' design-time environment, which will be used to display ' the initial state. Public Sub New( _ ByVal lightShape As MarqueeLightShape, _ ByVal editorService As IWindowsFormsEditorService) ' This call is required by the Windows.Forms Form Designer. InitializeComponent() ' Cache the light shape value provided by the ' design-time environment. Me.lightShapeValue = lightShape ' Cache the reference to the editor service. Me.editorService = editorService ' Handle the Click event for the two panels. AddHandler Me.squarePanel.Click, AddressOf squarePanel_Click AddHandler Me.circlePanel.Click, AddressOf circlePanel_Click End Sub
Dispose 메서드에서 Click 이벤트 처리기를 분리합니다.
protected override void Dispose( bool disposing ) { if( disposing ) { // Be sure to unhook event handlers // to prevent "lapsed listener" leaks. this.squarePanel.Click -= new EventHandler(squarePanel_Click); this.circlePanel.Click -= new EventHandler(circlePanel_Click); if(components != null) { components.Dispose(); } } base.Dispose( disposing ); }
Protected Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then ' Be sure to unhook event handlers ' to prevent "lapsed listener" leaks. RemoveHandler Me.squarePanel.Click, AddressOf squarePanel_Click RemoveHandler Me.circlePanel.Click, AddressOf circlePanel_Click If (components IsNot Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub
솔루션 탐색기에서 모든 파일 표시 단추를 클릭합니다. LightShapeSelectionControl.Designer.cs 또는 LightShapeSelectionControl.Designer.vb 파일을 열고 Dispose 메서드의 기본 정의를 제거합니다.
LightShape
속성을 구현합니다.// LightShape is the property for which this control provides // a custom user interface in the Properties window. public MarqueeLightShape LightShape { get { return this.lightShapeValue; } set { if( this.lightShapeValue != value ) { this.lightShapeValue = value; } } }
' LightShape is the property for which this control provides ' a custom user interface in the Properties window. Public Property LightShape() As MarqueeLightShape Get Return Me.lightShapeValue End Get Set(ByVal Value As MarqueeLightShape) If Me.lightShapeValue <> Value Then Me.lightShapeValue = Value End If End Set End Property
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 Forms 디자이너에서
DemoMarqueeControl
을 엽니다. 그러면DemoMarqueeControl
형식의 인스턴스가 만들어지고MarqueeControlRootDesigner
형식의 인스턴스에 표시됩니다.도구 상자에서 MarqueeControlLibrary 구성 요소 탭을 엽니다. 선택할 수 있는
MarqueeBorder
및MarqueeText
컨트롤이 표시됩니다.MarqueeBorder
컨트롤의 인스턴스를DemoMarqueeControl
디자인 화면으로 끌어다 놓습니다. 이MarqueeBorder
컨트롤을 부모 컨트롤에 도킹합니다.MarqueeBorder
컨트롤의 인스턴스를DemoMarqueeControl
디자인 화면으로 끌어다 놓습니다.솔루션을 빌드합니다.
DemoMarqueeControl
을(를) 마우스 오른쪽 단추를 클릭하고 바로 가기 메뉴에서 테스트 실행 옵션을 선택하여 애니메이션을 시작합니다. 테스트 중지를 클릭하여 애니메이션을 중지합니다.디자인 뷰에서 Form1을 엽니다.
두 Button 컨트롤을 양식에 배치합니다.
startButton
및stopButton
의 이름을 지정하고 Text 속성 값을 각각 시작 및 중지로 변경합니다.도구 상자에서 MarqueeControlTest 구성 요소 탭을 엽니다. 선택할 수 있는
DemoMarqueeControl
컨트롤이 표시됩니다.Form1 디자인 화면으로
DemoMarqueeControl
의 인스턴스를 끕니다.Click 이벤트 처리기의
DemoMarqueeControl
에서Start
및Stop
메서드를 호출합니다.Private Sub startButton_Click(sender As Object, e As System.EventArgs) Me.demoMarqueeControl1.Start() End Sub 'startButton_Click Private Sub stopButton_Click(sender As Object, e As System.EventArgs) Me.demoMarqueeControl1.Stop() End Sub 'stopButton_Click
private void startButton_Click(object sender, System.EventArgs e) { this.demoMarqueeControl1.Start(); } private void stopButton_Click(object sender, System.EventArgs e) { this.demoMarqueeControl1.Stop(); }
MarqueeControlTest
프로젝트를 시작 프로젝트로 설정하고 실행합니다.DemoMarqueeControl
양식이 표시됩니다. 시작 단추를 선택하여 애니메이션을 시작합니다. 텍스트가 깜박이고 표시등이 테두리 주위를 이동하는 것을 볼 수 있습니다.
다음 단계
MarqueeControlLibrary
은(는) 사용자 지정 컨트롤 및 관련 디자이너의 간단한 구현을 보여 줍니다. 이 샘플은 여러 가지 방법으로 더 정교하게 만들 수 있습니다.
디자이너에서
DemoMarqueeControl
의 속성 값을 변경합니다.MarqueBorder
컨트롤을 더 추가하고 부모 인스턴스 내에 도킹하여 중첩된 효과를 만듭니다.UpdatePeriod
및 조명 관련 속성에 대한 다양한 설정을 실험해 볼 수 있습니다.IMarqueeWidget
의 자체 구현을 작성합니다. 예를 들어 깜박이는 “네온 사인” 또는 여러 이미지가 있는 애니메이션 기호를 만들 수 있습니다.디자인 타임 환경을 추가로 사용자 지정합니다. Enabled 및 Visible보다 많은 속성을 섀도잉할 수 있으며 새 속성을 추가할 수 있습니다. 새 디자이너 동사를 추가하여 자식 컨트롤 도킹과 같은 일반적인 작업을 간소화합니다.
MarqueeControl
라이선스를 부여합니다.컨트롤이 직렬화되는 방법 및 컨트롤에 대한 코드 생성 방법을 제어합니다. 자세한 내용은 동적 소스 코드 생성 및 컴파일을 참조하세요.
참고 항목
.NET Desktop feedback