Delphi 元件設計初步(一)

原创 2002年06月05日 10:27:00

Delphi 元件設計初步(一)

作者:Danny Tzu
日期:May-2-2002

前言

其實這篇文章很早以前就想寫了, 但礙於個人能力及時間的問題, 就一直拖著沒動作, 本篇並不是要講很多 VCL 的大道理, 而是教您如何簡單的 Step By Step 作出您自己的元件.

當然有人從不用3-Party元件, 理由是可以直接在另一台電腦上撰寫程式不用安裝元件, 但是Delphi本身就是用元件堆出來的, 不使用自訂元件我認為並不是一個聰明的作法.

撰寫本篇是因為大多數使用 Delphi 的人只會使用元件, 也許這是當初採用 Delphi 的原因, 但是在設計軟體時, 有很多時候只要簡單的設計或修改元件就可以解決問題, 我知道也有人寫一些共用的 procedure 或 function 但使用起來會有些限制或不方便, 但一想到要寫(改)元件要知道很多技術及原理, 尤其完整的撰寫元件文章書籍都是片片斷斷, 可能就馬上打了退堂鼓, 其實寫元件也可以很簡單, 當然您懂的東西越多可以寫的元件功能及型態越多.

本篇不談 Interface 的使用(以下範例一個 Interface 都沒用到), 但並不表示 Interface 不重要, 相反的它非常重要, 主要是我認為這不適合在這理說明, 如果各位有興趣的話可以參考 BrianChang 的Interface的基礎 code6421 的淺談Interface」兩篇文章.

這裡我不會講很多的大道理, 但這並不表示您就可以不需要瞭解一些像: 物件導向, Windows 訊息機制, Stream I/O 機制 ... 等相關知識, 如果您懂得越多當然寫出來的元件越好; 雖然 Delphi 以視覺化設計著稱, 但寫元件一點也不視覺化, 您必須要像在 DOS 時代一樣純手工打造, 也許對有點年紀的人來說會反而更熟悉吧 !

使用環境

Delphi 5, 6 All Version
建議使用 Enterprise 版本可以有很多 VCL Source code 可以參考.

開始吧

元件大致上分為二大類:

  1. 不可視元件:
    不可視元件一般和系統有關, 他的特色是在 Run Time 時在軟體上是看不到他的, 但確實會提供服務, 像 TActionList, TMainMenu, TPopupMenu, TDataBase, TQuery.... 等. 
  2. 可視元件:
    可視元件和不可視元件剛好相反, 在 Run Time 時他是看得到的, 當然 Design Time 也是可以看到, 不然您如何設定特性(property)及 Event, 像 TImage, TDBGrid, TEdit .... 都是.

其實您已經在寫元件了可能您自己都不知道, 您每天寫程式都是寫在哪裡 ? TForm 對吧 ! 您的 Form 都是繼承自 TForm 這個元件, New Form Unit 會產生如下的框架程式碼:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  TForm1 = class(TForm)
  private
    
  public
    
  end;

var
  Form1: TForm1;

implementation



end.

而以 New Component (Component -> New Component) Unit 所產生的框架大概像下面這樣:

unit CustomControl1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  TCustomControl1 = class(TCustomControl)
  private
    
  protected
    
  public
    
  published
    
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TCustomControl1]);
end;

end.

注意到了嗎? 這二個 Unit 只差在 TForm 沒有宣告 protected, published 這二個節區, 因為大部份在寫 Form 時都用不到這二個節區, 所以 Delphi 會省略掉, 但是您要用的話也可以, 就自己手動加上吧!
另外因為要安裝到元件盤的關係, 所以 CustomControl1 會多出 procedure Register, 但反過來想 TForm 是不能安裝到元件盤的, 這部份您可以試試看有沒有辦法安裝到元件盤, 可以的話請告訴我一聲.
另外 TForm 不用安裝就能使用, 這點我想是為了方便的原因, 不過如果您是用 Delphi Package 技術的話還是需要安裝這一個步驟, 這就和元件一樣了, 如果您有興趣可以參考在下另一拙作 "Delphi Package 無痛使用"

