저작권에 대한 공지
이 문서에 포함된 모든 글과 소스 코드는 작자인 저 김성동의 지적 재산이지만 무단으로 배포하거나 책이나 강의 교재등을 위한 복제 등 저작권을 저해하는 행위를 제외하고는 자유롭게 사용할 수 있습니다.
또한 이 페이지에 대한 링크는 자유롭게 할 수 있으나 전문을 전제하는 일은 허용하지 않습니다.


3.3. 프로퍼티 에디터(Property Editor)

3.3.1. 개요
델파이는 자신의 IDE를 사용자가 확장할 수 있는 기능을 제공해 준다. 이런 기능을 총칭해서 Open Tools API라고 하는데 델파이 소스 디렉토리의 ToolsApi 디렉토리에 보면 Open Tools API를 사용하기 위해 필요한 여러 개의 유닛 파일을 볼 수 있다. 줄여서 OTA라고 얘기하는 Open Tools API는 개발자로 하여금 델파이의 오브젝트 인스펙터, 마법사, IDE의 메뉴 시스템 심지어 코드 에디터의 기능까지 확장할 수 있도록 해 준다. OTA를 사용하는 대표적인 툴이 ToolsFactory사의 ClassExplorer나 Eagle Software 사의 CodeRush, 공개로 유명한 GExperts등이 있다. 이번 절과 다음 절에서는 콤포넌트 개발자에게 꼭 필요한 OTA 기능 중에 하나인 프로퍼티 에디터와 콤포넌트 에디터에 대해서 알아 보겠다.
프로퍼티 에디터는 우리가 폼을 디자인할 때 사용하는 오브젝트 인스펙터에서 폼이나 콤포넌트의 프로퍼티를 설정할 때 사용하는 델파이 IDE의 확장 기능을 말한다. 예를 들어 Color 프로퍼티를 살펴 보면 우리가 오브젝트 인스펙터에서 Color 프로퍼티를 설정하기 위해 드롭 다운 버튼을 누르면 드롭 다운 리스트 박스가 나타나고 설정할 수 있는 색을 색의 이름과 함께 리스트로 보여 준다. 이 리스트에서 설정하고 싶은 색을 선택하면 Color 프로퍼티에는 우리가 선택한 색이 설정된다. 여기서 드롭 다운 리스트를 보여 주고 원하는 색을 선택할 수 있게 해주는 것 이것이 바로 프로퍼티 에디터이다. 우리가 오브젝트 인스펙터에서 각종 프로퍼티를 설정할 때 무의식적으로 사용하고 있을 뿐 Color, Cursor, Font등 기본적인 프로퍼티에 대해서는 여러 가지 프로퍼티 에디터들이 이미 만들어져 있다. 그러나 이미 만들어져 있는 기본 프로퍼티 에디터들이 사용자의 모든 요구 사항을 만족시켜 주지는 못한다. 한가지 예를 들자면 Hint 프로퍼티 같은 경우에 기본적으로 String 프로퍼티 에디터가 동작한다. 하지만 이 String 프로퍼티 에디터는 여러 줄을 입력할 수가 없다. HintWindow는 여러 줄을 입력해도 알맞게 윈도 크기를 설정해서 보여 줄 수 있지만 프로퍼티 에디터에서 여러 줄을 입력할 수가 없으니 불편할 수 밖에 없다. 이런 경우 나만의 프로퍼티 에디터를 만들어서 여러 줄로 Hint를 편하게 입력할 수 있으면 좋을 것이다.
프로퍼티 에디터를 만들려면 uses 절에 dsgnintf 유닛을 추가해 주어야 한다. 이 유닛에는 델파이가 기본적으로 가지고 있는 프로퍼티 에디터들이 선언되어 있다. 어떤 프로퍼티 에디터들이 있는지 살펴보자.
3.3.2. 기본 프로퍼티 에디터
앞에서도 얘기했지만 델파이는 대부분의 데이터 타입에 대해 기본적인 프로퍼티 에디터를 가지고 있으며 이들은 dsgnintf.pas 유닛에 정의되어 있다. 모든 프로퍼티 에디터의 부모 클래스는 TPropertyEditor로서 이 또한 dsgnintf.pas 유닛에 정의되어 있다.
아래 리스트는 dsgnintf.pas에서 TPropertyEditor 클래스의 선언부만 발췌한것이다.

TPropertyEditor = class
private
  FDesigner: IFormDesigner;
  FPropList: PInstPropList;
  FPropCount: Integer;
  function GetPrivateDirectory: string;
  procedure SetPropEntry(Index: Integer; AInstance: TPersistent;
    APropInfo: PPropInfo);
