LazarusCompleteGuide:4.5
패키지
라자루스 패키지는 유닛과 컴포넌트의 집합이다. 기능을 필요로 하는 다른 프로젝트 또는 패키지의 기반이 되거나, 라자루스 IDE에 새 메뉴 옵션을 추가하는 패키지처럼 플러그인이 되기도 한다.
현재 프리 파스칼 컴파일러는 동적으로 연결된 패키지는 지원하지 않고 정적으로 연결된 패키지만 지원한다. 다시 말하자면, 라자루스 패키지는 프로젝트의 실행 파일로 연결되어야 한다는 말이다. (동적 패키지라면 델파이에서와 같이 패키지를 구분된 라이브러리로 저장하는 것이 가능할 것이다.)
컴파일러를 이렇게 제한할 때 불편한 부작용은, IDE에 기능의 추가 여부나 컴포넌트 팔레트에 새 컴포넌트의 추가 여부와 상관없이 새 패키지가 설치될 때마다 패키지가 재컴파일되어야 한다는 점이다.
패키지를 많이 선컴파일할수록 더 빠르게 진행된다.
이러한 이유로, 라자루스 디렉터리에서 make clean을 실행하는 것은 그다지 좋은 생각이 아니다.
프리 파스칼 개발자들은 동적 패키지를 작업하고 있지만 현재로선 안정적 크로스 플랫폼의 해결책은 없다.
패키지는 다른 패키지를 필요로 할지도 모르지만 어찌되었건 프로젝트는 패키지를 필요로 한다.
프로젝트와 패키지 간 의존성은 패키지 에디터 또는 프로젝트 인스펙터의 Required Packages 섹션에서 추가된다. 버전을 명시할 수도 있다. 최저 및 최고 호환 버전 설정도 제공된다. 버전 정보를 생략하는 것도 가능한데, 이런 경우 명시된 패키지에 대한 어떤 버전이든 수락될 것이다.
패키지 에디터는 패키지의 컴파일과 "install"을 위한 Compile 버튼을 포함한다. 이번 사례에서 "install" 이란, 그 후에 패키지가 IDE에 알려지고 재컴파일될 것을 의미한다 (이전에 설치된 패키지들과 함께). 개발 과정에 필요한 패키지만 Use... 버튼을 이용해 IDE로 설치되어야 한다 (일부 패키지에선 Install 버튼이 되겠다). 이렇게 설치 가능한 패키지들은 다음을 포함한다: 컴포넌트 팔레트용 컴포넌트; 메뉴 확장; 대화창, 그리고 IDE에 통합될 필요가 있는 다른 옵션들.
패키지의 목적이 IDE의 일부가 되는 데에 그친다면 설치하지 않는다. IDE에 설치하더라도 프로젝트로 접근성이 부여되는 것은 아니므로 소용이 없다. 그러한 패키지는 프로젝트 인스펙터에서 의존성으로 추가되어야 한다. 이를 위해선 프로젝트 인스펙터의 트리 구조에서 Required Packages 노드를 클릭하여 플러스 기호가 있는 버튼을 누른다. 필요한 최저 및 최고 버전을 표시하는 대화창이 뜨면 프로젝트의 새 패키지 의존성을 추가할 수 있다.
사용자는 Package ⇒ New Package 또는 File ⇒ New ⇒ Package 선택 메뉴를 이용해 새 패키지를 생성한다. 메뉴를 선택하면 패키지에 새 파일을 추가하도록 패키지 에디터(Package Editor)가 열린다. 라자루스 패키지는 주로 파스칼 소스 코드를 포함하지만 이미지나 리소스와 같은 어떤 종류의 파일도 추가가 가능하다. 패키지 시스템은 이러한 파일을 구성하도록 도와줄 것이다.
모든 라자루스 패키지는 .lpk 확장자로 된 파일과 연관된다. 이는 패키지에서 가장 중요한 파일이다. 패키지에 대한 패키지 에디터를 열려면 lpk 파일을 라자루스로 로딩하면 된다. 모든 패키지에는 이름과 버전이 있다. 패키지 이름은 .lpk 파일 이름과 (확장자 제외) 동일해야 한다.
IDE는 메인 소스 파일 PackageName.pas 를 자동으로 생성하여 프로젝트에 모든 파스칼 소스 파일에 대해 uses 절을 추가하고 IDE에서 유닛을 등록하기 위한 소스 코드도 추가한다.
이러한 소스 파일은 절대로 수동으로 편집해선 안 되는데, 패키지 에디터에서 파일을 선택함으로써 필요한 모든 구성은 이미 설정되어 있기 때문이다. Package Unit 확인상자를 클릭하면 uses 절을 추가하고 유닛을 등록한다. 이는 IDE와 메인 패키지 파일의 uses 절에 등록시킨다.
패키지 내 메인 유닛에 대해 예제를 들어보겠다:
//This file was automatically created by Lazarus. Do not edit!
//This source is only used to compile and install
//the package GTKOpenGL 1.0.
unit GTKOpenGL;
interface
uses GTKGLArea, GTKGLArea_Int, NVGL, NVGLX, LazarusPackageIntf;
implementation
procedure Register;
begin
RegisterUnit('GTKGLArea', @GTKGLArea.Register);
end;
initialization
RegisterPackage('GTKOpenGL', @Register)
end.
lpk 파일을 포함하는 디렉터리를 package directory 라고 부르는데, 모든 경로 명세(path specification)는 이 디렉터리와 관련되어야 한다. 즉, 패키지 구조는 파일 시스템 내 어느 위치든 속할 수 있음을 의미한다. 절대 경로를 명시하는 것도 이론상으론 가능하지만, 패키지 시스템의 철학에 위배되므로 금해야 한다.
패키지 에디터에선 패키지에 대해 두 가지 타입의 검색 경로가 가능하다:
- 컴파일러 옵션 대화창 내 경로 (패키지 컴파일에 사용)
- Options 대화창의 Usage 페이지(탭) 상의 경로, 즉 패키지를 사용하길 원하는 프로젝트와 패키지에서 필요로 하는 경로.
일반적으로 Compiler Options 경로는 sources 패키지가 위치한 디렉터리를 가리키는 반면, Usage 경로는 compiled units 가 저장된 디렉터리를 가리킨다. 보통은 사용자가 경로에 대해 걱정하거나 수동으로 변경할 필요가 없다. 패키지 경로에 있지 않은 경로가 패키지로 추가될 경우 IDE는 이를 알아채고 경로를 컴파일러 설정 경로로 추가할 것인지 묻는다. 모든 컴파일 결과는 바이너리를 위해 동일한 위치에 저장되므로, 소스가 어디에 위치하든 그 위치를 변경할 필요는 없다.
사전에 설정된(preset) 출력 디렉터리는 패키지 디렉터리에 lib/$(TargetCPU)-$(TargetOS)/ 로 설정되어 있으며, 변경되어선 안 된다.
개발환경에 열린 패키지 리스트는 packagelinks.xml 파일에서 관리된다. 해당 파일은 IDE의 구성 디렉터리에 위치하며, 모든 설치와 삭제 시마다 업데이트된다. 라자루스가 삭제되더라도 이 파일은 제거되지 않을 것이다. 기존 설정은 유지되므로 다시 설치하더라도 이를 유념해야 한다. 이를 방지하고 싶다면 사용자의 라자루스 로컬 구성 디렉터리를 삭제할 필요가 있다.
IDE가 패키지를 찾을 수 없다면 IDE에서 lpk 파일을 로딩한다. 이를 실행하려면 파일을 열어 IDE에게 파일을 알리기만 하면 된다. 이름 기반의 시스템에선 패키지 이름이 유일해지는 결과가 발생한다. 동일한 이름으로 된 두 개의 패키지를 같은 IDE에 설치할 수 없다. 따라서 이름은 가능한 한 명확하고 일관되게 선택하는 것이 중요하다. FCL, LCL, SynEdit 는 IDE의 일부를 형성하고 자동으로 시작되는 특수 기본(special base) 패키지이다. 이 패키지들은 읽기만 가능하며, .lpk 파일을 가지지 않는다. 따라서 제거 또는 변경이 불가하다.
라자루스는 컴파일하는 패키지마다 패키지 빌드에 사용되는 옵션, 그리고 옵션이 변경 시 재빌드 되어야 하는지 여부를 보관하는 PackageName.compiled 이름의 상태 파일(state file)을 저장한다. 패키지에 대한 .compiled 파일의 예제를 아래 들어보겠다:
<?xml version="1.0"?>
<CONFIG>
<Compiler Value="/usr/bin/ppc386" Date="781388725"/>
<Params Value=" -Rintel -S2cgi -CD -Ch8000000 -OG1p1
-Tlinux -gl -vewnhi -l -Fu../../../lcl/units
-Fu../../../lcl/units/gtk -Fu../../../packager/units
-Fu. -FElib/ gtkopengl.pas"/>
</CONFIG>
IDE는 패키지를 컴파일 하기 전에 모든 의존성을 확인할 것이다. 그리고 필요 시, 또는 구성이 허용하는 한 모든 의존성을 재빌드할 것이다. 그리고 나면 uses 절에 패키지 유닛과 함께 메인 패키지 소스 파일과 등록 코드가 작성될 것이다. 이것이 완료되어야만 프리 파스칼이 패키지를 컴파일한다. 컴파일된 유닛은 출력 디렉터리에 쓰이고, 컴파일 중 시행되는 옵션이 있는 PackageName.compiled 라는 이름의 상태 파일이 작성될 것이다. 이러한 옵션이 변경되는 상황에서만 패키지를 재생성하면 될 것이다.
컴포넌트 추가하기
패키지는 IDE에서 컴포넌트 팔레트로 새 컴포넌트를 추가하는 데에 사용된다. 이를 위해선 컴포넌트를 추가하고자 하는 (또는 새 패키지를 생성하기 위해) 패키지를 열어야 한다. 패키지 에디터로 가서 [+] 버튼을 누른다. Add to package 대화창이 뜨면 New Component 탭을 선택한다. 그리고 해당하는 이름으로 된 확인상자에서 Ancestor Type (클래스)를 선택하여 새 컴포넌트 New class name 을 입력하고, 새 컴포넌트에 대한 Palette Page 를 선택한다.
위는 새 컴포넌트에 대한 패키지에 새 파일을 추가하고, 패키지를 설치하면 새 컴포넌트가 IDE로 추가될 것이다.
컴포넌트 등록하기
컴포넌트 팔레트에 컴포넌트를 표시하려면 우선 등록을 해야 한다. 컴포넌트는 이를 위한 등록 프로시저를 필요로 한다 (델파이와 같이). 사용자는 일반적으로 컴포넌트 소스 코드와 동일한 유닛 파일에 등록 프로시저를 위치시킨다. 프로시저는 유닛의 interface 부에서 선언되어야 하고, Register 라는 이름을 가져야 하며, 그 구현은 아래와 같은 선언을 가진 RegisterComponents 프로시저를 호출해야 한다:
procedure RegisterComponents(const Page: String; ComponentClasses: array of TComponentClass);"
이러한 선언은 프리 파스칼 파일 cregist.inc에서 찾을 수 있으며, classesh.inc 에 프로시저 포인트로서 포함된다. 해당 파일은 프리 파스칼 표준 유닛인 classes 에 의해 추가된다. 유닛 내 모든 컴포넌트를 등록하려면 이와 같은 등록 프로시저를 이용해야 한다. 새 컴포넌트에 대한 새 컴포넌트 에디터 또는 프로퍼티 에디터를 등록할 때도 마찬가지다. 오브젝트 인스펙터의 행들 중 하나의 값을 편집하려면 프로퍼티 에디터를 이용하고, 컴포넌트 에디터를 이용하면 폼 디자이너에서 컴포넌트를 조작할 수 있다. 이를 위한 프로시저는 RegisterPropertyEditor 와 RegisterComponentEditor 이다 (프리 파스칼의 부분은 아니지만 라자루스에 속한다).
RegisterComponentEditor 는 ComponentEditors.pas 에서 정의된다:
procedure RegisterComponentEditor(ComponentClass: TComponentClass;
ComponentEditor: TComponentEditorClass);
function GetComponentEditor(Component: TComponent;
const Designer: TComponentEditorDesigner): TBaseComponentEditor;
RegisterPropertyEditor는 propedits.pp 유닛에서 찾을 수 있다:
procedure RegisterPropertyEditor(PropertyType: PtypeInfo;
PersistentClass: TClass; const PropertyName: ShortString;
EditorClass: TPropertyEditorClass);
컴포넌트를 등록하려면 사용자는 그 유닛의 코드를 아래와 같이 조정한다. 먼저, 등록 프로시저가 interface 에서 선언된다:
procedure Register;
그리고 나서 Register 프로시저의 implementation 부에서 실제 작업이 이루어진다:
procedure Register;
begin
RegisterComponents('OpenGL', [TOpenGLControl]);
RegisterComponentEditor(TOpenGLControl, TOpenGLControlEditor);
end;
컴포넌트는 패키지로 추가되어 IIDE에서 설치 가능할 때에만 IDE에 표시된다. 'register' 프로시저를 포함하는 유닛은 패키지 내에 "register unit"으로 표시되어야 하는데, 이 작업은 패키지 에디터에서 파일을 선택하여 'register unit' 확인상자를 설정함으로써 실행할 수 있다.
프로퍼티 에디터
프로퍼티 에디터는 오브젝트 인스펙터에서 컴포넌트 프로퍼티를 편집할 수 있는 커스텀 대화창을 제공한다. 표준 프로퍼티에는 (문자열, 문자열 리스트, 열거형) 라자루스에서 이미 제공하는 기존 에디터가 있지만, 새 컴포넌트가 특수 프로퍼티를 가진 경우 그것을 구성하기 위한 커스텀 대화창이 필요할 것이다. 모든 프로퍼티 에디터는 TPropertyEditor (또는 그 자손들 중 하나)를 기반으로 한 클래스로서, 기반 클래스의 특정 메소드를 구현한다. 모든 프로퍼티 에디터는 PropEdits 유닛으로부터 RegisterPropertyEditor 함수를 이용해 IDE에서 등록되어야 한다. 명명 규칙에 따라 프로퍼티 에디터의 이름은 프로퍼티의 클래스 이름 다음에 PropertyEditor 를 붙여야 한다. 예를 들어, TField 프로퍼티에 대한 프로퍼티 에디터는 TFieldPropertyEditor 로 명명된다.
TPropertyEditor = class
public
function AllEqual: Boolean; Virtual;
function AutoFill: Boolean; Virtual;
procedure Edit; Virtual; // Activated by double-clicking the
// property value
procedure ShowValue; Virtual; // Activated by control-clicking the
// property value
function GetAttributes: TPropertyAttributes; Virtual;
function GetEditLimit: Integer; Virtual;
function GetName: ShortString; Virtual;
procedure GetProperties(Proc: TGetPropEditProc); Virtual;
function GetHint(HintType: TPropEditHint; x, y:integer): String; Virtual;
function GetDefaultValue: AnsiString; Virtual;
procedure GetValues(Proc: TGetStrProc); Virtual;
procedure SetValue(const NewValue: AnsiString); Virtual;
procedure UpdateSubProperties; Virtual;
function SubPropertiesNeedsUpdate: Boolean; Virtual;
function IsDefaultValue: Boolean; Virtual;
function IsNotDefaultValue: Boolean; Virtual;
// ... shortened
end;
TPropertyEditor 에는 수많은 메소드가 있는데, 가장 중요한 메소드를 표 4.8에 소개하겠다.
프로퍼티 | 설명 |
GetValue | 오브젝트 인스펙터에 프로퍼티의 현재값을 표시하는 문자열을 얻기 위해 실행된다. |
Edit | 생략부호 [...] 버튼을 누르거나 프로퍼티를 더블 클릭하면 호출된다. 주로 대화창을 불러온다. |
GetAttributes | 프로퍼티 표시 방식에 관한 오브젝트 인스펙터를 위한 정보. 상세한 정보는 아래 TPropertyAttributes 를 다룬 표 4.9를 참고한다. |
GetValues | GetAttributes 에 paValueList 가 리턴될 때 호출된다. 프로퍼티 다운드롭 리스트에 값 리스트를 채운다. 예를 들어, 열거형에 대한 표준 TEnumPropertyEditor 는 모든 요소를 열거형으로 전달할 것이다. |
GetEditLimit | 오브젝트 인스펙터에 입력이 허용되는 최대 문자수를 리턴한다. 기본 제한 값은 255자이다. |
GetProperties | 확장 가능 프로퍼티의 서브프로퍼티를 리턴한다. 기본 값으로는 어떤 프로퍼티도 추정되지 않지만, 클래스의 모든 published 프로퍼티를 서브프로퍼티로 전달하는 TClassPropertyEditor 의 자손들과 집합의 모든 요소들을 서브프로퍼티로 전달하는 TSetPropertyEditor 의 자손들은 제외한다. |
표 4.6: TPropertyEditor의 가장 중요한 메소드 |
TPropertyAttributes 는 새 프로퍼티 에디터를 쓰는 데 이해해야 할 중요한 클래스이다. 그 메인 필드는 아래와 같다.
프로퍼티 | 설명 |
paValueList | 프로퍼티를 열거된 리스트와 같이 편집할 수 있다. 리스트 내 이름은 GetValues 메소드가 리턴한 값이다. 이러한 경우, 생략 부호 […] 에디트 버튼이 없어지고 값의 전체 리스트를 표시하는 드롭다운 화살표가 표시될 것이다. |
paSortList | GetValues 가 리턴한 리스트는 사용 전 정렬되어야 한다. |
paPickList | paValueList 와 함께 사용되며, 사용자가 임의의 텍스트를 삽입할 수는 없지만 드롭다운 리스트의 옵션들 중 하나를 선택해야 함을 의미한다. |
paSubProperties | 클래스와 레코드 타입에 사용되어 항목 내 프로퍼티의 변경을 위해 항목을 확장가능하게 만든다. GetProperties 메소드는 속성의 리스트를 표시되도록 리턴할 것이다. |
paDynamicSubProps | UpdateListPropertyEditors 로의 호출을 통해 다시 읽어야 한다. |
paDialog | 에디터 메소드가 대화창을 가져올 것을 의미한다. 오브젝트 인스펙터에서 프로퍼티 우측에 생략부호 […] 버튼이 표시될 것이다. |
paMultiSelect | 하나 이상의 컴포넌트가 선택될 때 프로퍼티가 표시되도록 허용한다. 일부 프로퍼티는 다중 선택에 적합하지 않다 (예: Name 프로퍼티). |
paAutoUpdate | [Enter]를 누른 후가 아니라 에디터에 내용이 변경될 때마다 SetValue 메소드가 호출된다. 사용자 타입에 따라 시각적 텍스트가 연속적으로 업데이트되는 Caption과 같은 프로퍼티에 사용된다. |
paReadOnly | 오브젝트 인스펙터의 사용을 통해 또는 리스트의 콤보상자 항목 선택을 통한 값 변경이 허용되지 않는다. 하지만 paDialog 대화창이 활성화되고 대화창이 이를 허용하면 값이 변경될 수 있다. |
paRevertable | 프로퍼티가 본래 값으로 리셋되는 것을 허용한다. |
paFullWidthName | 오브젝트 인스펙터에 값을 표시할 필요가 없음을 알리고 오브젝트 인스펙터의 전체 너비를 이용해 이름을 렌더링(rendered)해야 함을 알린다. |
paVolatileSubProperties | 프로퍼티를 변경하려면 오브젝트 인스펙터가 subproperty 값의 전체 리스트를 재로딩해야 한다. |
paDisableSubProperties | 모든 서브프로퍼티는 읽기만 가능하다(read-only). |
paReference | paSubProperties 와 함께 사용하여 프로퍼티 값에서 참조되는 오브젝트의 프로퍼티들이 서브프로퍼티에 표시되도록 한다. 다른 컨트롤을 참조하는 프로퍼티에서 사용된다. 예를 들어, TDataSource 의 경우, DataSet 프로퍼티가 유효 DataSet 으로 설정되고 그 서브프로퍼티는 선택된 DataSet 에서 비롯되었다면 DataSet 프로퍼티는 확장 가능하다. |
paNotNestable | 프로퍼티가 자신을 참조하는 다른 프로퍼티를 참조하므로 확장된 서브프로퍼티 리스트에 표시할 수 없으며, 중첩된 프로퍼티의 무한 루프가 생성될 수 있음을 의미한다. 예제: 클래스 A가 클래스 B의 프로퍼티를 가지고, 클래스 B가 클래스 A의 프로퍼티를 가진 경우. |
표 4.7: TPropertyAttibutes의 필드 |
Tfont 는 사용 중인 프로퍼티 에디터에 대한 훌륭한 예제를 제공한다.
TClassPropertyEditor 의 자손이라는 것만으로 기본 기능을 이용하기에 충분하다. TFont 예제를 계속하자면, TClassPropertyEditor 로부터 상속은 이미 원하는 행위의 일부를 제공하므로, TFontPropertyEditor 클래스는 에디트 메소드에 대화창을 표시하고 GetAttributes 에 에디터의 속성을 설정하도록 구현하기만 하면 된다:
TFontPropertyEditor = class(TClassPropertyEditor)
public
procedure Edit; Override;
function GetAttributes: TPropertyAttributes; Override;
end;
procedure TFontPropertyEditor.Edit;
var FontDialog: TFontDialog;
begin
FontDialog := TFontDialog.Create(NIL);
try
FontDialog.Font := TFont(GetObjectValue(TFont));
FontDialog.Options := FontDialog.Options + [fdShowHelp,
fdForceFontExist];
if FontDialog.Execute then SetPtrValue(FontDialog.Font);
finally
FontDialog.Free;
end;
end;
function TFontPropertyEditor.GetAttributes: TPropertyAttributes;
begin
Result := [paMultiSelect, paSubProperties, paDialog, paReadOnly];
end
위의 프로퍼티 에디터는 운영체제 폰트를 처리한다.
프로퍼티 에디터는 특히 컴포넌트 내 class properties 를 변경할 때 유용하다 (위 그림의 TFont 클래스와 같이). 클래스는 다수의 서로 다른 포맷으로 된 필드를 가지는 경향이 있다. 이렇게 복잡한 프로퍼티들은 문자열이나 수치값 편집에서와 달리 일반적 입력 필드에서는 변경할 수 없다.
클래스 프로퍼티 에디터를 이용할 수 있도록 오브젝트 인스펙터는 괄호 안에 클래스 이름을 영구적으로 표시하고, 클래스의 프로퍼티를 편집할 수 있는 대화창을 여는 생략 부호 […] 버튼을 제공한다. 이러한 디스플레이 행위와 그에 동반되는 버튼은 (대화창 자체는 제외) TClassPropertyEditor 라는 이름의 표준 프로퍼티 에디터에 의해 구현되는데, 여기서 사용자는 자신만의 클래스를 위한 특정 에디터를 얻을 수 있다.
TClassPropertyEditor = class(TPropertyEditor)
public
consturctor Create(Hook: TPropertyEditorHook; APropCount: Integer);
Override;
function GetAttributes: TPropertyAttributes; Override;
procedure GetProperties(Proc: TGetPropEditProc); Override;
function GetValue: AnsiString; Override;
property SubPropsTypeFilter: TTypeKinds read FSubPropsTypeFilter
write SetSubPropsTypeFilter
default tkAny;
end;
컴포넌트 에디터
컴포넌트 에디터는 폼 디자이너(Form Designer)에서 오른쪽 마우스 클릭과 더블 클릭 프로세싱을 처리한다. 각 컴포넌트 에디터는 하나의 클래스로서, TBaseComponentEditor (주로 그의 자손 TComponentEditor 를 통해)에서 파생되며, 사용자의 자손(descendant)은 기반 클래스의 메소드를 구현해야 한다. 컴포넌트 에디터는 RegisterComponentEditor 함수를 이용해 (ComponentEditor 유닛에 위치) Register 프로시저에 등록되어야 한다. 컴포넌트 에디터를 명명하는 규칙은 앞에 'ComponentEditor'를 붙이고 에디터를 만든 목적이 되는 클래스 이름을 붙인다. 따라서 TStringGrid 컴포넌트에 대한 컴포넌트 에디터는 TStringGridComponentEditor 가 되겠다.
TBaseComponentEditor = class
protected
public
constructor Create(AComponent: TComponent;
ADesigner: TComponentEditorDesigner); Virtual;
procedure Edit; Virtual; Abstract;
procedure ExecuteVerb(Index: Integer); Virtual; Abstract;
function GetVerb(Index: Integer): String; Virtual; Abstract;
function GetVerbCount: Integer; Virtual; Abstract;
procedure PrepareItem(Index: Integer; const AnItem: TMenuItem);
Virtual; Abstract;
procedure Copy; Virtual; Abstract;
function IsInInlined: Boolean; Virtual; Abstract;
function GetComponent: TComponent; Virtual; Abstract;
function GetDesigner: TComponentEditorDesigner; Virtual; Abstract;
function GetHook(out Hook: TPropertyEditorHook): Boolean;
Virtual; Abstract;
procedure Modified; Virtual; Abstract;
end;
컴포넌트 에디터에서 가장 중요한 메소드를 항상 Edit라고 부른다. 이는 폼 에디터에서 컴포넌트를 더블 클릭하면 호출된다. 오른쪽 마우스 클릭으로 컴포넌트의 컨텍스트 메뉴를 호출하면 GetVerbCount 와 GetVerb 메소드를 이용해 메뉴가 빌드된다. 'verbs' 중 하나를 클릭하면 (이번 사례에서 'verbs'란 메뉴 엔트리를 칭함) ExecuteVerb 메소드가 활성화된다.
TDefaultEditor 라는 이름으로 사전에 정의된 컴포넌트 에디터가 있는데, 이는 Edit 를 구현하여 컴포넌트 프로퍼티를 최선으로 편집하는 방법을 검색한다. 메소드는 이를 위한 이벤트를 선택해 소스 코드창에 준비된 소스 코드 섹션을 추가하고 커서를 위치시킴으로써 사용자가 코드를 완성할 수 있도록 대기한다.
또 다른 중요한 TBaseComponentEditor 메소드는 ExecuteVerb(Index) 로, 팝업 메뉴를 오른쪽 마우스로 클릭할 때 키보드 단축키 또는 핫키(hotkey)와 같은 추가 메뉴 엔트리를 실행한다. GetVerb 는 그러한 추가 팝업 메뉴 엔트리 모두에 이름을 부여한다.
컴포넌트 에디터는 메뉴 엔트리를 모두 올바르게 모으는 작업을 책임진다. 특히 '&'의 단축키와 메뉴 구분자(menu separator)가 올바르게 할당되도록 처리하는데, Caption 이 마이너스 부호 '-'인 특수 엔트리를 예로 들 수 있겠다. GetVerbCount 는 팝업 메뉴에 추가할 엔트리의 수를 제공한다.
GetVerb 와 ExecuteVerb 루틴의 색인(index)은 0을 시작점(zero-based)으로 하기 때문에 0부터 GetVerbCount-1까지 계수한다. PrepareItem 은 메뉴 엔트리를 생성한 후 verbs마다 호출된다. 이 단계에서 메뉴의 추가 변경이 가능한데, 예를 들자면, 하위메뉴 또는 확인상자를 삽입할 수 있겠다. Visible 프로퍼티를 False 로 설정함으로써 엔트리를 숨길 수도 있겠다.
마지막으로 주목할 가치가 있는 메소드는 Copy 인데, 이는 사용자가 컴포넌트를 임시 저장 장치(temporary storage)에 복사할 때 호출한다. 라자루스 내부에서 사용되는 컴포넌트 데이터는 항상 추가는 가능하나 변경은 불가하다. Copy 메소드는 라자루스 고유의 컴포넌트 삽입 기능에 영향을 미치지 않고 컴포넌트를 클립보드를 통해 다른 애플리케이션으로 전달하기 위해 설계되었다.
TCheckListBox 는 단순하지만 흥미로운 컴포넌트 에디터를 가지는데, 이는 그 내용을 편집하는 대화창을 생성한다. 처음부터 메소드를 모두 구현하기보다는 TBaseComponentEditor 로부터 상속하는 편이 훨씬 편리하다. 이것이 TCheckListBoxEditor 가 하는 일이다. 그 기반 클래스에는 이미 대부분 메소드를 위한 빈(empty) 구현부와 더 많은 메소드를 위해 사전에 입력된(pre-filled) 구현부가 어느 정도 존재한다. 실제 편집은 ExecuteVerb(0) 을 호출하여 이루어지므로, 첫 번째 메뉴 엔트리는 더블 클릭과 동일한 액션을 실행한다. 이것은 정상적인 컴포넌트 에디터 행위로, Edit 를 따로 구현할 필요가 없다.
더블 클릭과 첫 팝업 메뉴 엔트리의 기본 액션은 주로 대화창의 표시이며, TCheckListBox 도 예외가 아니다:
TCheckListBoxComponentEditor = class(TComponentEditor)
protected
procedure DoShowEditor;
public
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;
procedure TCheckListBoxComponentEditor.DoShowEditor;
var Dlg: TCheckListBoxEditorDlg;
begin
Dlg := TCheckListBoxEditorDlg.Create(nil);
try
if GetComponent is TCheckListBox then
begin
Dlg.aCheck := TCheckListBox(GetComponent);
if not HasHook then exit;
AssignCheckList(Dlg.FCheck, Dlg.aCheck);
//ShowEditor
if Dlg.ShowModal=mrOK then
begin
AssignCheckList(Dlg.aCheck, Dlg.FCheck);
Modified;
end;
if Dlg.Modified then Modified;
end;
finally Dlg.Free;
end;
end;
procedure TCheckListBoxComponentEditor.ExecuteVerb(Index: Integer);
begin
DoShowEditor;
end;
function TCheckListBoxComponentEditor.GetVerb(Index: Integer): string;
begin
Result:=clbCheckListBoxEditor+'...';
end;
function TCheckListBoxComponentEditor.GetVerbCount: Integer;
begin
Result:=1;
end;