第一個自製元件

首先我們作一個 "不可視元件" , 為何要先選 "不可視元件" 作呢 ? 原因是 "簡單" 因為不需要很多的技術就能作了.

開始當然要先選繼承的父親是誰 ? 因為我們是作不可視元件所以選 TComponent 為父親, 另外順便一提 Delphi 有定義繼承自 TComponent (含)以下的叫 "元件" , 其他的叫 "物件", 因此 "物件" 包含 "元件" (白馬非馬也, 聽過吧 !), "元件" 和 "物件" 差在可不可以安裝在 "元件盤" 上(不過TForm是例外,雖然它也是繼承自TComponent).
首先要決定繼承自那一個元件, 因為我們要撰寫 "不可視元件",當然是 TComponent 了,如果您高興也可以繼承別的元件, 不過相信一定要額外處理比較多的事情, 我們就不找麻煩了.

1. TAutoClose 定時關閉元件所在TForm:

在如下畫面中選擇 New Component 新增元件, 此步驟以後不在重覆提到.

fig01.jpg

Ancestor type 中選擇要繼承的父階元件, 我們選 TComponent, Class Name 內輸入我們要建立的元件名稱, 這裡順便一提, 元件的撰寫慣例是用 T 開頭, 後接 2-3Byte的元件組名(自定), 其後才是此元件的名稱(詳細定義請參考煥麟的「請依照標準寫碼風格撰寫程式 Delphi 5 寫碼標準和「Delphi 5 元件型態字首」), 這麼做的原因是 Delphi 不允許 Class Name 重覆, 如果大家都亂編的話, 重覆的機率就很高了; 但我們只是示範如何寫元件就省略元件組名了.
但新增元件和 Class 有何關係, 我這樣說好了, 元件(物件)是類別(Class)的實作這樣說比較容易理解了吧!<?XML:NAMESPACE PREFIX = O />

fig02.jpg

設好了以後可以在 Unit file name 中選擇存檔的路徑及名稱, 按 OK 後出現如下內容就可以開始寫元件了, 但我先說明各區段的意義一下:

unit AutoClose;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

type
  TAutoClose = class(TComponent)
  private
    
    
    
  protected
    
    

  public
    
    
 
  published
    
    
    
    
     

  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TAutoClose]);
end;

end.

如果您看不懂以上的說明那也很正常, 這並不會影響後面的進度, 好啦 ! 開始寫作了.

因為我們要設定時間自動可以Close Form, 第一個想到的是用 TTimer 來作, 那就試試看吧!

unit AutoClose;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, extctrls;

type
  TAutoClose = class(TComponent)
  private
    
    FTimer: TTimer;  
  protected
    
  public
    
  published
    
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TAutoClose]);
end;

end.

首先在 private 中宣告一個Field(欄位)叫 FTimer: Timer, 但 Delphi 竟然不認識 TTimer, 在 TTimer 上按 F1 查到是放在 extctrls 單元中, 我們就手動加入吧 ! 

再來宣告產生及毀滅本元件的程式, 在 public 中宣告:

constructor Create(AOwner: TComponent); override;
destructor Destroy; override;

以上名稱當然可以改, 但是我不建議改它, 因為這會讓 C++ (就是BCB) 無法使用本元件, 因為 C++ 的記憶體管理和Delphi不同.
好啦! 現在按 [Ctrl] + [Shift] + [C] 讓Delphi幫我們產生相關程序, 再手動加工成為如下程式:

private
... 省略 ...
procedure NewTimer(Sender: TObject);
... 省略 ...

constructor TAutoClose.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FTimer := TTimer.Create(Self); 
  FTimer.Enabled := False;
  FTimer.OnTimer := NewTimer;
end;

destructor TAutoClose.Destroy;
begin
  FreeAndNil(FTimer);
  inherited Destroy;
end;

由於 Timer 是我們自己要用的物件, 所以要自己產生自己消滅, 加上產生 Timer 程式碼及時間到了以後要做什麼事的procedure (內容先不急得寫),