protected
  constructor Create(const ADesigner: IFormDesigner; APropCount: Integer); virtual;
  function GetPropInfo: PPropInfo;
  function GetFloatValue: Extended;
  function GetFloatValueAt(Index: Integer): Extended;
  function GetInt64Value: Int64;
  function GetInt64ValueAt(Index: Integer): Int64;
  function GetMethodValue: TMethod;
  function GetMethodValueAt(Index: Integer): TMethod;
  function GetOrdValue: Longint;
  function GetOrdValueAt(Index: Integer): Longint;
  function GetStrValue: string;
  function GetStrValueAt(Index: Integer): string;
  function GetVarValue: Variant;
  function GetVarValueAt(Index: Integer): Variant;
  procedure Modified; 
  procedure SetFloatValue(Value: Extended);
  procedure SetMethodValue(const Value: TMethod);
  procedure SetInt64Value(Value: Int64);
  procedure SetOrdValue(Value: Longint);
  procedure SetStrValue(const Value: string);
  procedure SetVarValue(const Value: Variant);
public
  destructor Destroy; override;
  procedure Activate; virtual;
  function AllEqual: Boolean; virtual;
  function AutoFill: Boolean; virtual;
  procedure Edit; virtual;
  function GetAttributes: TPropertyAttributes; virtual;
  function GetComponent(Index: Integer): TPersistent;
  function GetEditLimit: Integer; virtual;
  function GetName: string; virtual;
  procedure GetProperties(Proc: TGetPropEditProc); virtual;
  function GetPropType: PTypeInfo;
  function GetValue: string; virtual;
  function GetVisualValue: string;
  procedure GetValues(Proc: TGetStrProc); virtual;
  procedure Initialize; virtual;
  procedure Revert;
  procedure SetValue(const Value: string); virtual;
  function ValueAvailable: Boolean;
  procedure ListMeasureWidth(const Value: string; ACanvas: TCanvas;
    var AWidth: Integer); dynamic;
  procedure ListMeasureHeight(const Value: string; ACanvas: TCanvas;
    var AHeight: Integer); dynamic;
  procedure ListDrawValue(const Value: string; ACanvas: TCanvas;
    const ARect: TRect; ASelected: Boolean); dynamic;
  procedure PropDrawName(ACanvas: TCanvas; const ARect: TRect;
    ASelected: Boolean); dynamic;
  procedure PropDrawValue(ACanvas: TCanvas; const ARect: TRect;
    ASelected: Boolean); dynamic;
  property Designer: IFormDesigner read FDesigner;
  property PrivateDirectory: string read GetPrivateDirectory;
  property PropCount: Integer read FPropCount;
  property Value: string read GetValue write SetValue;
end;


프로퍼티 에디터는 오브젝트 인스펙터에서 선택된 하나 또는 그 이상의 콤포넌트의 프로퍼티를 조작한다. 오브젝트 인스펙터는 콤포넌트가 선택되면 그 콤포넌트의 Published 영역에 선언된 프로퍼티 리스트를 구하고 각 프로퍼티의 데이터 타입에 따라 등록된 프로퍼티 에디터를 생성한다. TPropertyEditor의 GetName과 GetValue 함수는 오브젝트 인스펙터가 프로퍼티의 이름과 현재 값을 읽을 때 호출되며 SetValue는 사용자가 프로퍼티의 값을 변경할 때 호출된다. Edit 함수는 오브젝트 인스펙터에서 프로퍼티를 더블 클릭하면 호출되며 GetValues 메소드는 열거형 프로퍼티에 대해 드롭 다운 리스트를 보여 줄 때 호출된다. AllEqual 메소드는 하나 이상의 콤포넌트가 선택되었을 때 같은 프로퍼티 값을 보여 줄지를 결정할 때 호출된다.
아래 표는 델파이에서 기본적으로 제공하는 프로퍼티 에디터의 계층 구조와 간단한 설명을 담고 있다.

