PDA

View Full Version : SuperComponents



s.mostafa.rahmani
سه شنبه 14 آبان 1387, 10:10 صبح
چگونه در دلفي يك كامپوننت تركيبي بسازيم؟

SuperComponent چيست؟
SuperComponents يا همان كامپوننتهاي مركب، عبارتند از مجموعه‌اي از زيركامپوننتها، و ارتباط آنها با يكديگر، در يك كامپوننت منفرد. معمولاً براي آسان شدن مديريت طراحي زيركامپوننتها، اين مجموعه داخل يك كامپوننت حامل (Container) قرار مي‌گيرد.

مزايا
SuperComponentها همه مزاياي كامپوننتهاي معمولي را دارند و بر اساس آنها ساخته مي‌شوند. RAD، استفاده مجدد از شئ، پايداري در رابط كاربر، همه از مزاياي اين كامپوننتها هستند. يك مزيت ديگر كم شدن كدنويسي است. اگر شما دو يا چند فرم داشته باشيد كه شامل ارتباطهاي مشابهي بين كامپوننتها باشند (در يك برنامه يا چند برنامه مختلف) اين ارتباطها و كدهايي كه براي آن نوشته‌ايد، در هر فرم تنها متعلق به همان فرم خواهند بود. و شما مجبوريد كدها و كامپوننتها را در هر فرم به طور جداگانه تكرار كنيد. با تركيب اين مجموعه كامپوننها در يك كامپوننت؛ كدهاي مديريت كننده ارتباط زيركامپوننتها از كدنويسي رابط كاربر جدا خواهند شد. در نتيجه كد موجود در هر فرم مربوط به وظيفه اختصاصي آن فرم بوده، و خواندن و پشتيباني آن آسانتر خواهد شد.

مزيت بزرگ ديگر SuperComponent از طريق ارث‌بري بدست مي‌آيد. شما مي‌توانيد قسمتهاي بزرگي از فرمهاي خود را به صورت SuperComponent تبديل كرده و در فرمهاي جديد براحتي استفاده كنيد. بدين طريق برنامه‌نويسي و طراحي رابط كاربر بسيار سريعتر از تجربه‌هاي گذشته شما در دلفي خواهد بود.

SuperComponentها مشخصه‌هاي (Properties) مورد نياز براي كنترل مجموعه كامپوننتها را نيز ساده‌تر مي‌كنند. بسياري از مشخصه‌ها و رويدادهاي كامپوننتها در SuperComponent مقداردهي مي‌شوند، و فقط مشخصه‌هاي مورد نياز خاص، ارائه خواهند شد، بنابراين از پيچيدگي كامپوننتها در زمان طراحي كاسته خواهد شد.

همانطور كه قبلاً گفتم، SuperComponent ارتباط بين كامپوننتها را شامل مي‌شود. اين ارتباط ممكن است يك الگوريتم و يا راه حل يك مسأله باشد، و يا تغيير حالتي را نگهداري كند كه هر زيركامپوننت به طور خاص نمايانگر آن نيست. پارامترهاي اين الگوريتم‌ها و تغيير حالتها، براحتي از طريق مشخصه‌ها و رويدادهاي جديدي كه براي SuperComponent تعريف مي‌شوند، قابل ارائه‌اند.

و بالاخره اينكه مي‌توانيد SuperComponentها را به عنوان ميني برنامه درنظر بگيريد. آنها كامپوننت هستند، و در صورتي كه خوب طراحي شوند، مي‌توانند به عنوان قسمتي جدا از كل پروژه انجام پذيرند. اين در محيط‌هاي توسعه تيمي، به آن معناست كه افرادي كه با مسأله يا راه حل آن آشناتر هستند مي‌توانند SuperComponentها را طراحي كنند و كاربران كم‌تجربه‌تر مي‌توانند قستمهاي ديگر برنامه را با استفاده از تركيب اين SuperComponentها ايجاد نمايند.

معايب
دو عيب در ساخت SuperComponent وجود دارد. اول اينكه ايجاد SuperComponent بصري (Visual) نياز به كدنويسي Windows Handle اضافي دارد. دوم، SuperComponent كمي سربار به كامپوننت حامل (Container) اضافه خواهد كرد.

