컬렉션 사용하기
2005.01.22 20:18
http://tong.nate.com/deljjang/331984
컬렉션 사용하기
델파이 디벨로퍼라는 잡지의 샘플 기사를 번역한 것 입니다.
번역은 제겐 쥐약... 그러려니~ 하구 봐 주시기 바랍니다.
원문 위치.
http://www.borland.com/delphi/news/delphi_developer/usecollections.html
from 델파이 코리아.
http://www.delphikorea.com
-------------------------------------------------------------------------
컬렉션 사용하기. -by Ir. Slos Johnny
컴포넌트를 제작할 때 가끔 TstatusBar 컴포넌트의 Panels 프로퍼티처럼 한
개 이상의 값을 저장해야 할 경우가 생긴다. 이런 기능을 구현하기 위해서는
컬렉션을 사용해야 하는데 이 컬렉션은 컴포넌트 제작의 바이블이라고 불리는
Ray Konopka 의 Developing Custom Delphi Components 에서 조차 제대로
설명되어 있지 않다. 하지만 델파이의컬렉션은 아주 유용하며 쓰기 편하니 꼭
익혀두도록 하자.
컬렉션을 이해하는 가장 쉬운 방법은 간단한 예제를 만들며 단계적으로
따라가는 것이다. 여기서는 TCustomLabel 에서 상속받은 TLWMultiLabel
컴포넌트를 만들어 보도록 하겠다. 이 컴포넌트는 캡션 인덱스라는 프로퍼티에
의해 여러 개의 캡션을 표시하는 기능을 가지며 다국어를 지원하는 경우 등에
유용하게 쓰일 것이다. 물론 여기서 캡션들은 컬렉션을 사용해 저장된다.
컬렉션 프로퍼티를 갖는 컴포넌트를 만들 때는 세개의 새로운 클래스를
작성해야 하는데, (Listing 1, 2, 3 참조) 첫번째는 실제로 만들 컴포넌트의
클래스이고 (이 경우 TLWMultiLabel 클래스) 두번째는 TCollection 으로부터
상속받은 클래스이다. (TCCaption). 그리고 마지막으로 TCollectionItem 에서
상속받은 클래스가 필요하다. (TCICaption).
Listing 1. 멀티 라벨 컴포넌트.
TLWMultiLabel = class(TCustomLabel)
private
// Caption의 인덱스를 저장하는 필드.
FCaptionIndex : integer;
// TCollection을 저장하는 필드
FCaptions : TCCaption;
procedure SetCaptionIndex(V : integer);
procedure SetCaptions(V : TCCaption);
public
constructor Create(AOwner : TComponent); override;
// 컴포넌트 내의 내용이 바뀔 때 호출되는 프로시저.
procedure UpdateCaption;
published
// 어떤 캡션이 보여질지를 나타내는 프로퍼티
property CaptionIndex : integer
read FCaptionIndex write SetCaptionIndex;
// Caption 들이 담겨진 TCollection
property Captions : TCCaption
read FCaptions write SetCaptions;
end;
Listing 2. TCollection 에서 상속받은 TCCaption 클래스.
TCCaption = class(TCollection)
private
// 컬렉션의 Owner를 저장하는 필드
FLWMultiLabel : TLWMultiLabel;
function GetCICaption(index : integer) : TCICaption;
procedure SetCICaption(index : integer; V : TCICaption);
protected
// 컬렉션의 Owner를 리턴하는 함수
function GetOwner : TPersistent; override;
procedure Update(Item: TCollectionItem); override;
public
// TCollectionItem 을 추가하기 위한 메소드
function Add : TCICaption;
property Items[index : integer] : TCICaption
read GetCICaption write SetCICaption;
constructor create(AOwner : TLWMultiLabel);
end;
Listing 3. TCollectionItem에서 상속받은 TCICaption 클래스.
TCICaption = class(TCollectionItem)
private
FText : string;
procedure SetText(V : string);
public
constructor Create(Collection : TCollection); override;
published
property Text : string read FText write SetText;
end;
TLWMultiLabel 컴포넌트의 생성자를 살펴보자. Listing 4 는 부모의
Create메소드 상속 후TCCaption 을 저장할 FCaptions 필드를 생성하는 것을
보여주고 있다. 여기서 필드 클래스를 생성할 때 인자로는 TLWMultiLabel
클래스 자신을 사용한다.
Listing 4. TLWMultiLabel의 생성자.
constructor TLWMultiLabel.Create(AOwner : TComponent);
begin
inherited;
FCaptions := TCCaption.create(self);
if csDesigning in componentstate
then
begin
FCaptions.Add;
FCaptions.Items[0].Text := Name;
FCaptionIndex := 0;
end;
end;
Listing 5 는 TCCaption 클래스의 생성자에서 컬렉션 아이템(여기서는
TCICaption)을 인수로 하여 부모 클래스의 생성자를 상속하고, 내부 변수인
FLWMultiLabel이 이 컬렉션의 owner가되는 모체 컴포넌트를 가리키도록 하는
것을 보여주고 있다.
Listing 5. 컬렉션의 생성자.
constructor TCCaption.create(AOwner : TLWMultiLabel);
begin
inherited create(TCICaption);
FLWMultiLabel := AOwner;
end;
GetOwner 메소드는 이 컬렉션의 owner를 리턴한다. (Listing 6 참조).
Listing 6. 컬렉션의 GetOwner 메소드.
function TCCaption.GetOwner : TPersistent;
begin
result := FLWMultiLabel;
end;
실제 화면에 표시될 캡션값을 저장하는 곳은 TCICaption 클래스의 Text
프로퍼티이다. 사용자가 이 프로퍼티를 조절하면 이 클래스의 SetText
프로시저가 호출될 것이다. (Listing 7 참조)
Listing 7. SetText 프로시저.
procedure TCICaption.SetText(V : string);
begin
if V <> FText
then
begin
FText := V;
Changed(false);
end;
end;
마지막에 호출한 Changed()는 이 컬렉션 아이템의 owner 인 TCollection
클래스의 Update() 메소드를 건드리게 된다. (Listing 8 참조)
Listing 8. The Update method.
procedure TCCaption.Update(Item: TCollectionItem);
begin
FLWMultiLabel.UpdateCaption;
end;
사실, 좀 더 유용한 컴포넌트를 만들기 위해서는 아이템들의 값이 유효한지를
검사하는 코드가 추가적으로 필요하다. 이것은 Assigned(Item) 값을
조사함으로써 알 수 있고 함께 포함시킨 소스 중 TLWStatusBarPro 컴포넌트를
살펴보면 이 방법을 사용하고 있다. 강좌 끝에서 이컴포넌트에 대한 추가적인
설명을 하겠다.
아무튼, Update() 메소드는 다시 TLWMultiLabel.UpdateCaption 메소드를
호출하고, 여기서 실제 화면에 표시되는 라벨 자체를 갱신한다. (Listing 9
참조).
Listing 9. The UpdateCaption method.
procedure TLWMultiLabel.UpdateCaption;
begin
Caption := FCaptions.Items[CaptionIndex].Text;
end;
TLWMultiLabel 컴포넌트는 TCCpation 인스턴스의 Items 프로퍼티를 통해
TCICaption 인스턴스의 Text 프로퍼티에 접근하고 있다. 일단 이것으로
컴포넌트에 컬렉션 기능을추가해 보았다. 추가적으로 해야 할 일은 Caption
프로퍼티를 폼의 리소스(*.dfm)에 저장하는 기능이다. 이제 DefineProperties
메소드를 정의해 Caption 이 폼 리소스에 저장되야 한다고 알려주도록 하자.
(Listing 10, 11, 12 참조).
Listing 10. DefineProperties 메소드.
procedure TLWMultiLabel.DefineProperties(Filer : TFiler);
begin
inherited;
Filer.DefineProperty(
'Captions', ReadCaptions, WriteCaptions, Filer.Ancestor <> nil
);
end;
Listing 11. WriteCaptions 메소드.
procedureTLWMultiLabel.WriteCaptions(Writer: TWriter);
begin
Writer.WriteCollection(Captions);
end;
Listing 12. ReadCaptions 메소드.
procedure TLWMultiLabel.ReadCaptions(Reader: TReader);
begin
Captions.Clear;
Reader.ReadValue;
Reader.ReadCollection(Captions);
end;
지금까지 살펴본 컴포넌트에 컬렉션 기능을 추가하기 위한 단계를
정리하면 다음과 같다.
1. TCollection 클래스의 프로퍼티를 정의한다.
2. TCollection 클래스를 정의한다.
3. TCollectionItem 클래스를 정의한다.
4.TCollectionItem 각 프로퍼티의 읽기 루틴에서 Changed() 를 호출하도록
한다. 이렇게 함으로써 TCollectionItem의 Owner인 TCollection의 Update
메소드가 동작한다.
5. TCollection 의 Update 메소드를 오버라이트 해서 컴포넌트의 Updatexxx
메소드를 호출한다.
6. 컴포넌트에 Updatexxx 메소드를 정의하고 TCollectionItem 내용이 바뀔 때
해야할 일들을 여기서 해준다.
위에서 잠깐 언급했던 TLWStatusBarPro 컴포넌트는 아기자기한 기능이 추가된
상태바 컴포넌트이다. TLWStatusBarPro 컴포넌트는 각 판넬을 시계, 날짜,
데이터베이스 이름, 사용자 이름등의 속성으로 설정할 수 있고 관련된 그림도
표시한다. 이 컴포넌트를 디자인시에 더블클릭하면 컬렉션 에디터가 나타나는
것을 볼 수 있는데, 이를 위해 Listing 13 에서 보듯이TLWStatusBarProEditor
라는 컴포넌트 에디터 클래스를 정의하였다.
Listing 13. TLWStatusBarPro의 컴포넌트 에디터 클래스.
TLWStatusBarProEditor = class(TDefaultEditor)
protected
procedure EditProperty(PropertyEditor: TPropertyEditor;
var Continue, FreeEditor: Boolean); override;
end;
procedure TLWStatusBarProEditor.EditProperty(PropertyEditor:
TPropertyEditor; var Continue, FreeEditor: Boolean);
begin
if CompareText( propertyEditor.GetName, 'PANELS') = 0 then
begin
propertyEditor.Edit;
continue := false;
end
else
inherited;
end;
작성한 컴포넌트 에디터를 등록하기 위해서는 다음과 같이 해 준다.
RegisterComponentEditor(TLWStatusBarPro, TLWStatusBarProEditor);
컬렉션은 좋은 컴포넌트를 만들려는 프로그래머라면 꼭 챙겨야 할 공구같은
것이다. 모쪼록 이기사가 여러분의 프로젝트에서 컬렉션의 유용함을 깨닫는
계기가 되길 바란다.
예제 파일의 위치 :
http://www.borland.com/delphi/news/delphi_developer/slos.zip
또는 델파이 코리아의 강좌란.
* 원문이 아닌 역문의 경우 역자가 저작권을 갖습니다.
* 이 강좌의 출처와 내역을 밝힐 경우... 어디에든 게재하실 수 있습니다.
// 아래는 델마당의 강좌입니다.
작성자 : 치즈
TCollection 을 사용한 콤포넌트 작성
아주 간단한 강좌입니다. ^^
THeaderControl 에 보면 Headers 나
TStatusBar 에 보면 Panels 가 있는데 오브젝트 인스펙터 상에서
Headers 나 Panels 프로퍼티를 더블클릭하면 프로퍼티 에디터가 열리는데
TCollection 에 대한 공통된 프로퍼티 에디터입니다.
거기에서 TCollectionItem 을 하나씩 추가, 삭제 할 수 있고
각각의 아이템에 대한 프로퍼티를 볼 수 있는데
그런 콤포넌트를 작성할 때 TCollection 을 씁니다.
TCollectionItem 은 TCollection 에 리스트상으로 존재하는 아이템으로서
예를 들면,
StatusBar 에서
StatusBar 는 Panels 라는 TCollection 을 프로퍼티로 갖는 콤포넌트이고
Panels[0], Panels[1] 처럼 접근가능한 것은 TCollectionItem 이 됩니다.
따라서 다음과 같은 세개의 객체가 존재합니다.
TStatusBar(TCustomControl), TPanels (TCollection), Panel(TCollectionItem)
이제 이런 류의 콤포넌트를 만드는 방법은 아래에 소스를 보면 알수 있을 겁니다.
(그냥 TStrings 로 처리해 버려도 좋을 Items 속성에 대해 TCollection 을 쓰면
더욱 코딩하기 쉬운 콤포넌트가 됩니다.)
소스에서 간단하게 세개의 클래스를 사용합니다.
TCollectionCmp, TTestCollection, TTestCollectionItem
설치하면 samples 에 등록되며 동작을 확인할 수 있습니다.
-----------------------------------------------------------------------------
unit TestCollection;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Graphics;
type
TTestCollection = class;
TCollectionCmp = class(TCustomControl)
private
FItems: TTestCollection;
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Items: TTestCollection read FItems write FItems;
end;
TTestCollection = class(TCollection)
private
{ Private declarations }
FCollectionOwner: TPersistent;
FTestVal: integer;
protected
{ Protected declarations }
function GetOwner: TPersistent; override;
property CollectionOwner: TPersistent read FCollectionOwner write FCollectionOwner;
public
{ Public declarations }
published
{ Published declarations }
property TestVal: integer read FTestVal write FTestVal;
end;
TTestCollectionItem = class(TCollectionItem)
private
FMyCaption: String;
protected
public
published
property MyCaption: String read FMyCaption write FMyCaption;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TCollectionCmp]);
end;
{ TCollectionCmp }
constructor TCollectionCmp.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FItems := TTestCollection.Create(TTestCollectionItem);
FItems.CollectionOwner := Self;
end;
destructor TCollectionCmp.Destroy;
begin
FItems.Clear;
FItems.Free;
inherited Destroy;
end;
procedure TCollectionCmp.Paint;
begin
Canvas.Brush.Color := clYellow;
Canvas.Rectangle(0, 0, Width-1, Height-1);
Canvas.TextOut(10, 10, ClassName);
end;
{ TTestCollection }
function TTestCollection.GetOwner: TPersistent;
begin
result := FCollectionOwner;
end;
end.