표 3-2 델파이에서 기본적으로 제공하는 프로퍼티 에디터
프로퍼티 에디터설명
TPropertyEditor모든 프로퍼티 에디터의 부모 클래스
TordinalProperty서수형 프로퍼티 에디터의 부모 클래스로서 AllEqual 메소드를 오버라이드 해서 선택된 콤포넌트가 여러 개일 경우 같은 프로퍼티 값을 보여 줄지를 결정해 주어야 한다.
TCharPropertyChar 나 'A'..'Z' 등과 같은 문자형 프로퍼티의 기본 에디터
TEnumProperty열거형 프로퍼티의 기본 에디터로서 GetValues 메소드를 오버라이드 해서 드롭 다운 리스트에 나타날 항목을 제공해주어야 한다.
TBoolPropertyBoolean 형 프로퍼티의 기본 에디터
TBrushStylePropertyTBrush 객체의 Style 프로퍼티 에디터
TPenStylePropertyTPen 객체의 Style 프로퍼티 에디터
TIntegerPropertyLongInt, Integer, Word 등의 정수형 프로퍼티에 대한 기본 에디터
TColorPropertyTColor 형 프로퍼티의 기본 에디터
TCursorPropertyTCursor 형 프로퍼티의 기본 에디터
TFontCharsetPropertyTFont 객체의 CharSet 프로퍼티에 대한 기본 에디터
TModalResultPropertyTModalResult 형 프로퍼티의 기본 에디터
TTabOrderPropertyTabOrder 프로퍼티의 기본 에디터
TSetproperty집합형 프로퍼티에 대한 기본 에디터로서 집합의 각 요소를 Boolean 형으로 설정할 수 있도록 해 준다.
TShortCutPropertyShortCut 프로퍼티에 대한 기본 에디터로서 직접 단축키를 입력하거나 리스트에서 선택할 수 있다.
TStringProperty문자열 프로퍼티에 대한 기본 에디터
TCaptionpropertyCaption이나 Text 프로퍼티에 대한 기본 에디터
TComponentNamePropertyName 프로퍼티에 대한 기본 에디터
TFontNamePropertyTFont 객체의 FontName 프로퍼티에 대한 기본 에디터로서 윈도 시스템에 설치된 글꼴 리스트를 보여주고 선택할 수 있게 해준다.
TImeNamePropertyTImeName 형 프로퍼티에 대한 기본 에디터
TMPFilenamePropertyTMediaPlayer 객체의 FileName 프로퍼티를 조작하기 위한 에디터로서 Media 파일을 선택할 수 있는 대화 상자를 보야 준다.
TClassProperty모든 객체에 대한 기본 에디터로서 프로퍼티 값을 직접 조작할 수는 없지만 클래스의 이름을 보여 주며 부 프로퍼티로서 객체가 가지고 있는 프로퍼티를 조작할 수 있게 해 준다.
TFontPropertyTFont 형 프로퍼티에 대한 기본 에디터로서 글골 선택 대호상자를 보여 주거나 TFont 객체의 프로퍼티를 부 프로퍼티로 조작할 수 있게 해 준다.
TNestedProperty부모의 Designer, PropList, PropCount를 사용하는 프로퍼티 에디터로서 TSetElementProperty 같은 에디터가 사용한다.
TSetElementProperty집합형 프로퍼티의 각 요소를 나타내는 부 프로퍼티에 대한 에디터로서 GetName 메소드는 프로퍼티의 이름대신 요소의 이름을 반환하고 Get/SetValue 메소드는 각 요소의 상태를 조작하며 이 에디터는 TSetProperty 프로퍼티 에디터에 의해서 생성된다.
TComponentPropertyTComponent 형 프로퍼티에 대한 기본 에디터로서 콤포넌트의 프로퍼티를 편집하는 것이 아니고 다른 콤포넌트를 가리키도록 설정할 수 있다. 즉 다른 콤포넌트의 이름을 직접 입력하거나 형이 호환되는 콤포넌트 리스트를 보여 주고 이 리스트에서 선택할 수 있도록 해 준다.
TFloatPropertyFloat, Single, Double 등의 실수형 프로퍼티에 대한 기본 에디터
TInt64PropertyInt64 형 프로퍼티에 대한 기본 에디터
TMethodProperty모든 메소드 프로퍼티에 대한 기본 에디터로서 주로 이벤트 프로퍼티에 사용된다.
TDatePropertyTDateTime의 날자 부분을 편집하기 위한 에디터
TTimePropertyTDateTime의 시간 부분을 편집하기 위한 에디터
TDateTimePropertyTDateTime 형 프로퍼티에 대해 날자와 시간을 같이 조작하기 위한 에디터


위의 표에서 언급한 프로퍼티 에디터 이외에도 델파이 소스 디렉토리의 Property Editors 디렉토리에 보면 기본 에디터들을 확장한 다양한 에디터(TStringListProperty, TFieldLinkProperty, TPictureProperty, TGraphicProperty 등)들이 정의되어 있으니 참조하기 바란다.
3.3.3. 새 프로퍼티 에디터 만들기
새 프로퍼티 에디터를 만드는 것은 TPropertyEditor 클래스나 기본 프로퍼티 에디터를 부모 클래스로 새로운 파생 클래스를 만드는 작업이다. 따라서 새로 만들 프로퍼티 에디터가 조작하게 될 프로퍼티의 형에 따라 TPropertyEditor 클래스에서 가상으로 선언된 메소드를 적절하게 오버라이드 해주면 된다.
먼저 새로운 프로퍼티 에디터를 만들기 위해서 알아야 할 TPropertyEditor 클래스에서 제공하는 메소드와 프로퍼티에 대해서 알아 보자.

