การพัฒนา Custom Component ใน Delphi เป็นหนึ่งใน Feature สำคัญที่ทำให้ Delphi ได้รับความนิยมมาอย่างยาวนานจนถึงปัจจุบัน โดยเฉพาะใน Delphi เวอร์ชั่นล่าสุดก็ยังคงรองรับการพัฒนา Custom Component โดยใช้ Paradigm แบบ OOP
หัวใจสำคัญของการพัฒนา Component ให้ทำงานถูกต้อง ปลอดภัย และใช้งานได้ดีทั้งใน Designer-time (ตั้งค่าผ่าน Object Inspector) และ Runtime โดยการเขียนโค้ดนั้น Component Writer จำเป็นที่จะต้องเข้าใจ Lifecycle การทำงานของ Component และ Method หลักที่สำคัญที่ Delphi ได้เตรียมไว้ให้
นักพัฒนา Delphi หลายท่านนิยมเรียก Component ว่า VCL (Visual Component Library)
TForm, TButton, TEdit, ฯลฯTTimer, TFDQuery, TImageList, ฯลฯบทความนี้จะพาไปรู้จัก จะข้อเน้นตัวอย่างไปที่การพัฒนา Non-Visual Component โดย Method ตัวอย่างที่จะพูดถึงในบทความนี้คือ
Create, Destroy, Free, Loaded, Notification และอื่นๆ พร้อมตัวอย่างการใช้งาน
อ่านเพิ่มเติมเกี่ยวกับ OOP Concept และ คำศัพท์สำหรับ Delphi Component Writer
unit MyComponent;
interface
uses
System.Classes;
type
TMyComponent = class(TComponent) // สืมทอดความสามารถต่อจาก TComponent
end;
implementation
end.
จากตัวอย่างโค้ดข้างต้น Component ใหม่ของเรา
TMyComponentจะมีคุณสมบัติเท่ากับ Component ต้นแบบ (TComponent) ทุกประการโดยยังไม่มีคุณสมบัติใหม่เพิ่มเติม
วัตถุประสงค์
Create เป็นเมธอดประเภท constructor ที่จะถูกเรียกเมื่อมีคำสั่งสร้าง Component วัตถุประสงค์ของการโค้ดในเมธอดนี้คือ
type
TMyComponent = class(TComponent)
private
FAbout: string;
FList: TStringList;
public
constructor Create(aOwner: TObject); override;
published
property About: string read FAbout;
end;
implementation
constructor TMyComponent.Create(aOwner: TObject);
begin
inherited;
FAbout := 'TMyComponent Version 0.1 by tutor4dev';
FList := TStringList.Create;
end;
end.
Precautions
เมธอด
Createจะต้องเรียกinherited Create(aOwner)หรือเรียกแค่inheritedเหมือนในตัวอย่างก็ได้ในกรณีที่ Component ใหม่ของเรามี Method Signature เหมือนกันกับ Component ต้นฉบับ
วัตถุประสงค์
เมธอด Destroy ใช้สำหรับ
Precautions:
Object ซึ่งเป็น Member ของ Component ควรจะต้องถูก Free ที่ Destroy
type
TMyComponent = class(TComponent)
private
FList: TStringList;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
FList := TStringList.Create; // สร้าง Object
end;
destructor TMyComponent.Destroy;
begin
FList.Free; // ทำงาน Object
inherited;
end;
วัตถุประสงค์
Free เป็นเมธอดที่ใช้เรียกเมธอด Destroy แบบปลอดภัยถ้า Object เป็น nil และเรียกใช้ Free จะไม่เกิด Error
MyComponent.Free; // ปลอดภัยกว่าการเรียก Destroy โดยตรง
Precautions
❌ อย่าเรียก
Destroyโดยตรง ✅ เรียกFreeเพื่อทำทำลาย Object โดยFreeจะเรียกDestroyให้โดยอัตโนมัติ
วัตถุประสงค์
Loaded จะถูกเรียกหลังจาก Object Inspector โหลดค่า Property ของ Component ถูกโหลดจาก .dfm
type
TMyComponent = class(TComponent)
private
FFDQuery: TFDQuery;
FCachedUpdates: Boolean;
procedure SetFDQuery(aFDQuery: TFDQuery);
protected
procedure Loaded; override;
published
property FCachedUpdates: Boolean read FCachedUpdate write FCachedUpdates;
property FDQuery: TFDQuery read FFDQuery write SetFDQuery;
end;
procedure TMyComponent.Loaded;
begin
inherited;
FFDQuery.CachedUpdates := FCachedUpdates; // กำหนดค่าได้อย่างมั่นใจว่าทั้ง FFDQuery และ FCachedUpdates ได้รับการ Assigned ค่าครบถ้วนทั้ง 2 ตัวแล้ว
end;
procedure TMyComponent.SetFDQuery(aFDQuery: TFDQuery);
begin
FFDQuery := aFDQuery;
// FFDQuery.CachedUpdates := FCachedUpdates; // FCachedUpdates อาจยังไม่ถูก Assign ค่า
end;
เนื่องจากเราไม่สามารถมั่นใจได้ว่าในขณะ Runtime ซึ่งเมธอด
SetFDQueryกำลังทำงานนั้นFCachedUpdatesได้รับการ Assign ค่าแล้วหรือไม่
วัตถุประสงค์
ใช้ตรวจสอบเมื่อ Component ที่เราอ้างอิงอยู่ถูกสร้างหรือถูกลบ สำคัญมากเมื่อ Component มีการอ้างอิงกัน
type
TMyComponent = class(TComponent)
private
FDataSet: TDataSet;
protected
procedure Notification(aComponent: TComponent; Operation: TOperation); override;
published
property DataSet: TDataSet read FDataSet write FDataSet;
end;
procedure TMyComponent.Notification(aComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (FDataSet = aComponent) then
FDataSet := nil;
end;
ป้องกัน Access Violation ในกรณีที่ผู้ใช้ลบ DataSet ที่ได้กำหนดค่าให้กับ Property
TMyComponent.DataSetไว้แล้วไปลบ DataSet ทิ้งจาก Form, DataModule ในภายหลัง หรือถูกลบในขณะ Runtime
วัตถุประสงค์
ใช้สำหรับคัดลอก Property จาก Component หนึ่งไปยังอีกตัวมักใช้กับ TPersistent
procedure TMyComponent.Assign(Source: TPersistent);
begin
if Source is TMyComponent then
FText := TMyComponent(Source).FText
else
inherited;
end;
วัตถุประสงค์
ใช้กำหนด Property พิเศษที่ไม่ใช่ published Property หรือเก็บข้อมูลในรูปแบบเฉพาะ มักใช้ใน Component ระดับ Advanced
procedure DefineProperties(Filer: TFiler); override;
Create ทำงานLoaded ทำงานNotification ทำงานในกรณีที่ Component ตัวอื่นถูกลบDestroy ทำงานโดยถูกเรียกผ่าน Free