我們可以宣告二個 property Enabled(啟動AutoClose) 及 Interval (時間) 讓User 自己設定處理, 在 published 中宣告如下:

property Enabled: Boolean read GetEnabled write SetEnabled;
property Interval: Cardinal read GetInterval write SetInterval;

一樣按 [Ctrl] + [Shift] + [C] 讓Delphi幫我們產生相關程序, 再改成如下這樣:

function TAutoClose.GetEnabled: Boolean;
begin
  Result := FTimer.Enabled;
end;

function TAutoClose.GetInterval: Cardinal;
begin
  Result := FTimer.Interval;
end;

procedure TAutoClose.SetEnabled(const Value: Boolean);
begin
  FTimer.Enabled := Value;
end;

procedure TAutoClose.SetInterval(const Value: Cardinal);
begin
  FTimer.Interval := Value;
end;

procedure TAutoClose.NewTimer(Sender: TObject);
begin
  Enabled := False;
  ShowMessage('Hello My First Component');       
end;

這樣基本上元件就寫好了, 因為 Delphi VCL 是以 Package(.BPL)型式存在的, 所以需有一個 Package 容納我們寫的元件。接著 File ->New,如圖3:

fig03.jpg

選 Package 再選 OK:

fig04.jpg

出現如下視窗 按 Mouse 右鍵選 Save:

fig05.jpg

選擇要存 Package 的檔名及位置, 建議存在 Projects/BPL 下 , 決定好後選存檔:

fig06.jpg

回到(圖 5) 畫面, 按一下 Add,出現如圖 7 的視窗:

fig07.jpg

選[Browse] 將 AutoClose.pas 加入, 再選 OK。

fig08.jpg

然後選 Install 安裝元件到元件盤中。

好啦! 到元件盤的 Samples 頁中選 AutoClose 到 Form 中測試看看有沒有正常.

fig09.jpg

可以放到 Form 而且 Object Inspector 內也有這二個自訂property了, 應該沒問題了吧 ! 將 Enabled 設成 True 測試看看

fig10.jpg

可以出現 (圖 10) 對話框沒問題了.

但是這元件如果在 Design Time 那不是也有問題嗎 ? 沒錯, 所以要稍微加工改一下, 再將 TAutoClose 真正要做的事寫到 NewTimer 中, 完整程式碼如下:

unit AutoClose;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls;

type
  TAutoClose = class(TComponent)
  private
    
    FEnabled: Boolean;
    FTimer: TTimer;
    FOnTimer: TNotifyEvent;

    procedure NewTimer(Sender: TObject);
    function GetInterval: Cardinal;
    procedure SetEnabled(const Value: Boolean);
    procedure SetInterval(const Value: Cardinal);
  protected
    
  public
    
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

  published
    
    property Enabled: Boolean read FEnabled write SetEnabled;
    property Interval: Cardinal read GetInterval write SetInterval;
    property OnTimer: TNotifyEvent read FOnTimer write FOnTimer;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TAutoClose]);
end;



constructor TAutoClose.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FTimer := TTimer.Create(Self);
  FTimer.Enabled := False;
  FTimer.OnTimer := NewTimer;
end;

destructor TAutoClose.Destroy;
begin
  FreeAndNil(FTimer);
  inherited Destroy;
end;

function TAutoClose.GetInterval: Cardinal;
begin
  Result := FTimer.Interval;
end;

procedure TAutoClose.SetEnabled(const Value: Boolean);
begin
  FEnabled := Value;
  
  if not (csDesigning in ComponentState) then
    FTimer.Enabled := Value; 
end;

procedure TAutoClose.SetInterval(const Value: Cardinal);
begin
  FTimer.Interval := Value;
end;

procedure TAutoClose.NewTimer(Sender: TObject);
begin
  Enabled := False;    
  if Assigned(FOnTimer) then
    FOnTimer(Sender);
    
  (Owner as TForm).Close;
end;

end.