표 3-3 TPropertyEditor의 메소드
메소드설명
Activate오브젝트 인스펙터에서 프로퍼티가 선택되면 호출된다.
AllEqual여러 개의 콤포넌트가 동시에 선택될 때마다 호출된다. 이 함수가 참을 반환하면 GetValue 메소드가 호출되고 그렇지 않으면 프로퍼티 값은 비어 있게 된다. 이 메소드는 GetAttributes 메소드가 paMultiSelect 플래그를 반환해야지만 호출된다.
AutoFillGetAttributes 메소드의 반환 값에 paValueList 플래그가 있을 경우에만 호출되는데 GetValues 메소드를 통해서 구해진 리스트에서 오브젝트 ㅇ니스펙터가 자동으로 다음 값을 선택할 지를 결정할 때 호출된다.
Edit오브젝트 인스펙터에서 프로퍼티가 더블 클릭되거나 '...' 버튼이 눌러지면 호출된다. Font 프로퍼티처럼 프로퍼티를 편집할 때 대화 상자를 사용하려면 이 메소드를 오버라이드해야 한다.
GetAttributes프로퍼티 에디터의 성격을 나타내는 집합(TPropertyAttributes)을 반환하며 사용 가능한 플래그는 아래에 별도의 표로 나타내었다.
GetComponent현재 편집 중인 콤포넌트를 반환한다. 하나의 콤포넌트를 편집중이면 인자로 0을 입력한다.
GetEditLimit오브젝트 인스펙터의 입력 박스에서 허용하는 문자열의 길이를 설정하며 기본값은 255이다.
GetName프로퍼티의 이름을 반환한다.
GetPropertiesGetAttributes 메소드의 반환값에 paSubProperties 플래그가 있으면 사용 가능한 부 프로퍼티 리스트를 구하기 위해서 이 메소드가 호출된다.
GerPropType편집 중인 프로퍼티의 형 정보(Type Information)에 대한 포인터를 반환한다.
GetValue프로퍼티의 현재 값을 문자열로 반환한다. 기본값은 '(unknown)' 이다.
GetValuesGetAttributes 메소드의 반환 값에 paValueList 플래그가 있으면 호출되며 이 프로퍼티가 가질 수 있는 각 요소 값에 대해 Proc 프로시저를 호출해 주어야 한다.
Initialize프로퍼티 에디터가 생성된 후 사용되기 전에 호출된다.
SetValue프로퍼티의 현재 값을 설정한다. 프로퍼티 에디터는 이 메소드의 인자로 들어 오는 문자열을 프로퍼티의 데이터 형에 맞게 변환해서 SetXXXValue 메소드를 호출해 주면 된다.
ListMeasureWidth*드롭 다운 리스트를 그리기 위해 리스트의 폭을 얻을 때 호출된다.
ListMeasureHeight*드롭 다운 리스트를 그리기 위해 리스트에서의 각 아이템의 높이를 구할 때 호출된다. TListBox의 MeasureItem과 유사하다.
ListDrawValue*드롭 다운 리스트의 각 아이템을 그릴 때 호출되며 TListBox의 DrawItem과 유사하다.
PropDrawName*오브젝트 인스펙터에서 프로퍼티 이름을 보여주는 컬럼이 그려질 때 호출된다.
PropDrawValue*오브젝트 인스펙터에서 프로퍼티의 값을 보여주는 컬럼이 그려질 때 호출된다.


* : 델파이 5.0에서 새로 생긴 메소드이며 오브젝트 인스펙터에서 프로퍼티를 나타내는 방법을 커스터마이즈할 때 사용한다.

표 3-4 TPropertyEditor의 프로퍼티
프로퍼티설명
Designer폼 디자이너 객체
PrivateDirectory"HKEY_CURRENT_USER\Software\Borland\Delphi\*\Globals\PrivateDir"위의 레지스트리 키에 지정된 작업 디렉토리를 나타내며 만약 프로퍼티 에디터가 임시 파일을 생성하고 저장할 필요가 있으면 이 디렉토리를 이용해야 한다.