قبل از شروع
بهتر است بين كاربران برنامه‌نويس و كاربران نهايي كامپوننت فرق قائل شويم. سه دسته كاربر با اين كامپوننتها تعامل خواهند داشت:
• كاربران نهايي (به طور خلاصه كاربران) كه برنامه ساخته شده نهايي را مورد استفاده قرار خواهند داد.
• برنامه‌نويسان كه برنامه را با استفاده از كامپوننت ما خواهند نوشت.
• كامپوننت‌نويس (يعني خودمان!) كه كامپوننتهايي خواهيم ساخت تا كار برنامه‌نويس در ساختن برنامه و استفاده توسط كاربر نهايي، آسان‌تر شود. اين مهم است كه شما به عنوان يك كامپوننت‌نويس، هنگام طراحي كامپوننت به هر دو دسته از كاربران توجه داشته باشيد.

طرح‌بندي كامپوننتها
به طور كلي مراحل ساخت يك SuperComponent عبارتند از:
ابتدا آرايش قرار گرفتن كامپوننتها را در يك فرم، با قرار دادن آنها در يك كامپوننت حامل (مثلاً TPanel، يا شبيه به آن) طراحي نماييد. كد Dfm آن پنل را انتخاب، كپي و در يك فايل متني Paste كنيد (با زدن كليدهاي Alt+F12 روي فرم در حال طراحي، مي‌توانيد كد dfm آن را ببينيد - م).
همه "="ها را به ":=" تبديل كنيد و در انتهاي هر خط يك ";" اضافه كنيد.
همه خطوط اعلان object را در اين كد با تنظيم مشخصه parent كامپوننتها به كامپوننت حامل، تغيير دهيد.
بقيه كدها را حذف كنيد. بيت‌مپها را بايد در Resourceها قرار دهيد.
كد پاسكال بدست آمده را در سازنده كامپوننت قرار دهيد. در اين كد، بخشهاي مربوط به هر شئ را در قسمت زيركامپوننت متناسب، گروه‌بندي نماييد.
حال براي تشريح مراحل فوق، به يك مثال مي‌پردازيم. در اين مثال يك تركيب كليدهاي Ok، Cancel و Help خواهيم ساخت. در يك فرم دلفي كليدها را شبيه به تصوير زير قرار دهيد (توجه كنيد كه خاصيت Border پنل به None تغيير يافته است) :
25180

كد dfm پنل را در يك فايل متني كپي كنيد:

object Panel1: TPanel
Left = 114
Top = 10
Width = 75
Height = 95
BevelOuter = bvNone
TabOrder = 0
object OKButton: TButton
Left = 0
Top = 0
Width = 75
Height = 25
Caption = 'OK'
Default = True
ModalResult = 1
TabOrder = 0
end
object CancelButton: TButton
Left = 0
Top = 35
Width = 75
Height = 25
Cancel = True
Caption = 'Cancel'
ModalResult = 2
TabOrder = 1
end
object HelpButton: TButton
Left = 0
Top = 70
Width = 75
Height = 25
Caption = 'Help'
TabOrder = 2
end
end

اين نماي متني گروه SuperComponent ما است. حال، بايد اين متن را به چيزي كه به كد پاسكال شبيه‌تر باشد تبديل كنيم:

object Panel1: TPanel
Left := 114;
Top := 10;
Width := 75;
Height := 95;
BevelOuter := bvNone;
TabOrder := 0;
object OKButton: TButton
Left := 0;
Top := 0;
Width := 75;
Height := 25;
Caption := 'OK';
Default := True;
ModalResult := 1;
TabOrder := 0;
end
object CancelButton: TButton
Left := 0;
Top := 35;
Width := 75;
Height := 25;
Cancel := True;
Caption := 'Cancel';
ModalResult := 2;
TabOrder := 1;
end
object HelpButton: TButton
Left := 0;
Top := 70;
Width := 75;
Height := 25;
Caption := 'Help';
TabOrder := 2;
end
end