以上程式已經將 procedure GetEnabled 移除, 並宣告一個 FEnabled 欄位, 這樣作的目的是可以讓我們在 Design 中設 Enabled := True , 但不會真正的觸發 FTimer , 當然 procedure SetEnabled 也要改寫才行, 另外再增加一個 FOnTimer , 可以讓User 在 Form Close 前處理一些事情; 完成後再寫一個程式測試一下此元件是否可以正常運作, 例如: 在 Design Time 設 Enabled := True 看在Design Time 會不會動作, Run Time 是否會在設定時間時 Close Form, 如果沒問題那此元件就測試完成了; 但是您有沒有覺得內定的元件Icon有點醜, 而且如果每一個元件 ICON 都長得一樣 User也會弄錯, 接下來做元件 ICON 吧!
選 Image Editor , File -> New -> Component Resource File (.dcr)

fig11.jpg

按 Mouse 右鍵, New -> Bitmap

fig12.jpg

圖型長寬設成 24 x 24 這是 Delphi 元件盤可以接受的ICON大小

fig13.jpg

按 Mouse 右鍵, Rename 輸入 Class Name TAutoClose

fig14.jpg

在 Class Name 上, 快按 Mouse 左鍵二次, 開始畫 ICON 內容, 您也可以按
[Ctrl] + [I] 放大
[Ctrl] + [U] 縮小

fig15.jpg

存檔檔名設 AutoClose.dcr 並且必需和 AutoClose.pas 同目錄才可以重新Install TAutoClose 的 Package (MyPackage.dpk), 如果 AutoClose.dcr 沒有在 Package 中, 您要手動加進來, 完成後看 Samples 的頁面:

fig16.jpg

已經有我們自己設計的 ICON 在上面了…..

前面講解的不只是如何寫一個元件, 還包含程式的debug方法及製造流程, 雖然有一點繁複但是卻是問題最少, 比較不會發生程式寫完了, 卻發生程式也完蛋了的情形, 必竟良好的程式設計習慣也是很重要的.

附錄A: 訊息的應用

訊息(Message)的應用很廣, 精確的說,如果沒有訊息, Borland 的 VCL 應該架構不出來吧!

訊息的使用時機大部份是補您在元件設計上的不足, 或者是為了程式的精簡而使用, 像有時候需要對很多的元件作處理(例如:重畫元件), 如果一個一個去處理, 是乎不是個好方法, 尤其元件種類繁多, 這時用訊息就是很好的選擇.

訊息可以只對某元件傳送或是用廣播的方式送出, 但是接受的元件必需有相對應的處理程序, 而且必須是元件的 Month, 聽起來好像和網路的處理方式一樣, 沒錯! 是一樣的. 

VCL其實已經將訊息處理機制包含在裡面了, 在所有元件(物件)的始祖 TObject 中已經包含訊息處理機制, 也就是說所有的元件都有處理訊息的能力, 不過 VCL 將訊息包裝的比較簡單了, 所以使用起來就很簡單了, 接下來我就來示範如何使用訊息.

元件內的訊息廣播:

要能夠做訊息的廣播必須是 TWinControl 的後代子孫, 廣播使用的是 BroadCast 方法, 要做廣播大慨像以下這樣寫法

var 
  Msg: TMessage;
begin
  Msg.Msg := CM_REPAINT_BY_SELF;
  Msg.WParam := 0;
  Msg.LParam := 0;
  Msg.Result := 0;
  (Self as TWinControl).Broadcast(Msg);
end;

其中 CM_REPAINT_BY_SELF; 是我自己定的訊息, 當然您也可以使用 Windows 的訊息, 他們定義在Windows.pas 中, 如果要定義自定訊息要像以下這樣

const
  CM_REPAINT_BY_SELF = WM_USER + 100;

自定訊息必須以 WM_USER + 100 開始一直到 $7FFF 結束, 我想 Windows 提供的應該夠用了, 這個區域以外請不要使用, 因為 Windows 已經定義了這區域, 如果重覆定義可能會有想不到的問題發生

接受訊息部份程式像以下這樣:

procedure WMREPAINT_BY_SELF(var Message: TMessage); message CM_REPAINT_BY_SELF;