표 3-5 TPropertyAttributes 플래그
플래그설명
paValueList이 플래그는 프로퍼티 값을 드롭 다운 리스트로 편집하고 싶을 때 설정한다. 이 플래그가 설정되어 있으면 GetValues 메소드가 호출된다.
paSortList이 플래그는 오브젝트 인스펙터가 GetValues로 구해진 리스트를 정렬하도록 할 때 설정한다.
paSubProperties이 플래그가 설정되어 있으면 프로퍼티 에디터가 부 프로퍼티를 가지고 있음을 나타낸다. 이 플래그가 설정되어 있으면 사용 가능한 부 프로퍼티 리스트를 구하기 위해 GetProperties 메소드가 호출된다.
paDialog프로퍼티를 편집할 때 대화 상자를 사용할 것임을 나타낸다. 이 플래그가 설정되면 오브젝트 인스펙터는 '…' 버튼을 생성하며 이 버튼을 누르면 Edit 메소드가 호출된다.
paMultiSelect여러 개의 콤포넌트가 선택되었을 때 프로퍼티 값을 나타낼 지를 결정한다.
paAutoUpdate프로퍼티 값이 완전히 입력이 되지 않았어도 SetValue 메소드가 호출된다. Caption 프로퍼티처럼 엔터를 치지 않아도 값이 반영되도록 하고 싶을 때 사용한다.
paReadOnly사용자가 프로퍼티 값을 변경하지 못하도록 읽기 전용으로 만든다.
paRevertable이 플래그는 프로퍼티 값이 원래의 값으로 복원될 수 있는지를 나타낸다.


프로퍼티 에디터에서 모든 프로퍼티는 문자열로 표현된다. 오브젝트 인스펙터가 프로퍼티의 실제 데이터 형과 상관없이 프로퍼티의 값을 읽거나 변경할 때 Value 프로퍼티(GetValue/SetValue 메소드)를 참조하기 때문에 새로 만드는 프로퍼티 에디터에서는 GetValue/SetValue 메소드를 오버라이드해서 실제 프로퍼티의 데이터 형에 따라 적절하게 변환해 주어야 한다. 이렇게 변환된 값을 실제로 적용할 때는 protected 멤버인 GetXXXValue, SetXXXValue 메소드를 사용한다. 예를 들어 TIntegerProperty 같은 경우에는 아래와 같이 정의되어 있다.

TIntegerProperty = class(TOrdinalProperty)
  public
    function GetValue: string; override;
    procedure SetValue(const Value: string); override;
  end;

function TIntegerProperty.GetValue: string;
begin
  with GetTypeData(GetPropType)^ do
    if OrdType = otULong then // unsigned
      Result := IntToStr(Cardinal(GetOrdValue))
    else
      Result := IntToStr(GetOrdValue);
end;

procedure TIntegerProperty.SetValue(const Value: String);

  procedure Error(const Args: array of const);
  begin
    raise EPropertyError.CreateResFmt(@SOutOfRange, Args);
  end;

var
  L: Int64;
begin
  L := StrToInt64(Value);
  with GetTypeData(GetPropType)^ do
    if OrdType = otULong then
    begin   // unsigned compare and reporting needed
      if (L < Cardinal(MinValue)) or (L > Cardinal(MaxValue)) then
        // bump up to Int64 to get past the %d in the format string
        Error([Int64(Cardinal(MinValue)), Int64(Cardinal(MaxValue))]);
    end
    else if (L < MinValue) or (L > MaxValue) then
      Error([MinValue, MaxValue]);
  SetOrdValue(L);
end;


TIntegerProperty의 경우 TPropertyEditor의 GetValue, SetValue 메소드를 오버라이드해 주고 GetValue 메소드는 GetOrdValue 메소드를 호출해서 실제 값을 얻어 온 다음 IntToStr 함수로 문자열로 변환해서 반환하고 있고 SetValue 메소드는 StrToInt64 함수로 문자열을 정수로 변환한 다음 SetOrdValue 메소드로 프로퍼티 값을 변경하고 있다는 것을 알 수 있다.

TPropertyEditor 클래스의 Edit 메소드는 오브젝트 인스펙터의 '…' 버튼이나 프로퍼티 편집 창을 더블 클릭하면 호출되며 주로 대화상자를 이용해서 프로퍼티 값을 편집할 때 사용한다. 대화 상자를 사용하면 텍스트로 편집하는 것보다 좀 더 쉽고 시각적으로 프로퍼티 값을 편집할 수 있을 것이다. 대화상자를 이용하는 프로퍼티의 대표적인 예로 Font, Color, TMemo 콤포넌트의 Lines 프로퍼티 등을 들 수 있다. Edit 메소드가 오브젝트 인스펙터에 의해서 호출되도록 하려면 GetAttributes 메소드의 반환값에 paDialog 플래그가 있어야 한다. TFontProperty 에디터는 아래 코드와 같이 대화 상자를 이용해서 프로퍼티 값을 설정하도록 되어 있다.

type
  TFontProperty = class(TClassProperty)
  public
    procedure Edit; override;
    function GetAttributes: TPropertyAttributes; override;
  end;

procedure TFontProperty.Edit;
var
  FontDialog: TFontDialog;
begin
  FontDialog := TFontDialog.Create(Application);
  try
    FontDialog.Font := TFont(GetOrdValue);
    FontDialog.HelpContext := hcDFontEditor;
    FontDialog.Options := FontDialog.Options + [fdShowHelp, fdForceFontExist];
    if FontDialog.Execute then SetOrdValue(Longint(FontDialog.Font));
  finally
    FontDialog.Free;
  end;