حالا به آنچه مي‌خواهيم، نزديكتر شده‌ايم. مرحله بعد انتقال آماده‌سازي اوليه پنل به متد سازنده كامپوننت است. كنترل‌هاي جاسازي شده را به اين شكل خواهيم ساخت:

constructor TOkCancelHelp.Create(AOwner: TComponent);
{ Creates an object of type TOkCancelHelp, and initializes properties. }
begin
inherited Create(AOwner);
Width := 75;
Height := 95;
BevelOuter := bvNone;
TabOrder := 0;
OKButton := TButton.Create(Self);
OKButton.Parent := Self;
CancelButton := TButton.Create(Self);
CancelButton.Parent := Self;
HelpButton := TButton.Create(Self);
HelpButton.Parent := Self;
end; { Create }

بايد هر سه كليد OkButton، CancelButton و HelpButton را به صورت فيلد در كامپوننت جديدمان تعريف كنيم. اكنون، اعلان كامپوننت ما به اين شكل است:

type
TOkCancelHelp = class(TPanel)
OKButton: TButton;
CancelButton: TButton;
HelpButton: TButton;
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published properties and events }
end; { TOkCancelHelp }

حالا با استفاده از متن dfm، آماده‌سازي اوليه سه دكمه را انجام مي‌دهيم. گرچه اين كار را مي‌شود در متد سازنده كامپوننت هم انجام داد، اما كد آماده‌سازي اوليه بعضي از زيركامپوننتهاي VCL نياز به يك Window Handle موجود در كامپوننت Parent دارد. در هنگام ساخت SuperComponent (در متد سازنده) اين Handle هنوز موجود نيست. در نتيجه ما نياز به متدي در TPanel با قابليت override داريم كه قبل از نمايش و بارگذاري كامپوننت و بعد از تعيين Handle براي TPanel فراخواني مي‌شود. بهترين متد براي هدف ما CreateWindowHandle است. متد override شده آن را به همراه كد آماده‌سازي اوليه دكمه‌ها در زير مي‌بينيد:

procedure TOkCancelHelp.CreateWindowHandle(const Params: TCreateParams);
{ Calls inherited CreateWindowHandle and initializes subcomponents. }
begin
inherited CreateWindowHandle(Params);
with OKButton do
begin
Left := 0;
Top := 0;
Width := 75;
Height := 25;
Caption := 'OK';
Default := True;
ModalResult := 1;
TabOrder := 0;
end; { OKButton }
with CancelButton do
begin
Left := 0;
Top := 35;
Width := 75;
Height := 25;
Cancel := True;
Caption := 'Cancel';
ModalResult := 2;
TabOrder := 1;
end; { CancelButton }
with HelpButton do
begin
Left := 0;
Top := 70;
Width := 75;
Height := 25;
Caption := 'Help';
TabOrder := 2;
end; { HelpButton }
end; { CreateWindowHandle }

و همچنين اعلان كامپوننت بايد شبيه به اين باشد:

type
TOkCancelHelp = class(TPanel)
OKButton: TButton;
CancelButton: TButton;
HelpButton: TButton;
private
{ Private declarations }
protected
{ Protected declarations }
procedure CreateWindowHandle(const Params: TCreateParams); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published properties and events }
end; { TOkCancelHelp }

در نهايت، نياز به يك پروسيجر Register داريم تا بتوانيم كامپوننت خود را در پالت كامپوننتهاي دلفي قرار دهيم:

procedure Register;
begin
RegisterComponents('CDK', [TOkCancelHelp]);
end; { Register }


نمايش مشخصه‌هاي زيركامپوننتها
تركيب كامپوننتها براي ايجاد يك SuperComponent يك روش اصولي است. يكي از مزاياي آن اين است كه به شما امكان مي‌دهد بسياري از مشخصه‌هاي زيركامپوننتها را براي برنامه‌نويس محدود كنيد. در حقيقت شما بايد صراحتاً در كد SuperComponent مشخصه‌هاي زيركامپوننت را ذكر كنيد، وگرنه تمام مشخصه‌هاي زيركامپوننتها از ديد برنامه‌نويس مخفي خواهند ماند!