… 省略 …

procedure TahDBEdit.WMREPAINT_BY_SELF(var Message: TMessage);
begin
  Invalidate;
end;

以上例子只是收到訊息重畫元件而已, 當然您可以做很複雜的事. 不過以上範例最多只有侷限在一個 TForm 中而已.


待續...

2. TDoHotKey 簡單設定 HotKey
3. 有底圖的TLabel
4. TStatusProcess 在 TStatusBar 上加上 TProcessBar 功能
5. 資料感知元件:
6. 元件協同處理:
7. 自製密碼詢問元件:
8. 不同程序(Processe)程式(EXE),視窗(Form)的訊息廣播:

Delphi 元件設計初步(一)

  • zgqtxwd
  • zgqtxwd
  • 2008年04月30日 16:26
  • 138

Delphi 元件設計初步(二)

http://sun.cis.scu.edu.tw/~nms9115/articles/delphi/VclWrite2/VclWrite2.htm Delphi 元件設計初步(二)作者:曾培彥日期:...
  • liukeforever
  • liukeforever
  • 2009年05月05日 22:31
  • 694

电路与Multisim基础 小特点:该元器件已废弃

慈心积善融学习,技术愿为有情学。善心速造多好事,前人栽树后乘凉。我今于此写经验,愿见文者得启发。 出生,发展,衰退。 一些规律,要意识到。 Multisim软件优秀,值得...
  • yushaopu
  • yushaopu
  • 2016年09月10日 22:32
  • 747

詳細設計用の言葉

仕様書を作成するとき、常用言葉です。プロジェクトをやる中、全ての担当者は同じな言葉で仕様書を作成すれば、理解しやいだけではなく、効率も向上できて、品質も保証できる。 1 DBについてleft Join...
  • haru
  • haru
  • 2009年02月19日 22:22
  • 711

基本設計書手順

基本設計フェーズ手順: ①業務フロー作成 ②機能一覧作成 ③基本設計書(表紙+I/O関連図+画面/帳票レイアウト)   基本設計書書き方      ①どのデータを使って、処理方法などを i/...
  • iameyama
  • iameyama
  • 2013年03月27日 23:52
  • 532

论复盘的正确姿势2.0

原创 2017-09-15 作手阿见 超短英雄社    粉丝福利活动火热进行中,后来的没看到活动详情的朋友,请在公众号底部菜单栏《新友指南》里面《粉丝福利活动》获取详细内容,谢谢大家支持!!...
  • u014032673
  • u014032673
  • 2017年10月13日 21:00
  • 111

delphi与api中的加一减一函数

用于增减变量的并不是常用的Inc/Dec过程,而是用了InterlockedIncrement/InterlockedDecrement这一对过程,它们实现的功能完全一样,都是对变量加一或减一。但它们...
  • diligentcat
  • diligentcat
  • 2012年07月27日 12:08
  • 792

delphi 中实现当期日期 减去 若干小时的方法

假定当期日期为:2011-08-01 15:00:00  now - 1      :代表前一天的日期 返回值:2011-07-31 15:00:00  now - 1/3   :代表8小时前  ...
  • lotusyangjun
  • lotusyangjun
  • 2011年08月01日 15:28
  • 2338

Cardboard虚拟现实开发初步(二)

Google Cardboard 虚拟现实眼镜开发初步(二) Cardboard SDK for Unity的使用 上一篇文章作为系列的开篇,主要是讲了一些虚拟现实的技术和原理,本篇就会带领大家去...
  • sunmc1204953974
  • sunmc1204953974
  • 2015年08月02日 09:06
  • 9622

Wolf Mac中文版(网页设计) v1.5.2破解版

Wolf Mac中文版(网页设计) v1.5.2破解版,详见博客:http://003e5258-ab01-4b8c-83e6-a78718399eb3.coding.io/2018/01/19/wo...
  • zhangzhihong8001
  • zhangzhihong8001
  • 2018年01月19日 17:25
  • 29
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Delphi 元件設計初步(一)
举报原因:
原因补充:

(最多只允许输入30个字)