end;

function TFontProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paMultiSelect, paSubProperties, paDialog, paReadOnly];
end;


만약에 프로퍼티의 값을 드롭 다운 리스트로 여러 가지 설정 가능한 값 중에서 선택하도록 하고 싶으면 GetValues 메소드를 오버라이드 해야 하며 GetAttributes 메소드를 오버라이드 해서 paValueList 플래그를 반환하도록 해야 한다. paValueList 플래그가 GetAttributes의 반환값에 있으면 오브젝트 인스펙터는 프로퍼티 편집 박스에 작은 화살표 버튼을 만들어 주며 이 버튼을 사용자가 누르면 드롭 다운 리스트가 펼쳐져서 프로퍼티 값을 선택할 수 있게 된다.
GetValues 메소드는 하나의 인자를 가지는데 이 인자는 메소드 포인터이다. 이 메소드 포인터는 실제로 리스트를 가지고 있을 내부 리스트(TStrings)의 Add 메소드를 호출한다. GetValues 가 호출될 때 이 내부 리스트는 비어 있게 되며 GetValues 메소드에서 사용 가능한 값을 추가해 주어야 한다. TFontNameProperty 에디터의 GetValues 메소드를 살펴 보면 이 작업이 어떻게 이루어 지는지 알 수 있을 것이다.

type
  TFontNameProperty = class(TStringProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    procedure GetValues(Proc: TGetStrProc); override;
    { 생략 }
  end;
  
function TFontNameProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paMultiSelect, paValueList, paSortList, paRevertable];
end;

procedure TFontNameProperty.GetValues(Proc: TGetStrProc);
var
  I: Integer;
begin
  for I := 0 to Screen.Fonts.Count - 1 do Proc(Screen.Fonts[I]);
end;


3.3.4. 프로퍼티 에디터 등록
콤포넌트처럼 오브젝트 인스펙터가 새로 만든 프로퍼티 에디터를 사용하도록 하려면 등록을 해 주어야 한다. 프로퍼티 에디터를 델파이 IDE에 등록하기 위해서는 RegisterPropertyEditor 프로시저를 이용하며 dsgnintf.pas 유닛에 아래와 같이 선언되어 있다.

procedure RegisterPropertyEditor(PropertyType: PTypeInfo; ComponentClass: TClass;
  const PropertyName: string; EditorClass: TPropertyEditorClass);


첫번째 인자 PropertyType은 프로퍼티의 데이터 형 정보에 대한 포인터로서 앞에서 얘기한 GetEnumName이나 GetEnumValue 함수처럼 TypeInfo 함수를 이용해서 구할 수 있다.
두번째 인자 ComponentClass는 등록하는 프로퍼티 에디터의 적용 범위를 결정하는 것으로서 특정 콤포넌트 클래스를 입력하면 해당 콤포넌트의 지정 프로퍼티에만 이 프로퍼티 에디터를 사용하도록 한다. 하지만 nil 값을 입력하면 모든 콤포넌트의 PropertType 형을 가지는 모든 프로퍼티에 대해 이 프로퍼티 에디터를 사용하도록 한다. 즉 세번째 인자인 PropertyName을 무시하게 된다. 세번째 인자인 PropertyName은 두번째 인자가 nil이 아닐 경우에 이 프로퍼티 에디터를 사용할 프로퍼티의 이름을 입력해 준다. PropertyName에 빈 문자열('')을 입력하면 ComponentClass에서 PropertyType을 가지는 모든 프로퍼티에 대해 적용된다. 네번째 인자는 등록할 프로퍼티 에디터의 클래스 형을 입력한다.
예를 들어 보자.

RegisterPropertyEditor(TypeInfo(string), nil, '', TdpbHintProperty);


이렇게 등록하면 모든 콤포넌트의 string 형 프로퍼티는 모두 TdpbHintProperty를 사용하도록 등록한다. 하지만 아래와 같이 등록하면

RegisterPropertyEditor(TypeInfo(string), TControl, 'Hint', TdpbHintProperty);


TControl 클래스나 그 자손 클래스들의 string 형 프로퍼티 중 Hint 라는 이름을 가진 프로퍼티에 대해 TdpbHintProperty 에디터를 사용하도록 한다.
프로퍼티 에디터를 등록할 때 주의할 것은 아래와 같이 동시에 등록한 경우에 즉 하나의 프로퍼티에 여러 개의 프로퍼티 에디터가 등록될 경우 오브젝트 인스펙터가 어떤 프로퍼티 에디터를 사용하는가이다.

RegisterPropertyEditor(TypeInfo(string), nil, '', TStringProperty);
RegisterPropertyEditor(TypeInfo(string), TAnimate, 'FileName', TMPFileNameProperty);


