기본적인컴포넌트의제작 (Creating Basic Components) 델파이는가장잘된윈도우용 OOP 개발환경이라고말할수있다. 이러한델파이의가장핵심부분은누가뭐라해도델파이의튼튼한컴포넌트라고말할수있다. 컴포넌트란 OOP 의기본개념을충실하게지원하는델파이의객체로, 이러한컴포넌트를개발하는방법이야말로, 델파이개발자에게는가장중요한기술이라고말해도과언이아니다. 이번장에서는컴포넌트가동작하는방법을이해하고실제로컴포넌트를제작하는방법에대해서알아보기로한다. 컴포넌트의구조 컴포넌트는크게나누어필드, 메소드, 프로퍼티라는세가지의파트로나뉘어있다. 필드는 객체내부의데이터변수이며, 메소드는객체에속한프로시저와함수를말하고, 프로퍼티 란객체에속한데이터와코드에접근하는방법을제공하는엔티티이다. 간단한컴포넌트의제작 컴포넌트를만드는가장간단한방법은 Component New Component... 메뉴를선택해서 Component Expert 를시작하는것이다. 여기에서앞에서와같이새로만들컴포넌트가상속할클래스의이름과컴포넌트클래스의
이름, 그리고컴포넌트가위치할컴포넌트팔레트페이지를지정하면뼈대가되는코드가 만들어진다. 만들어진코드는다음과같다. unit ExamButton; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TExamButton = class(tbutton) private { Private declarations } protected { Protected declarations } public { Public declarations } published { Published declarations } procedure Register; implementation procedure Register; RegisterComponents('Samples', [TExamButton]); end. 이렇게뼈대가만들어지면, 프로퍼티나메소드등을추가해서간단하게확장된새로운컴포
넌트를제작할수있다. TExamButton 컴포넌트를클릭하면간단한메시지박스를표시할 수있도록수정해보자. 이를구현하려면 protected 섹션에서 Click 메소드를다음과같이 오버라이드하면된다. protected procedure Click; override; 그리고, 다음과같이구현한다. procedure TExamButton.Click; inherited Click; ShowMessage( 클릭! ); inherited 키워드는오버라이드한과거의메소드를실행하도록하는키워드이다. 이렇게만들어진컴포넌트는파일로저장하고, Component Install Component 메뉴를통해서실제로인스톨할수있게된다. 참고 : 오버라이드할메소드알아내기컴포넌트를제작할때특정이벤트에서다른동작을하도록만들고싶을때가있다. 이럴때에는보통오브젝트인스펙터에서관찰할수있는이벤트이름에서 On 을뺀이름의메소드를오버라이드하면된다. 앞의예제에서는 OnClick 이벤트에대해서조작을가하기위해 On 을뺀 Click 메소드를오버라이드하였다. 상속 (Inheritance) 의활용 상속이란부모클래스에속해있는메소드와프로퍼티를재사용하거나 override 해서사용할수있는특성을말하는것이다. 이를이용하면부모클래스의기능을유지한채, 새로운기능을추가해서향상된클래스를쉽게만들어낼수있다. Protected 프로퍼티의노출 델파이에는많은수의 TCustomXxxx 클래스가있는데, 이들의모든프로퍼티는 protected 로정의되어있다. 이들은컴포넌트팔레트에는나타나지않지만실제로사용되는컴포넌트클래스의기초가되는것들이다. 예를들어, TCustomEdit 는 TCustimMaskEdit,
TCustomMemo, TDBLookupCombo, TEdit, TSpinEdit 컴포넌트의공통된부모클래스이다. 또한, TCustomMaskEdit 컴포넌트는 TDBEdit, TMaskEdit 의공통된부모클래스가된다. 이렇게기초가되는부모 TCustomXxxx 클래스를상속받아서이들의 protected 프로퍼티, 이벤트를오브젝트인스펙터에나타나게하려면이들을 published 섹션에재선언만해주면된다. 예를들어, TSpeedButton 클래스는 Align 프로퍼티가없는데, 이프로퍼티를추가하려면다음과같이 Align 프로퍼티를 published 섹션에재선언해주는컴포넌트를만들면된다. unit AlignBtn; inteface uses Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms, Dialogs, Menus, Buttons; type TAlignSpeedButton = class(tspeedbutton) published property Align; // 원래는 protected 섹션에선언되어있었음 procedure Register; implementation RegisterComponents( Samples, [TAlignSpeedButton]); end. 상속받은프로퍼티감추기 위의경우와는반대로, 상속받아만든컴포넌트의프로퍼티나이벤트를사용자가오브젝트인스펙터를통해접근하지못하도록하고싶을때가있다. 이럴때에는기존의프로퍼티와같은이름의프로퍼티를재선언하고, 이를 read-only 로설정하면된다. 다음의코드는 TPanel 컴포넌트에서 Left, Top, Height, Width 프로퍼티를없앤컴포넌트이다.
TSnapPanel = class(tpanel) private FDummyProperty: Byte; // 프로퍼티를숨기기위해사용되는필드 ( 중략 ) published property Height: Byte read FDummyProperty; property Left: Byte read FDummyProperty; property Top: Byte read FDummyProperty; property Width: Byte read FDummyProperty; 가상메소드의 override 메소드를 override 하는것이클래스의기능을확장하는가장빠른방법이다. 델파이로컴포넌트를다룰때에는각각의컴포넌트가 publish 하는다양한이벤트에익숙해져야한다. 새로운컴포넌트를상속받을때에가장흔히하는실수는상속받은컴포넌트의생성자에서이벤트핸들러를동적으로생성하고, 여기에값을대입하는것이다. 이렇게하면, 사용자가해당이벤트에해당되는핸들러를사용하게되면, 생성자에서만든이벤트핸들러는절대로호출되지않는다. 그러므로, 해당컴포넌트가이벤트에반응해야한다면이벤트핸들러를만들지말고처리해야할이벤트와대응되는가상메소드를 override 하도록한다. 문제는가상메소드와연결된이벤트에대한정보를찾기가어렵다는것인데, 이를알기위해서는본래소스코드를봐야알수있겠지만, 일반적으로 VCL 에서는이벤트이름의 On 부분을뺀이름이가상메소드의이름이다. 예를들어, OnClick 이벤트에해당하는가상메소드는 Click 이다. 다음컴포넌트는버튼컴포넌트의 Click 메소드를 override 해서버튼을클릭할때마다소리를나게하는것이다. unit SountBtn; interface uses Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms, Dialogs, Menus, StdCtrols;
type TSountButton = class(tbutton) private FSoundFile: string; protected public constructor Create(AOwner: TComponent); override; procedure Click; override; published property SoundFile: string read FSoundFile write FSoundFile; procedure Register; implementation uses MMSystem; procedure TSoundButton.Click; sndplaysound(@fsoundfile, snd_async or snd_nodefault); inherited Click; constructor TSoundButton.Create(AOwner: TComponent); inherited Create(AOwner); FSoundFile := *.wav ; procedure Register; RegisterComponents( Samples, [TSoundButton]);
end. 윈도우메시지핸들러 전통적인윈도우프로그래밍에서가장중요한것중의하나가윈도우에전달된메시지를처 리하는것이다. 델파이는많은부분을처리해준다. 그렇지만, 델파이가처리하지못하는 메시지는개발자가직접처리해주어야한다. 메시지 - 처리시스템의이해 모든델파이클래스는메시지를처리하기위한기본적인방법으로메시지-처리메소드나메시지핸들러와같은방법을사용한다. 메시지핸들러의기본적인아이디어는메시지를받은클래스가메시지종류에따라특정메소드세트를호출하는것이다. 이때, 지정된메소드가없을경우에는디폴트핸들러가실행된다. 다음그림은메시지-디스패치 (message-dispatch) 시스템에대한다이어그램이다. VCL 은메시지 - 디스패치시스템을모든윈도우메시지를특정클래스의메소드호출로처 리한다. 개발자가할일은메시지 - 처리메소드를생성하는것이다. 1. 윈도우메시지 델파이에서의윈도우메시지는몇개의필드로이루어진데이터레코드이다. 그중에서도가장중요한것은메시지를확인할수있는정수값이다. 윈도우는많은메시지를정의하고있다. 그리고, 이런메시지들은 Messages.pas 유닛에선언되어있으며이들은정수값으로구별된다. 그리고, 메시지레코드에는 2 개의파라미터필드와 1 개의결과필드를포함하고있다. 하나의파라미터는 16 비트이며, 다른하나는 32 비트값이다. 이를 Win32 에서의표현방법을이용하면각각 wparam, lparam 에해당하며, lparam 값의경우순서를나누어 lparamhi, lparamlo 와같이접근하는것이가능하다. 처음윈도우를이용해서프로그래밍을할때에는 API 를사용할때, 개발자가각각의파라미터의내용을찾아봐야했다. 그런데, 지금은메시지크래킹 (message cracking) 이라고하는명명된파라미터를사용하기때문에이해하는것이간단해졌다. 예를들어, WM_KEYDOWN 메시지의파라미터는 nvirtkey, lkeydata 이다.
2. 메시지의디스패칭 (Dispatching Message) 어플리케이션이윈도우를생성할때, 윈도우프로시저를윈도우커널에등록하게된다. 이때윈도우프로시저는윈도우로넘어오는메시지를처리하는루틴을말한다. 전통적으로윈도우프로시저는각각의메시지에대한커다란 case 문으로작성했었다. 여기에서꼭기억해두어야할것은윈도우핸들을가진모든윈도우가메시지를처리한다는것이다. 즉, 새로운윈도우를생성할때마다, 이들은반드시완전한형태의윈도우프로시저를각각가지고있어야하는것이다. 델파이는메시지디스패칭을다음과같은방법으로단순화하였다. - 각각의컴포넌트는완전한메시지-디스패칭시스템을상속한다. - 디스패치시스템은디폴트처리가된다. 그러므로, 개발자는디폴트처리와다른부분 만핸들러를만들어주면된다. - 메시지처리를할때에도, 일부내용만수정하고나머지처리부분을상속해서처리하 면된다. 3. 메시지흐름의추적 델파이는어플리케이션에있는각각의컴포넌트에대한윈도우프로시저로 MainWndProc 라는메소드를등록한다. MainWndProc 메소드에는예외처리블록을포함하고있으며, 메시지구조체를윈도우에서 WndProc 라는가상메소드로넘겨주고, 예외처리는클래스의 HandleException 메소드를호출하여수행된다. MainWndProc 메소드는특별히메시지를처리하지는않는다. 실제로, 처리하는부분은필요에의해메소드를오버라이드할수있는 WndProc 에서이루어진다. WndProc 메소드는메시지를처리할때영향을미칠수있는특별한조건들을검사해서, 원하지않는메시지를처리할수있다. 예를들어, 드래그를하고있을때컴포넌트는키보드이벤트를무시한다. 그러므로, TWinControl 클래스의 WndProc 메소드는드래그를하지않을때에만키보드이벤트를처리한다. 결국에는 WndProc 가 TObject 객체에서상속받은 Dispatch 메소드를호출하게되며여기에서메시지를처리할메소드가어떤것인지를결정하게된다. Dispatch 메소드는메시지구조체의 Msg 필드를이용하여, 특정메시지를어떻게디스패치할것인지를결정한다. 컴포넌트가특정메시지에대한핸들러를정의한다면 Dispatch 는그메소드를호출하며, 해당메시지에대한메소드가없으면 DefaultHandler 를호출한다. 메시지처리방법의변경
컴포넌트에대한메시지처리방법을변경하기전에, 실제하려고하는것이무엇인지를분명히해야한다. 델파이는대부분의윈도우메시지를컴포넌트의이벤트로번역해서처리하므로많은경우에는직접메시지를처리하기보다는이벤트를처리하는것으로해결이된다. 메시지처리방법을변경하기위해서는메시지를처리하는메소드를오버라이드해야한다. 1. 메시지핸들러메소드의오버라이드 컴포넌트가특정메시지를처리하는방법을변경하려면, 메시지를처리하는메소드를오버라이드해야한다. 컴포넌트에메시지를처리하는메소드가없는경우라면, 새로운메시지처리메소드를선언해야한다. 메시지처리메소드를오버라이드하기위해서는, 메소드가오버라이드하고있는것과같은메시지인덱스를이용해서새로운메소드를선언하면된다. override 지시어를사용하는것이아니라, 동일한메시지인덱스를이용하여 message 지시어를사용한다는것에주의한다. 예를들어, WM_PAINT 메시지를처리하는메소드인 WMPaint 메소드를다음과같이선언하여사용할수있다. type TMyComponent = class(...)... procedure WMPaint(var Message: TWMPaint); message WM_PAINT; 2. 메시지파라미터의활용 메시지핸들러에넘겨진파라미터는 var 형이므로, 핸들러에서파라미터의값을변경할수있다. Message 파라미터의데이터형은처리할메시지에따라다양하다. 그러므로, 이를잘알기위해서는윈도우메시지에대한문서를참고해야한다. 만약, 과거스타일로 WParam, LParam 등으로메시지파라미터를표현한경우에는이를 TMessage 데이터형으로형전환해서사용한다. 3. 메시지트래핑 어떤경우에는컴포넌트가메시지를무시하도록하고싶을때가있다. 이렇게메시지를트 랩하도록하려면 WndProc 메소드를오버라이드한다. WndProc 메소드는앞에서설명했듯
이 Dispatch 메소드가메시지처리메소드를호출하기전에메시지를처리할수있기때문 에, 여기에서디스패치를하기전에메시지를거를수있다. TWinControl 에서상속한컨 트롤의 WndProc 를다음과같이오버라이드하여사용할수있다. procedure TMyControl.WndProc(var Message: TMessage); inherited WndProc(Message); WndProc 메소드를오버라이드하면메시지의범위를거를수있고, 메시지를디스패치하 지않도록할수있기때문에핸들러는호출되지않는다. 다음의코드는 TControl 에대한 WndProc 메소드의일부이다. procedure TControl.WndProc(var Message: TMessage);... if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then if Dragging then // 드래그를하고있다면, DragMouseMsg(TWMMouse(Message)) // 마우스드래그에해당하는메시지처리를... else... // 아니면, 정상적인처리를한다.... 새로운메시지핸들러의작성 델파이는대부분의윈도우메시지에대한핸들러를제공하기때문에, 메시지핸들러를새로 작성할필요가있는경우는사용자정의메시지를정의해서여기에대한메시지핸들러를 작성할때가많다. 1. 메시지의정의 많은수의표준컴포넌트들이내부적인사용을위해메시지를정의한다. 이렇게메시지를 정의하는이유로가장흔한것이표준윈도우메시지로처리할수없는정보나, 상태의변 경을알리기위한것이다.
메시지 identifier 는정수형으로, 윈도우는특정번호이하만사용하기때문에사용자정의메시지에나름대로번호를부여해서사용할수있다. WM_APP 상수는사용자정의메시지의시작번호를대표한다. 그러므로, 메시지를정의할때에는 WM_APP 를바탕으로하여 identifier 를결정해서사용한다. 그런데, 주의해야할것은표준윈도우컨트롤중에서도사용자정의메시지범위에들어가는메시지를사용하는경우가있다는점이다. 이런컴포넌트에는리스트박스, 콤보박스, 에디트박스와버튼등이있다. 이런컴포넌트를이용해서새로운메시지를정의할때에는 Messages.pas 유닛을참고하여컨트롤이이미사용하고있는윈도우메시지와겹치지않도록주의해야한다. 사용자정의메시지는다음과같이정의하면된다. const WM_MYFIRSTMESSAGE = WM_APP + 400; WM_MYSECONDMESSAGE = WM_APP + 401; 메시지레코드는메시지처리메소드로전송되는파라미터의데이터형이다. 만약메시지의파라미터를사용하지않거나, 과거와같이 wparam, lparam 을사용한파라미터를사용할경우에는디폴트메시지레코드인 TMessage 를사용하면된다. 메시지레코드데이터형을선언하기위해서는다음과같은규칙을따라야한다. - Msg 레코드의첫번째필드는 TMsgParam 데이터형으로선언한다. - 그다음의 2 바이트를 Word 파라미터로설정하고다음 2 바이트는사용하지않거나, 4 바이트를 LongInt 파라미터를설정한다. - 마지막으로 LongInt 형의 Resut 필드를추가한다. 예를들어, 모든마우스메시지를처리하는 TWMMouse 메시지레코드의선언부를여기에 소개한다. 가변형레코드를사용하여, 같은파라미터를 2 개의세트로정의해서사용한다. type TWMMouse = record Msg: TMsgParam; Keys: Word; // 메시지 ID //wparam 에해당 case Integer of //lparam 을해석하는방법이 2 가지! 0: ( XPos: Integer; //x, y 좌표로접근 YPos: Integer);
1: ( Pos: TPoint; Result: Longint); // 위치 // 결과필드 2. 새로운메시지 - 처리메소드의선언 새로운메시지-처리메소드는컴포넌트가표준컴포넌트에의해처리되지않는윈도우메시지를처리하거나, 자신의메시지를정의해서사용할경우에필요하다. 다음의코드는 CM_CHANGECOLOR 라는사용자정의메시지에대한메시지핸들러를선언한것이다. const CM_CHANGECOLOR = WM_APP + 400; type TMyComponent = class(tcontrol)... protected procedure CMChangeColor(var Message: TMessage); message CM_CHANGECOLOR; procedure TMyComponent.CMChangeColor(var Message: TMessage); Color := Message.lParam; inherited; 그러면, VCL 에서 TControl 컴포넌트에 OnClick 이벤트를처리할수있도록하는부분의 소스코드를살펴보자. 다소복잡하지만기본적인방법은동일하게사용되고있다. TControl = class(tcomponent) private FOnClick: TNotifyEvent; ( 중략 )
procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP; ( 중략 ) protected ( 중략 ) procedure Click; dynamic; property OnClick: TNotifyEvent read FOnClick write FOnClick; ( 중략 ) ( 중략 ) procedure TControl.Click; if Assigned(FOnClick) then FOnClick(Self); procedure TControl.WMLButtonUp(var Message: TWMLButtonUp); inherited; if cscapturemouse in ControlStyle then MouseCapture := False; if csclicked in ControlStyle then Exclude(FControlState, csclicked); if PtInRect(ClientRect, SmallPointToPoint(Message.Pos)) then Click; // 이벤트핸들러를호출하게되는가상메소드를호출한다. DoMouseUp(Message, mbleft); end 컴포넌트에서그래픽이용하기 델파이는윈도우 GDI 를여러가지레벨로캡슐화하고있다. GDI 함수를직접호출할때에
는디바이스컨텍스트에대한핸들을사용하게된다. 일단그래픽이미지를그리고나면, 반드시디바이스컨텍스트를원래의상태로복귀시키고이를처리해야한다. 10 장에서이미그래픽에대한내용을다룬바있지만, 컴포넌트를제작하기위해서는여기에대해서잘알고있어야한다. 델파이는컴포넌트에 Canvas 프로퍼티를제공함으로써복잡한 GDI 함수를직접호출해서사용하는것을대신한다. 캔버스는리소스를관리하고, 선택하고, 사용하는등의모든작업을도맡아하게된다. 델파이를사용할때의장점으로는이밖에도리소스를캐쉬하기때문에나중에다시사용하거나, 반복적인작업을할때속도의증진을기대할수있다. 캔버스의활용 Canvas 클래스는윈도우그래픽을여러가지레벨에서캡슐화한다. 즉, 도형을그리거나라인을그리고, 텍스트를그릴수있는고수준함수에서부터, 윈도우 GDI 에접근하는저수준함수까지지원한다. 다음에캔버스에대한내용을정리하였다. 레벨작업메소드와프로퍼티 High Intermediate 라인과도형그리기텍스트디스플레이와측정영역채우기텍스트와그래픽정의픽셀처리이미지복사와병합 MoveTo, LineTo, Rectangle, Ellipse TextOut, TextHeight, TextWidth, TextRect FillRect, FloodFill Pen, Brush, Font 프로퍼티 Pixels 프로퍼티 Draw, StretchDraw, BrushCopy, CopyRect 메소드 CopyMode 프로퍼티 Low GDI 함수호출 Handle 프로퍼티 Picture 객체작업하기 델파이는캔버스에직접그림을그리는것이외에, 비트맵이나메타파일, 아이콘등과같 은그래픽이미지를처리할수있는 Picture 객체를제공한다. 1. Picture, graphic, canvas 델파이가그래픽을처리하는클래스에는 3 가지가있다. 이들을구별하고잘사용하는것이중요하다. 앞에서도간단히설명했지만, 캔버스는폼이나, 그래픽컨트롤, 프린터또는비트맵의표면에그릴수있는객체이다. 다시말해서, 실제로화폭에그림을그리는화가를연상할때
화폭에해당되는것이캔버스이다. 보통캔버스는독립적인클래스로사용하지않고, 컨트롤의프로퍼티로제공된다. 그래픽은파일이나리소스의형태로접근할수있는그래픽이미지를대표한다. 델파이는 TGraphic 클래스를상속한 TBitmap, TIcon, TMetafile 등의클래스를제공한다. TGraphic 클래스는여러가지다른종류의그래픽에서공통적으로사용하는표준인터페이스를정의하고있다. Picture 는그래픽의컨테이너로, 어떤그래픽클래스도담을수있다. 그리고, 어플리케이션은이들에게똑같은방법으로접근할수있는방법을제공한다. 예를들어, Image 컨트롤은 Picture 프로퍼티를제공하는데이를이용하여여러종류의그래픽이미지를표시할수있다. 2. 팔레트다루기 256 색상의비디오모드처럼팔레트에기초한디바이스를사용할때에델파이는자동으로팔레트의 realization 을지원한다. 대부분의컨트롤은팔레트가필요없다. 그렇지만, 그래픽이미지를표시하는컨트롤은이미지를제대로표시하기위해서는윈도우와스크린디바이스드라이버와상호작용해야한다. 윈도우는이런작업을팔레트의 realization 이라고한다. 다시말해, 팔레트를 realize 하는것은현재의윈도우에모든팔레트를사용하도록하고, 배경윈도우는가능한팔레트를가장가까운색상과맞도록표시하는작업이다. 그러므로, 활성화된윈도우가바뀔경우에윈도우는팔레트를 realize 해야한다. 컨트롤에대한팔레트를지정하기위해서는컨트롤의 GetPalette 메소드를오버라이드하여팔레트의핸들을반환하도록한다. 컨트롤이 GetPalette 메소드를오버라이드하여팔레트를지정하면, 델파이는자동으로윈도우의팔레트메시지에반응한다. 팔레트메시지를처리하는메소드는 PaletteChanged 이다. PaletteChanged 메소드의주목적은컨트롤의팔레트를 realize 하는것이다. 윈도우가팔레트를 realization 할때에는활성화된윈도우에 foreground 팔레트를가지도록하고, 다른 background 팔레트를가지도록한다. 델파이는여기에서추가적으로윈도우내에있는컨트롤들의탭순서에따라팔레트를 realize 한다. 그러므로, 디폴트핸들러를오버라이드하여팔레트를관리할필요가있는경우는탭순서에서첫번째가아닌컨트롤에 foreground 팔레트를적용하고자할경우이외에는거의없다. Off-screen 비트맵 복잡한그래픽이미지를그릴때윈도우프로그래밍에서공통적인테크닉은 off-screen 비 트맵을생성하고, 비트맵에이미지를그리고, 스크린에비트맵을복사하여붙여넣는작업이 있다. Off-screen 이미지를사용하면, 스크린에반복적인그리기작업을할때처럼깜빡이
는현상을줄일수있다. 델파이의비트맵클래스역시 off-screen 이미지로작업할수있 다. 변화에반응하기 모든그래픽객체는객체의변화에반응하는이벤트를가질수있다. 이런이벤트를이용하여컴포넌트가이미지를다시그릴때반응하도록할수있는것이다. 그래픽객체의변화를반영하는것은그래픽객체를디자인-타임인터페이스로사용할때에더욱중요하다. 그래픽객체의변화에반응하려면, 클래스의 OnChange 이벤트에메소드를대입해야한다. 예를들어, TShape 컴포넌트는펜, 브러쉬등의객체를프로퍼티로제공하는데, 컴포넌트의 constructor 에서메소드를 OnChange 이벤트에대입하여컴포넌트가펜이나브러쉬가변경되면이미지를 refresh 하도록한다. type TShape = class(tgraphiccontrol) public procedure StyleChanged(Sender: TObject);... implementation... constructor TShape.Create(AOwner: TComponent); inherited Create(AOwner); Width := 65; Height := 65; FPen := TPen.Create; FPen.OnChange := StyleChanged; //OnChange 이벤트에메소드대입 FBrush := TBrush.Create; FBrush.OnChange := StyleChanged; //OnChange 이벤트에메소드대입 procedure TShape.StyleChanged(Sender: TObject); Invalidate(); // 컴포넌트를다시그린다.
그래픽컴포넌트의제작 순수한그래픽컨트롤은포커스를가질수없기때문에, 윈도우핸들을필요로하지않는다. 사용자들은컨트롤을마우스로조작할수는있지만, 키보드인터페이스는가질수없다. 여기에서는 Additional 페이지에있는 TShape 와거의동일한 TSampleShape 라는컴포넌트를만들어볼것이다. 컴포넌트시작하기 먼저컴포넌트를 TGraphicControl 에서상속받도록하고, 이름을 TSampleShape 라고한 다. 이컴포넌트는 Samples 페이지에등록하도록한다. 이렇게설정하면다음과같은뼈 대코드가만들어질것이다. unit Shapes; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TSampleShape = class(tgraphiccontrol) private protected public published procedure Register; implementation procedure Register; RegisterComponents('Samples', [TSampleShape]);
end. 먼저, TGraphicControl 에서 protected 섹션에정의된여러프로퍼티를 published 섹션에사용하도록선언한다. 많은프로퍼티들은이미 TGraphicControl 클래스의 published 섹션에선언되어있으므로, 마우스이벤트와드래그-드롭을지원하는이벤트만 publish 하면된다. published property DragCursor; property DragMode; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnMouseDown; property OnMouseMove; property OnMouseUp; 그래픽기능의추가 1. Shape 프로퍼티의추가 그래픽컨트롤은사용자입력을포함한동적조건을반영하여형태를바꿀수있다. 일반적으로그래픽컨트롤의형태는이들의조합이라고할수있다. 예를들어, TGauge 컨트롤의경우형태와방향을결정하고, 숫자와그림을보여줄것인지여부등을프로퍼티에서결정할수있다. TSampleShape 컨트롤에도어떤것을그릴것인지여부를결정할수있도록 Shape 라는프로퍼티를제공하도록한다. 이를위해먼저, 프로퍼티로사용할열거형 (enumerated type) 을정의하도록하자. TSampleShapeType = (sstrectangle, sstsquare, sstroundrect, sstroundsquare, sstellipse, sstcircle); 이제다음과같이프로퍼티를선언한다.
private FShape: TSampleShapeType; procedure SetShape(Value: TSampleShapeType); published property Shape: TSampleShapeType read FShape write SetShape; 그리고, SetShape 메소드를다음과같이구현한다. procedure TSampleShape.SetShape(Value: TSampleShapeType); if FShape <> Value then FShape := Value; Invalidate; 2. 디폴트프로퍼티의변경 그래픽컨트롤의디폴트크기는매우작다. 그러므로, 이를수정하려면다음과같이프로 퍼티를선언할때 default 값을변경하고 constructor 에서수정할필요가있다. public constructor Create(AOwner: TComponent); override published property Height default 65; property Width default 65; constructor TSampleShape.Create(AOwner: TComponent); inherited Create(AOwner);
Width := 65; Height := 65; 3. 펜과브러쉬의 publish 펜과브러쉬를변경할수있게하려면, 내부적으로사용할객체를 private 섹션에필드로 선언해야한다. private FPen: TPen; FBrush: TBrush;... 이들을프로퍼티로접근할수있게하기위해 Set 메소드와프로퍼티를 published 섹션에 추가한다. private procedure SetBrush(Value: TBrush); procedure SetPen(Value: TPen); published property Brush: TBrush read FBrush write SetBrush; property Pen: TPen read FPen write SetPen; 그리고, SetBrush 와 SetPen 메소드를다음과같이구현한다. procedure TSampleShape.SetBrush(Value: TBrush); FBrush.Assign(Value); procedure TSampleShape.SetPen(Value: TPen); FPen.Assign(Value);
또한, 이들을 constructor 에서생성해야런타임에서사용할수있다. 또한, destructor 에 서는이객채들을해제해야한다. public destructor Destroy; override; constructor TSampleShape.Create(AOwner: TComponent); inherited Create(AOwner); Width := 65; Height := 65; FPen := TPen.Create; FBrush := TBrush.Create; destructor TSampleShape.Destroy; FPen.Free; FBrush.Free; inherited Destroy; 마지막으로펜과브러쉬가변경되었을때, 그려진도형을다시그릴필요가있다. 펜과브러쉬객체는모두 OnChange 이벤트를가지고있으므로, 이들이벤트를설정해서사용하면된다. published procedure StyleChanged(Sender: TObject);... constructor TSampleShape.Create(AOwner: TComponent);
inherited Create(AOwner); Width := 65; Height := 65; FPen := TPen.Create; FPen.OnChange := StyleChanged; FBrush := TBrush.Create; FBrush.OnChange := StyleChanged; procedure TSampleShape.StyleChanged(Sender: TObject); Invalidate; 컴포넌트이미지그리기 마지막으로스크린에그리는부분을구현할차례이다. 그리는부분은 TGraphicControl 클 래스의추상메소드인 Paint 를오버라이드해야한다. 여기에서선택된펜과브러쉬를가지 고선택된도형을그린다. protected procedure Paint; override;... Paint 메소드는도형의형태에따라좌표를사용하는방법을달리해서구현해야한다. Paint 메소드는다음과같이구현하도록한다. procedure TSampleShape.Paint; var X, Y, W, H, S: Integer; with Canvas do Pen := FPen;
Brush := FBrush; W := Width; H := Height; if W < H then S := W else S := H; case FShape of sstrectangle, sstroundrect, sstellipse: X := 0; Y := 0; sstsquare, sstroundsquare, sstcircle: X := (W - S) div 2; Y := (H - S) div 2; W := S; H := S; case FShape of sstrectangle, sstsquare: Rectangle(X, Y, X + W, Y + H); sstroundrect, sstroundsquare: RoundRect(X, Y, X + W, Y + H, S div 4, S div 4); sstcircle, sstellipse: Ellipse(X, Y, X + W, Y + H); 객체결합 (Object Composition) 객체결합이란여러개의컴포넌트를하나로묶어서새로운컴포넌트를만드는것을의미한다. 이테크닉을이용하면각각의요소가되는컴포넌트의이벤트와프로퍼티에사용자가함부로접근하지못하도록보호할수있다. 비주얼컴포넌트의결합에는컨테이너컴포넌트 (TPanel, TCustomPanel, TWinControl) 등이요구되며, 이들은서브-컴포넌트의부모윈도우가된다.
이테크닉은복잡한컴포넌트를그룹화하거나컴포넌트간의유기적인관계가필요한경우에유용하게사용될수있다. 다음의예제컴포넌트는 OK, Cancel 의 2 개의버튼을가지고있으며, 각각의컴포넌트에대한 OnClick, Caption 프로퍼티를제공한다. 참고로, 각각의버튼이 Caption 프로퍼티를가지므로앞에서설명한프로퍼티숨기기를이용하여 OKCancel 버튼자체의 Caption 프로퍼티는없애버린다 (FDummyProperty 를이용 ). unit OKCancel; interface uses Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms, Dialogs, Menus, Buttons; type TOKCancelButton = class(twincontrol) // 여기에서브-컴포넌트들을선언한다. OKButton: TBitBtn; CancelButton: TBitBtn; private FOnClick_OKButton: TNotifyEvent; FOnClick_CancelButton: TNotifyEvent; FDummyProperty: string; procedure SetCaption_OKButton(Value: TCaption); function GetCaption_OKButton: TCaption; procedure SetCaption_CancelButton(Value: TCaption); function GetCaption_CancelButton: TCaption; protected procedure Click_OKButton(Sender: TObject); virtual; procedure Click_CancelButton(Sender: TObject); virtual; public constructor Create(AOwner: TComponent); override; published property Caption_OKButton: TCaption read GetCaption_OKButton write SetCaption_OKButton; property Caption_CancelButton: TCaption read GetCaption_CancelButton
write SetCaption_CancelButton; property OnClick_OKButton: TNotifyEvent read FOnClick_OKButton write FOnClick_OKButton; property OnClick_CancelButton: TNotifyEvent read FOnClick_CancelButton write FOnClick_CancelButton; property Caption: string read FDummyProperty; procedure Register; 참고로, 여기까지선언부를작성하고 Ctrl+Shift+C 키를누르면다음과같은구현부분에대한뼈대코드가모두자동으로만들어진다. 이기능이바로클래스완료 (Class Completion) 이다. 컴포넌트를제작하는사람들에게는허드렛일을많이줄여줄수있는기능이다. 어쨌든구현부분은다음과같이구현한다. implementation procedure TOKCancelButton.SetCaption_OKButton(Value: TCaption); OKButton.Caption := Value; function TOKCancelButton.GetCaption_OKButton: TCaption; Result := OKButton.Caption; procedure TOKCancelButton.SetCaption_CancelButton(Value: TCaption); CancelButton.Caption := Value; function TOKCancelButton.GetCaption_CancelButton: TCaption; Result := CancelButton.Caption;
procedure TOKCancelButton.Click_OKButton(Sender: TObject); if Assigned(FOnClick_OKButton) then FOnClick_OKButton(Self); procedure TOKCancelButton.Click_CancelButton(Sender: TObject); if Assigned(FOnClick_CancelButton) then FOnClick_CancelButton(Self); constructor TOKCancelButton.Create(AOwner: TComponent); inherited Create(AOwner); Width := 75; Height := 60; OKButton := TBitBtn.Create(Self); with OKButton do Parent := Self; Kind := bkok; SetBounds(0, 0, 75, 30); TabOrder := 0; OnClick := Click_OKButton; CancelButton := TBitBtn.Create(Self); with CancelButton do Parent := Self; Kind := bkcancel; SetBounds(0, 31, 75, 30); TabOrder := 1; OnClick := Click_CancelButton; FDummyProperty := ;
procedure Register; RegisterComponents( Samples, [TOKCancelButton]); end. 이와같이컴포넌트를제작할때, 내부에서제어할수있는컴포넌트를활용하면훨씬강력하고편리한활용이가능하다. 이밖에도 private 섹션에 FComponetName 과같은형태로내부에서생성하여관리할컴포넌트를선언해서, 필드변수로활용하고프로퍼티를선언해서사용하는방법이있다. 패키지제작과컴포넌트추가 비록기본적인예제컴포넌트이긴했지만, 이번장에서제작한컴포넌트는모두 6 개이다. 이들을하나의패키지로묶어보도록하자. 먼저, File New 메뉴를선택한후객체저장소에서 Package 를더블클릭하여새로운패키지를하나생성한다. 그리고, File Save As 명령을선택하여적당한디렉토리에.DPK 파일을저장한다. 여기서는 chap22.dpk 라는이름으로저장하도록한다. 패키지에디터화면에서 Add 버튼을클릭하고다음과같이앞에서제작한 6 개의컴포넌트를차례대로추가한다. 패키지파일을저장하고, Install 버튼을누르면패키지가설치될것이다. 컴파일이끝나면
컴포넌트팔레트의 Samples 페이지에는 6 개의컴포넌트가추가될것이다. 그리고, 이명령에의해.BPL 파일이생성되는데이파일을따로배포할수도있다. 어떤가? 컴포넌트를만들고, 이들을자신의패키지로엮어서배포하는일이생각보다너무나쉽다고생각될것이다. 델파이 4 에서는이와같이컴포넌트를개발하고, 이를관리하는강력한수단을제공하고있다. 정리 (Summary) 이번장에서는가장기본적인컴포넌트를만드는방법과이들을이용하여패키지로묶는방법에대해서알아보았다. 컴포넌트는델파이의핵심이라고말할수있는부분이다. 다음장에서는기본적인컴포넌트에기능을추가하는방법과데이터인식컨트롤을제작하는방법과같이보다고급스러운컴포넌트제작기법에대해서알아볼것이다.