[C++Builder] TApLabel 만들기 (2) Object 형 property

볼랜드포럼에 올린글을 블로그에 재게시 합니다.
http://cbuilder.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_tutorial&no=135


Object 형 property 라는 것은 말 그대로 property 가 object 형태로 된 것입니다.
여러가지의 자료형이 혼합되어 있고, 각각의 자료형에 맞는 처리를 해주어야 할 때 , 그리고
이것들이 유사한 것들이어서 통합해서 관리하고 싶을때 object 형으로 property 를 만듭니다.

object 형 property 를 사용하는 쪽에서는 생성자에서 new 로 생성하고 파괴자에서 delete 로
파괴해야 합니다.

TFont3D* FFont3D; //Font3D property 포인터


__fastcall TApLabel::TApLabel(TComponent* Owner)
  : TCustomLabel(Owner)
{
  FFont3D = new TFont3D(); //TFont3D 의 object 생성 (object 형 property 이므로)
}

__fastcall TApLabel::~TApLabel(void)
{
  delete FFont3D; //FFont3D 파괴
}


전체적인 모습을 살펴봅시다.

class TFont3D : public TPersistent //Font3D property를 위한 class
{
private:
  TTextStyle FTextStyle;
  TShadowDirection FShadowDirection;
  TColor FShadowColor;
  int FShadowDepth;
  TNotifyEvent FOnChange;
  void __fastcall DoOnChange(void);
  void __fastcall SetShadowDepth(const int Value);
  void __fastcall SetShadowDirection(const TShadowDirection Value);
  void __fastcall SetTextStyle(const TTextStyle Value);
  void __fastcall SetShadowColor(const TColor Value);

public:
  __property TNotifyEvent OnChange = {read = FOnChange, write = FOnChange};
  void __fastcall Assign(TPersistent* Source);

__published:
  __property TColor ShadowColor = {read = FShadowColor, write = SetShadowColor, default = DefShadowColor};
  __property TShadowDirection ShadowDirection = {read = FShadowDirection, write = SetShadowDirection, default = DefShadowDirection};
  __property int ShadowDepth = {read = FShadowDepth, write = SetShadowDepth, default = DefShadowDepth};
  __property TTextStyle TextStyle = {read = FTextStyle, write = SetTextStyle, default = DefTextStyle};
};


TPersistent 로부터 상속받았다는 것을 알 수 있습니다. TPersistent 는 Stream 이 가능한 class 들의 제일 위 조상입니다.
Stream 이 가능하다는 것은 속성의 변화된 값을 파일에 저장할 수 있다는 뜻입니다.
즉, dfm 파일에 저장할 수 있다는 뜻입니다. property 들은 최소한 TPersistent 로부터 상속받아야 합니다.
물론, TPersistent 로부터 상속받은 class 로부터 상속받아도 됩니다. VCL class 계층도를 보면 TPersistent는
TObject 로부터 상속받는다는 것을 알 수 있습니다. 그리고 모든 컴포넌트의 조상인 TComponent 는 TPersistent 의 자손입니다.
TObject
     |
TPersistent
     |
TComponent

여기서 좀 묘한 문제가 발생합니다. Object 형 property 인 TFont3D는 ShadowColor, TextStyle 등을 변화시킬 수 있습니다.
그런데 TFont3D 는 TPersistent 로부터 상속받은 Object 형 property 이지 Component 가 아닙니다. 따라서 TFont3D의
property 들인 TextStyle , ShadowColor 등을 변화시킨다고 해서 Component 를 직접 변화시킬 수가 없습니다.
변화를 시킨다는 것은 화면을 다시 그려준다는 이야기인데 TPersistent 로부터 직접 상속받은 class 는 그런 기능이 없습니다.
따라서 TFont3D 를 Object 로 생성해서 사용하는 다른 Component 에서 대신 해줄 필요가 있습니다. 화면을 다시 그려주는
Invalidate() 함수를 호출하는 함수를 TFont3D의 property 와 연결시켜주는 이벤트 디스패치 함수가 있어야 됩니다. 그 역할을
하는 것이 DoOnChange() 함수입니다.


void __fastcall TFont3D::DoOnChange(void)
{
  if(FOnChange) FOnChange(this);  //TFont3D 와 이벤트 핸들러 연결
}


FOnChange 변수는 TNotifyEvent 형입니다. 저번의 메시지 핸들러 부분에서도 보았듯이 TNotifyEvent 형은
이쪽 class 의 함수와 저쪽 class 의 함수를 연결시켜줍니다. 실제로는 다른 class 의 함수가 하는 일을 마치
TFont3D class 내부의 함수가 하는 것처럼 만드는 것입니다.

(1) 값이 변화되었으면 DoOnChange() 호출
void __fastcall TFont3D::SetTextStyle(const TTextStyle Value)
{
  if(FTextStyle != Value)
  {
    FTextStyle = Value;
    DoOnChange();
  }
}

(2) TFont3D를 object 로 생성하여 사용하는 component 의 이벤트 핸들러와 연결
void __fastcall TFont3D::DoOnChange(void)
{
  if(FOnChange) FOnChange(this);  //TFont3D 와 이벤트 핸들러 연결
}

(3) TApLabel 생성자에서 연결
FFont3D->OnChange = Changed;

(4) TApLabel 의 이벤트 핸들러 Changed 에서 넘겨받아서 처리
void __fastcall TApLabel::Changed(TObject *Sender)  //이벤트 핸들러
{
  Invalidate(); //컨트롤을 새로 그린다.
}


(비교)
void __fastcall TApLabel::SetEdges(const TEdges Value)
{
  if(FEdges != Value)  //변경하려는 값이 기존의 값과 다르면
  {
    FEdges = Value;    //새로운 값으로 변경
    Invalidate();          //컨트롤을 새로 그린다.
  }
}


(1), (2), (3), (4) 로 표시된 부분과 (비교) 부분을 비교해 보세요 TFont3D 에서는 값이 변화되었을때의
처리만 해줄 뿐, 화면처리등의 구체적인 내용은 TApLabel 에서 하게 됩니다.

또 한가지 특이한 것은 Assign 함수입니다.

TFont3D* FFont3D; //Font3D property 포인터

위와 같이 포인터를 만들고

__property TFont3D* Font3D = {read = FFont3D, write = SetFont3D};

void __fastcall TApLabel::SetFont3D(TFont3D* Value)
{
  FFont3D->Assign(Value); //Object 형 property 의 값 설정
}

위와 같이 값을 넣을 때 Assign 함수를 사용해야 합니다. 만약 다음과 같이 한다면

void __fastcall TApLabel::SetFont3D(TFont3D* Value)
{
  FFont3D = Value;
}

에러가 나게 됩니다. FFont3D 는 포인터일 뿐이므로 Value 를 넣는다는 것은 주소만 바꾼다는
의미입니다. 원래의 객체는 해제되지 않은채로 잃어버리게 됩니다. 따라서 Assign 함수를 통해서
멤버들의 값을 일일히 대입해야 합니다.

void __fastcall TFont3D::Assign(TPersistent* Source)
{
  TFont3D* LS = dynamic_cast<TFont3D*>(Source);
  if(Source && dynamic_cast<TFont3D*>(Source)) //Source 가 NULL 이 아니고 안전하게 타입캐스팅 되었다면
  {
    ShadowDirection = LS->ShadowDirection; //속성을 복사한다.
    ShadowDepth = LS->ShadowDepth;
    ShadowColor = LS->ShadowColor;
    TextStyle = LS->TextStyle;
  }
}