오브젝트 인스펙터는 등록된 에디터 중 적용 범위가 가장 제한된 프로퍼티 에디터를 사용하며 적용 범위가 같을 경우에는 제일 마지막에 등록된 프로퍼티 에디터를 사용한다. 즉 위와 같은 경우 첫번째 문장에서 TAnimate의 FileName 프로퍼티는 TStringProperty로 등록되었다가 두번째 문장에서 다시 TMPFileNameProperty로 등록된다.
3.3.5. Hint 프로퍼티 에디터
이번 절에서는 앞에서 배운 내용들을 가지고 모든 콤포넌트의 Hint 프로퍼티를 편집할 때 다중 라인을 입력할 수 있는 Hint 프로퍼티 에디터를 만들어 보자.
대부분 새 프로퍼티 에디터의 부모 클래스는 TStringProperty, TIntegerProperty, TClassProperty, TComponentProperty로 정해진다. Hint 프로퍼티는 string 형이므로 TStringProperty를 부모 클래스로 선정하면 될 것이다. 그리고 TStringProperty는 기본적으로 오브젝트 인스펙터의 편집 창을 바로 사용하므로 Hint 프로퍼티를 여러 줄로 편집하기 위해서 TStringListProperty 에디터처럼 대화 상자를 이용해서 프로퍼티를 편집하도록 하겠다. 먼저 새로운 폼을 하나 만들고 아래와 같이 설계한다.

그림 3-2 Hint 프로퍼티 에디터 폼


그리고 폼의 유닛 파일에 아래와 같이 TdpbHintProperty 클래스를 선언한다.

TdpbHintProperty = class(TStringProperty)
  public
    function GetAttributes: TPropertyAttributes; override;
    procedure Edit; override;
  end;


대화 상자를 사용해야하므로 GetAttributes 메소드와 Edit 메소드를 오버라이드한다.

Procedure Tdpbhintproperty.Edit;
Begin
  With Tfrmhintpropedit.Create(Nil) Do
  Try
    If Propcount > 1 Then
      Caption := 'Hint 


GetAttributes 메소드는 paDialog 플래그를 반환할 수 있도록 해주고 Edit 메소드에서는 편집 폼을 생성하고 오브젝트 인스펙터에서 선택한 콤포넌트의 개수에 따라 폼의 Caption 프로퍼티를 설정한다. 그리고 GetStrValue 함수를 이용해서 구한 프로퍼티의 현재 값을 메모 콤포넌트의 Text 프로퍼티에 설정해 준다. 마지막으로 사용자가 OK 버튼을 누르면 SetStrValue로 프로퍼티 값을 설정한다.
그리고 이 프로퍼티 에디터를 등록하기 위해서 Register 프로시저를 하나 만들고 이 프로시저 내에서 RegisterPropertyEditor를 호출해 준다.

procedure Register;
begin
  RegisterPropertyEditor(TypeInfo(string), TControl, 'Hint', TdpbHintProperty);
end;


이렇게 프로퍼티 에디터 작성을 모두 마치면 새 콤포넌트 패키지나 기존 패키지에 이 파일을 추가하고 다시 컴파일한 후 Hint 프로퍼티를 편집해 보면 아래 화면처럼 새로 작성한 프로퍼티 에디터가 동작할 것이다.

그림 3-3 동작중인 Hint 프로퍼티 에디터




그림 3-4 여러 줄로 편집된 Hint 프로퍼티


3.3.6. 대화 상자를 이용한 About 프로퍼티 에디터
이전 장에서 작성한 TdpbLabel 콤포넌트에 About 프로퍼티를 하나 추가하고 이 프로퍼티를 편집하면 대화 상자가 나타나서 콤포넌트를 만든 사람, 콤포넌트의 버전등 여러가지 정보를 보여주는 프로퍼티 에디터를 하나 만들어 보자.
먼저 TdpbLabel 클래스에 아래 코드처럼 About 프로퍼티를 추가해 준다.

const
  VERSION = '1.0';

type
  { 생략 }
  TdpbLabel = class(TCustomLabel)
  private
    { 생략 }
    procedure SetAbout(const Value : string);
    function  GetAbout : string;
    { 생략 }
  published
    { Published declarations }
    property About : string read GetAbout write SetAbout stored False;
    { 생략 }
  end;

implementation

{ TdpbLabel }
{ 생략 }

function TdpbLabel.GetAbout: string;
begin
  Result := VERSION;
end;

procedure TdpbLabel.SetAbout(const Value: string);
begin
end;
{ 생략 }