خوب، حالا چگونه يك مشخصه از زيركامپوننت را آشكار كنيم؟ بايد دو متد انتقال دهنده بسازيم كه مشخصه‌هاي زير‌كامپوننت را به دنياي بيرون منتقل كنند.

مثلاً در كامپوننت TOkCancelHelp ما، انتقال مشخصه Caption دكمه Ok، مفيد است، در نتيجه برنامه‌نويس خواهد توانست آن را تغيير دهد (مثلاً براي برنامه‌اي كه زبانش انگليسي نيست). متدهاي انتقال ما بسيار شبيه متدهاي Get و Set معمولي براي مشخصه‌ها هستند كه از قبل با آنها آشناييم. اين هم اعلان مشخصه Caption براي دكمه Ok:

type
TOkCancelHelp = class(TPanel)
.
.
.
private
{ Private declarations }
procedure SetCaption_OKButton(newValue: TCaption);
function GetCaption_OKButton: TCaption;
.
.
.
published
{ Published properties and events }
property Caption_OKButton: TCaption read GetCaption_OKButton write SetCaption_OKButton;
end;

اين متدهاي انتقال، مقدار مشخصه را به/از زيركامپوننت انتقال مي‌دهند. پياده‌سازي آنها به اين شكل است:

function TOkCancelHelp.GetCaption_OKButton: TCaption;
{ Returns the Caption property from the OKButton subcomponent. }
begin
result := OKButton.Caption;
end; { GetCaption_OKButton }
procedure TOkCancelHelp.SetCaption_OKButton(newValue: boolean);
{ Sets the OKButton subcomponent's Caption property to newValue. }
begin
OKButton.Caption := newValue;
end; { SetCaption_OKButton }

حتماً توجه داريد كه براي اين مشخصه هيچ فيلدي در نظر گرفته نشده، تمام مشخصه‌هاي زيركامپوننتها براي ذخيره‌سازي به خود زيركامپوننت متكي هستند. و همچنين توجه داشته باشيد كه اين متد Set بر خلاف ديگر متدهاي مشابه اين كه آيا مقدار ورودي با مقدار فعلي برابر است يا نه، را بررسي نمي‌كند. ما اين بررسي را به خود زيركامپوننت واگذار مي‌كنيم.

نمايش رويدادهاي زيركامپوننتها
همانطور كه ممكن است لازم باشد كه مشخصه‌هايي از زيركامپوننت را نمايش دهيد، شايد نياز به نمايش رويدادهايي از زيركامپوننتها نيز داشته باشيد. دليل نمايش رويدادهاي زيركامپوننتها همان دليل نمايش مشخصه‌هاي آنها است. اما تفاوت اينجاست كه در نمايش رويداد بايد گرداننده (handler) مربوط به رويداد را در يك فيلد ذخيره كنيم (درست مثل يك گرداننده رويداد معمولي). بعلاوه بايد امكان فراخواني رويداد را براي هر رويداد به طور جداگانه براي برنامه‌نويس فراهم نماييم، يعني بايد يك گرداننده رويداد (Event Handler) براي هر كدام بنويسيم. يك گرداننده رويداد را بايد هنگام ايجاد كردن كامپوننت (Create) به طور پويا مقداردهي نماييم. نحوه اعلان يك رويداد و گرداننده رويداد متناظر آن به شكل زير است:

type
TOkCancelHelp = class(TPanel)
.
.
.
private
{ Private declarations }
FOnClick_OKButton: TNotifyEvent;
.
.
.
procedure Click_OKButtonTransfer(Sender: TObject); { TNotifyEvent }
published
{ Published properties and events }
.
.
.
property OnClick_OKButton: TNotifyEvent read FOnClick_OKButton write FOnClick_OKButton;
end;

Click_OKButtonTransfer به عنوان گرداننده رويداد عمل مي‌كند. به نوع آن توجه كنيد، TNotifyEvent، اين همان نوع رويداد OnClick است. پياده سازي اين متد انتقال بدين شكل است:

procedure TOkCancelHelp.Click_OKButtonTransfer(Sender: TObject);
{ Transfers the OKButton OnClick event to the outside world. }
begin
if assigned(FOnClick_OKButton) then
FOnClick_OKButton(Self); { Substitute Self for subcomponent's Sender. }
end; { Click_OKButtonTransfer }

اگر متدهاي حساس به رويداد (مثل كليك – م) نوشته باشيد، حتماً با كد بالا آشنا هستيد. بخش if بررسي مي‌كند كه آيا اين رويداد مقداردهي شده است يا نه (مثلاً هنگام طراحي از طريق بازرس شئ (Object Inspector)) كه اگر اين طور باشد، آن متد نسبت داده شده را فراخواني مي‌كند و خود SuperComponent را به عنوان Sender به آن ارسال مي‌نمايد. بنابراين با اين متد، رويداد زيركامپوننت مديريت مي‌شود و بعد از آن هم اگر برنامه‌نويس متدي نسبت داده باشد اجرا خواهد شد (بله، دو گرداننده براي يك رويداد!). اين اجراي متوالي دو گرداننده به اين دليل است كه ما متدها را به صورت پويا (Dynamic) به زيركامپوننت نسبت داده‌ايم. ما اين كار را در CreateWindowHandle كه override شده انجام داديم:

procedure TOkCancelHelp.CreateWindowHandle(const Params: TCreateParams);
{ Calls inherited CreateWindowHandle and initializes subcomponents. }
begin
inherited CreateWindowHandle(Params);
with OKButton do
begin
.
.
.
OnClick := Click_OKButtonTransfer;
end; { OKButton }
.
.
.
end; { CreateWindowHandle }


مديريت رويدادهاي زيركامپوننتها
گاهي نياز است كه به يك رويداد از يك زيركامپوننت پاسخ داده شود، بدون اين كه آن رويداد را براي برنامه‌نويس آشكار كرده باشيم. مراحل انجام اين كار شبيه به مراحل نمايش رويداد براي برنامه نويس است، با اين تفاوت كه رويداد را اعلان نمي‌كنيم (در نتيجه فيلدي هم براي ذخيره سازي آن در نظر نمي‌گيريم) :

type
TOkCancelHelp = class(TPanel)
.
.
.
private
{ Private declarations }
.
.
.
procedure Click_CancelButtonHandler(Sender: TObject); { TNotifyEvent }
published
.
.
.
end;

و گرداننده رويداد آن:

procedure TOkCancelHelp.Click_CancelButtonHandler(Sender: TObject);
{ Handles the CancelButton OnClick event. }
begin
{ Place your event-handling code here. }
end; { Click_CancelButtonHandler }

و نسبت دادن آن به زيركامپوننت دقيقاً شبيه به حالتي است كه مي‌خواستيم آن رويداد را نمايش دهيم:

procedure TOkCancelHelp.CreateWindowHandle(const Params: TCreateParams);
{ Calls inherited CreateWindowHandle and initializes subcomponents. }
begin
inherited CreateWindowHandle(Params);
.
.
.
with CancelButton do
begin
.
.
.
OnClick := Click_CancelButtonHandler;
end; { CancelButton }
.
.
.
end; { CreateWindowHandle }


خلاصه
SuperComponentها، پايداري و قابليت استفاده مجدد را بهبود مي‌بخشند. آنها مي‌توانند شامل تركيب پراستفاده كنترلها باشند كه به طور چشمگيري از زمان توسعه برنامه، و نيز ميزان كدنويسي مي‌كاهد. ضمن اينكه ماهر شدن در تكنيكهايي كه در اين مقاله بحث شد، چندان سخت نيست.

---------------------------
فايل PDF فارسي: 25181
---------------------------

markm@eagle-software.com
Copyright © 1996, 1997 Mark Miller
Eagle Software

آدرس فايل اصلي: http://delphi.about.com/library/weekly/suprcomp.zip

سيدمصطفي رحماني
s.mostafa.rahmani@gmail.com
http://hemmat.freehostia.com