About Property는 실행시에 콤포넌트의 동작에 영향을 미치는 프로퍼티가 아니고 설계시에만 정보를 보여 주는 프로퍼티이므로 폼 파일에 프로퍼티 값을 저장하지 않도록 Stored를 False로 해 준다.
다음 콤포넌트 정보를 보여 줄 대화 상자를 아래와 같이 설계한다.

그림 3-5 TdpbLabel의 About 프로퍼티 에디터 폼


그리고 폼의 유닛 파일에 아래와 같이 TdpbAboutProperty 클래스를 선언한다.

TdpbAboutProperty = class(TStringProperty)
  public
    procedure Edit; override;
    function GetAttributes : TPropertyAttributes; override;
    function GetValue : string; override;
  end;


About 프로퍼티 에디터도 대화 상자를 사용해야하므로 GetAttributes 메소드와 Edit 메소드를 오버라이드한다.

function TdpbAboutProperty.GetAttributes : TPropertyAttributes;
begin
  Result := [ paDialog, paReadOnly ];
end;

function TdpbAboutProperty.GetValue : string;
begin
  Result := 'Press ... to display About dialog';
end;

procedure TdpbAboutProperty.Edit;
begin
  with TfrmAboutPropEdit.Create(nil) do
  try
    { 클래스 이름과 버전 정보를 구한다 }
    CompClass := GetComponent(0).ClassName;
    Version := inherited GetValue;
    Caption := CompClass + ' 에 대해...';
    ShowModal;
  finally
    Free;
  end;
end;


GetAttributes 메소드는 paDialog 플래그와 paReadOnly 플래그를 반환할 수 있도록 해주고 Edit 메소드에서는 편집 폼을 생성하고 오브젝트 인스펙터에서 선택한 콤포넌트의 클래스 이름을 폼의 Public 변수인 CompClass에 저장하고 버전 정보를 Version 변수에 저장한다.
이 프로퍼티 에디터를 설치하기 전에는 About 프로퍼티의 값이 단순히 버전 정보만 보여주도록 하고 프로퍼티 에디터가 설치되면 'Press … button to dispaly About dialog'라는 메시지를 보여 줄 수 있도록 하기 위해 GetValue 메소드를 오버라이드 한다.
다음 아래 코드와 같이 폼의 OnPaint 이벤트에서 편집중인 콤포넌트의 팔레트 비트맵을 구해서 이 비트맵과 콤포넌트의 클래스 이름, 버전 정보를 폼의 캔바스에 그려주도록 한다.

procedure TfrmAboutPropEdit.FormPaint(Sender: TObject);
var
  Bmp : TBitmap;
begin
  { 콤포넌트의 팔레트 비트맵을 그린다. }
  Bmp := TBitmap.Create;
  try
    Bmp.LoadFromResourceName(hInstance, UpperCase(CompClass));
    Canvas.Brush.Color := Self.Color;
    Canvas.BrushCopy(Bounds(16, 16, 24, 24), Bmp, 
Bounds(0, 0, 24, 24), Bmp.TransparentColor);
  finally
    Bmp.Free;
  end;

  { 콤포넌트의 클래스 이름과 버전을 그린다. }
  SetBkMode(Canvas.Handle, TRANSPARENT);
  Canvas.Font.Name := 'Times New Roman';
  Canvas.Font.Size := 16;
  Canvas.Font.Style := [fsBold];
  Canvas.Font.Color := clBtnShadow;
  Canvas.TextOut(49, 15, CompClass + ' ' + Version);
  Canvas.Font.Color := clWindow;
  Canvas.TextOut(51, 17, CompClass + ' ' + Version);
  Canvas.Font.Color := clMaroon;
  Canvas.TextOut(50, 16, CompClass + ' ' + Version);
end;


마지막으로 dpbLabel.pas 유닛의 uses 절에 dsgnintf와 AboutPropEdit 유닛을 추가해 주고 Register 프로시저에 아래와 같이 프로퍼티 에디터를 델파이 IDE에 등록하는 루틴을 추가해 준다.

procedure Register;
begin
  RegisterComponents('DPB', [TdpbLabel]);
  RegisterPropertyEditor(TypeInfo(string), TdpbLabel, 'About', TdpbAboutProperty);
end;


아래 그림은 About 프로퍼티 에디터가 동작하는 화면이다.

그림 3-6 오브젝트 인스펙터에서 동작중인 About 프로퍼티 에디터




그림 3-7 동작중인 About 프로퍼티 에디터





저작권에 대한 공지
이 문서에 포함된 모든 글과 소스 코드는 작자인 저 김성동의 지적 재산이지만 무단으로 배포하거나 책이나 강의 교재등을 위한 복제 등 저작권을 저해하는 행위를 제외하고는 자유롭게 사용할 수 있습니다.
또한 이 페이지에 대한 링크는 자유롭게 할 수 있으나 전문을 전제하는 일은 허용하지 않습니다.