天天看點

Delphi 元件撰寫常問問題

1.1 此份檔案的目的為何?

這份檔案的目的是為了解答有關撰寫 Delphi元件時常見或檔案上找不到的問題。我曾經花了一段很長的時間來了解探索 TDataLink 類别,這讓我覺得應該将撰寫元件時常遇到的問題及經驗心得寫下來,分享給大家。不過我并不能保證寫在這份檔案裡頭的解答完全正确。如果你對其中的任何問題有更好的解決方法,或認為有什麼資訊适合放在這份檔案裡的話,請告知作者。有任何錯誤或缺漏也歡迎指正。

除了再加上更多的問題及解答外,我試着再補充兩個部分:

進階程式設計師喜愛的工具:這也許跟元件設計沒有直接的關系但至少它們跟 Delphi有關系。

值得參考的檔案刊物:由于空間的關系,這份檔案不能放置太多的範例程式,是以參考其它檔案是十分需要的。這不是一份教材式的檔案,我不會做太多條理式的說明,但會試着将最具有參考價值的文獻列出。

如果你有任何意見或建議,歡迎來信告訴我。

--------------------------------------------------------------------------------

第二部份 整合環境

2.1 在整合環境中如何找出元件所産生的問題?

我發現唯一能找出問題的方法隻有:

在 Delphi 整合環境的 Tools|Options 對話框的 Library 頁中将『Compile with debug info』選項打勾。

選 Component|Rebuild Library 重新編譯元件庫。

從 Turbo Debugger 中執行 Delphi。

選File|Change Dir移至包含元件程式代碼的目錄下。

如果你的元件發生GPF時就可以檢視堆棧然後得知到底是哪些發生問題了。

2.2 如何檢視 Delphi 所産生的彙編語言碼?

Glen Boyd 的回答:

開啟登入編輯程式(REGEDIT.EXE),接着到『HKEY_CURRENT_USER/Software/Borland/Delphi/2.0/Debugging』下新增一個字元串機碼『EnableCPU』,将它的字元串值設為『1』。此後Delphi整合環境的View選單下就會多一個『CPU』選項,它會開啟一個視窗來檢視目前程式指令的記憶體及彙編語言。你可以在偵錯時利用單步追蹤或其它方法來觀察它。

2.3 我可以在執行時期動态建立元件,但在設計時期就會發生錯誤。為什麼?

你的元件必須繼承自TComponent類别或其衍生類别。

你的元件建構函式及滅構函式宣告必須看起來像這樣:

constructor Create(AOwner: TComponent); override;

destructor Destroy; override ;

所有在published區段宣告的字段型态必須是ordinal、single、double、extended 、comp、currency、string、small set(譯注:指元素編号不超過0..31這個範圍的集合;平常的集合可容許的範圍為0..255)、method pointer或class其中一種。如果你宣告了其它型态的字段,Delphi編譯器并不會檢查出錯誤。然而當你使用這個元件時依然會得到一個GPF。

如果你想讓TMyComponent元件可以在設計時期操作,注意下面的宣告會引發十分嚴重的問題:

type TComplex = record

RealPart: Double;

ComplexPart: Double;

end;

class TMyComponent = Class(TComponent)

private

F1: TComplex;

published

property P1: TComplex read F1 write F1;

end;

2.4 如何撰寫一個無法放置到表格上的元件?

Ray Lischner 的回答:

如果你不想讓使用者将元件拉曳至表格上的話,使用 RegisterNoIcon 及 RegisterClass 程式來注冊元件。

2.5 在程式代碼編輯器中快速切換程式區段最簡單的方法是什麼?

Ray Konopka 的回答:

在探索 VCL 原始程式代碼時,強烈建議你最好熟悉程式代碼編輯器裡的書簽功能。使用方法很簡單:Ctrl-Shift-N,N 是從 0 至 9 的數字,用來設定一個書簽。此後就可以使用 Ctrl-N 來跳躍至書簽處。(譯注:使用這項功能真的可以節省你許多來回卷動程式及找尋函式的時間,别遲疑了,快學吧!)

2.6 如何使我的元件在按下滑鼠右鍵時出現快速功能選單?

你必須要建立一個元件編輯器。元件編輯器決定了元件在設計時期時對滑鼠鍵的反應及動作,你可以為元件定義它自己的快速功能選單。

建立元件編輯器的步驟大緻如下:

從 TComponentEditor 類别繼承一個新的類别。

改寫類别的 GetVerbCount、GetVerb及 ExecuteVerb方法。

在 Register 程式中使用 RegisterComponentEditor 程式來注冊此元件編輯器。

有關元件編輯器這個主題在『Developing Delphi Components』這本書中有詳盡的解說及資訊。

2.7 為什麼元件在設計時期會出現『I/O 103』的錯誤?

你可能在元件中使用了Writeln這個程式。

2.8 為什麼元件編輯器不會将元件屬性的變動儲存起來?

我發現有時自制的元件編輯器不會将元件屬性儲存起來。設計時期一切正常,但是儲存起來再重新讀入後就有問題了。

原因是你很可能忘了在元件編輯器中呼叫此方法:

Designer.Modified;

如此一來Delphi才會知道你的元件編輯器更改過屬性值了。

--------------------------------------------------------------------------------

第三部分在元件中使用其它元件

3.1 如何在元件中加入滾動條元件并讓它在設計時期能動作?

你的滾動條元件類别必須處理 CM_DESIGNHITTEST 元件訊息才行。

TMyScrollBar = class (TScrollBar)

procedure CMDesignHitTest

(var Message: TCMDesignHitTest); message CM_DESIGNHITTEST;

end;

procedure TMyScrollBar.CMDesignHitTest( var Message: TCMDesignHitTest);

begin

Message.Result := 1;

end;

你的元件必須以以下方法建立滾動條:

TMyScrollBar.Create(nil);

而不是

TMyScrollBar.Create(Self);

3.2 如何建立Windows95式樣的滾動條?

你必須設定滾動條的頁面大小。你可以用以下的程式代碼來做:

procedure SetPageSize(ScrollBar: TScrollBar; PageSize: Integer);

var

ScrollInfo: TScrollInfo;

begin

ScrollInfo.cbSize := Sizeof (ScrollInfo);

ScrollInfo.fMask := SIF_PAGE;

ScrollInfo.nPage := PageSize;

SetScrollInfo (ScrollBar.Handle, SB_CTL, ScrollInfo, True);

end;

要取得目前頁面大小可用如下方法:

function GetpageSize (ScrollBar: TScrollBar): Integer;

var

ScrollInfo: TScrollInfo;

begin

if HandleAllocated then

begin

ScrollInfo.cbSize := SizeOf (ScrollInfo);

ScrollInfo.fMask := SIF_PAGE;

GetScrollInfo (ScrollBar.Handle, SB_CTL, ScrollInfo);

Result := ScrollInfo.nPage;

end;

end;

--------------------------------------------------------------------------------

第四部分 Bound Controls

4.1 哪裡可以找得到有關 TDataLink 類别的說明檔案?

我可以大膽地說全世界有關 TDataLink 的說明檔案隻有一份,就在這兒:

屬性 (Property) 介紹

property Active: Boolean(隻讀)

當此 DataLink 連結至一個已開啟的 DataSource 時會傳回 True。當 Active 狀态改變時會 觸發ActiveChanged方法。

property ActiveRecord: Integer(可擦寫)

用來設定或取得 DataLink 緩沖區中目前所指向的記錄代碼,代碼的範圍是 0 .. BufferCount - 1。使用它來設定記錄代碼時必須小心不要超過這個範圍,否則可能導緻不可預期的錯誤。

property BufferCount: Integer(可擦寫)

DataLink 擁有一個資料緩沖區。而 BufferCount 屬性即用來設定或取得緩沖區大小,緩沖區大小決定了一個dataset同時可以顯視的資料記錄筆數。對大部分的資料感覺元件來說,BufferCount 的值是 1;但對 TDataGrid 來說,BufferCount 代表它的可視列數目。

property DataSet: TDataSet(隻讀)

傳回此 DataLink 所連結的 DataSet。其實就是 DataSource.DataSet。

property DataSource: TDataSource(可擦寫)

傳回此DataLink所連結的DataSource。

property DataSourceFixed: Boolean(可擦寫)

這個屬性可用來防止 DataSource 屬性被更改。如果此屬性設為 True,當我們試着改變 DataSource 屬性時會引發一個例外。

property Editing: Boolean(隻讀)

如果 DataLink 正處于編輯狀态則傳回 True。

property ReadOnly: Boolean(可擦寫)

設定 DataLink 是否為隻讀狀态。這個屬性并不會影響所連結的 DataSet。在隻讀狀态下這個 DataLink 無法進入編輯狀态。

property RecordCount: Integer(隻讀)

傳回DataSet的資料記錄數目。

方法 (Method) 介紹

function Edit: Boolean;

讓所連結的DataSet進入編輯狀态。傳回值: 成功傳回 True ,失敗傳回 False

procedure UpdateRecord;

我們不直接呼叫這個方法,它是提供其它程式來呼叫的。這個方法隻有設定一個旗幟然後呼叫 UpdateData 方法。

虛拟方法 ( Virtual Method )

要讓 TDataLink 對象與元件溝通必須改寫下列這些方法:

procedure ActiveChanged

當連結的 DataSource 開啟狀态改變時會呼叫此方法。使用 Active 屬性可以得知目前是否為開啟狀态。

procedure CheckBrowseMode

資料庫有任何改變之後都會先呼叫這個方法。

procedure DataSetChanged;

當下列任一事件發生時都會呼叫此方法:

移至DataSet的開頭

移至DataSet的結尾

在DataSet中插入或新增資料

删除DataSet的資料

取消DataSet的編輯

更新記錄

如果不想改寫這個方法隻要在其中呼叫:

RecordChanged(nil);

procedure DataSetScrolled(Distance: Integer)

每當目前記錄變更時會呼叫此方法。Distance 參數代表緩沖區欲卷動的行數。(其值範圍皆在 -1 .. 1 之間)。使用 ActiveRecord 屬性可以取得緩沖區中目前所指向的記錄。我們無法強制讓 DataLink 的緩沖區卷動。

procedure FocusControl(Field: TFieldRef)

與TField.FocusControl方法相同。

procedure EditingChanged

當 DataLink 的編輯狀态改變時會呼叫此方法。使用 Editing 屬性可以得知DataLink 是否 正處于編輯狀态。

procedure LayoutChanged

當 DataSet 的 Layout 改變時會呼叫此方法(例如新增一個column)。

procedure RecordChanged(Field: TField)

當下列任一事件發生時都會呼叫此方法:

目前記錄進入編輯狀态

目前記錄内容更動

procedure UpdateData

在一筆記錄被更新以前會呼叫此方法。你可以呼叫 Abort 程式來防止資料庫更新。

4.2 如何得知一個 dataset 中有幾筆記錄?

TDateSet 的 RecNo 屬性可以傳回資料記錄的數目,但很不幸地它隻适用于 dBase 及 Paradox 的資料表格。若想得知目前資料記錄的編号,可以從 TDataLink 類别衍生一個新的類别,然後進行下 列步驟:

改寫 DataSetScrolled 方法以取得目前記錄是否被卷動。

改寫 DataSetChanged 方法來确認目前記錄是否跳至最前面或最後面了。

接着你可以将這個新類别的對象連結到TDataSource對象上然後就可以随時得知目前的記錄編号了。

--------------------------------------------------------------------------------

第五部分 VCL

5.1 使用整合環境除錯時如何追蹤檢視 VCL元件的程式代碼?

将你想要追蹤的 VCL 原始程式單元拷貝至存放項目的目錄中并重新編譯元件庫,此後你就可以在那些 VCL單元中追蹤檢視程式代碼了。

5.2 我的元件參考到其它元件,如何得到參考元件被消滅的訊息?

Max Nilson 的回答:

TComponent 類别提供了 Notification 方法。當一個元件被移除時我們可以利用這個方法得到消息以進行适當的反應。你可以參考『Component Writer's Guide』内有關 Notification 及FreeNotification 這兩個方法的說明。

當你的元件參考到另一個元件,例如,你的元件中有一個 TDataSource 型态的屬性。那你必須改寫此元件的 Notification 方法,在其中檢查被移除的元件是否就是本身所參考的元件。預設情況下,當元件被移除時,所有其它在同一個表格上的元件才會收到消息,如果參考元件位于另一個表格上時,你的元件無法得知這件事情。Delphi 2.0 推出了TDataModule,參考元件位于另一個表格上的機會大幅增加,是以你應該利用 FreeNotification 方法來确定當參考元件移除時,你一定可以得到消息。

如果你不改寫 Notification 方法來處理參考元件被移除的訊息,這會讓 Delphi整合環境陷入十分不穩定的狀态。它可能不會立刻當掉,但你也不能再正常地繼續其它工作了。

下面是一個範例,當你的元件參考其它元件時,千萬記得要做以下的處理:

TMyComponent = class (TComponent)

private

FDataSource: TDataSource;

procedure SetDataSource(Value: TDataSource);

protected

procedure Notification(AComponent: TComponent; Operation: TOperation); override;

published

property DataSource: TDataSource read FDataSource write SetDataSource;

end;

procedure TMyComponent.SetDataSource(Value: TDataSource);

begin

if Value <> FDataSource then

begin

FDataSource := Value;

// 告訴參考元件說,當它被移除時記得通知我一聲。

if FDataSource <> nil then FDataSource.FreeNotification(Self)

end;

end;

procedure TMyComponent.Notification(AComponent: TComponent; Operation:TOperation);

begin

inherited Notification(AComponent, Operation);

// 如果被移除的正是參考元件,把FDataSource字段清除。

if (Operation = opRemove) and (AComponent = FDataSource) then

FDataSource := nil

end;

5.3 什麼是元件訊息?

元件訊息是什麼?它十分類似Windows的視窗訊息,隻有一點不同:元件訊息隻适用于 VCL 元件;而視窗訊息可以用在系統内所有具有 window handle 的控件或視窗。如果你有一個具有 Font 屬性的元件(例如TLabel元件),當我們更改它的 Font 屬性時并沒有送出視窗訊息(譯注:TLabel 元件不是視窗控件,根本也沒有視窗 Handle可以讓我們傳送視窗訊息),但是控件仍然知道字型改變了是以要重畫自己,為什麼?因為我們有元件訊息。

元件訊息不可以由虛拟方法來處理,這可能是設計 VCL 時的考量,大概是因為不想讓虛拟方法表格(Virtual Method Table)過于龐大的原因。

『Secrets of Delphi 2.0』這本書對于所有的元件訊息有十分詳盡的解說。

接下來我們列出一些比較常見的元件訊息及它們的作用。标示着『Notification Only』 的訊息表示送出這個訊息隻是為了通知元件某件消息而己,并不傳入任何參數而且也不需要傳回值。

CM_ACTIVATE (Notification Only)

當表格成為焦點視窗時會傳給本身這個訊息。

CM_CTL3DCHANGED (Notification Only)

當控件的Ctl3D屬性更改時會傳給本身這個訊息。

CM_DESIGNHITTEST 參數:TCMDesignHitTest 傳回值:0或1

在設計時期當滑鼠移到元件上頭時,整合環境會送給此元件這個訊息。此訊息的目的用來決定元件在設計時期是否要處理滑鼠訊息。如果傳回值是 1,整合環境就讓元件自行處理滑鼠訊息;若傳回值是 0,則整合環境會幫你處理滑鼠訊息。如果傳回值永遠是 1,那麼元件的快速功能選單則永遠不會出現;如果元件不處理這個訊息或永遠傳回 0,那此元件在設計時期将無法對滑鼠訊息做任何反應。

CM_FONTCHANGED (Notification Only)

控件的字型改變後送給本身此訊息。

CM_FONTCHANGE (Notification Only)

當控件收到WM_FONTCHANGE視窗訊息時會送給本身這個訊息。

CM_PARENTCTL3DCHANGED (Notification Only)

當元件父控件的Ctl3D屬性改變或設定新的父控件時會收到此訊息。

CM_PARENTCOLORCHANGED (Notification Only)

當元件父控件的 Color 屬性改變或設定新的父控件時會收到此訊息。

CM_PARENTFONTCHANGED (Notification Only)

當元件父控件的Font屬性改變或設定新的父控件時會收到此訊息。

CM_PARENTSHOWHINTCHANGED (Notification Only)

當元件父控件的ShowHint屬性改變或設定新的父控件時會收到此訊息。

CM_WININICHANGE 參數:TWMWinIniChange 傳回值:無

當控件收到WM_WININICHANGE視窗訊息時會送給本身這個訊息。

5.4 我的元件得到輸入焦點後仍不能接受鍵盤訊息,為什麼?

如果你的元件有 DragMode 屬性而且将它設成 dmAutomatic 時,很有可能讓你的元件以為它正被拖曳但實際上并沒有的情況。在 Controls 單元中有一個區域變量 DragControl 指 向目前正被拖曳的元件。你遇到的情況很可能就是明明沒有拖曳的動作但是DragControl 變量卻指向你的元件。在 TWinControl 的 WndProc 方法中,當 DragControl 變量指向元件本身時,會忽略所有鍵盤訊息,這就是原因了!

--------------------------------------------------------------------------------

第六部分 其它資訊

6.1 有哪些書介紹或講解如何撰寫元件?

有關撰寫元件的『标準』參考書籍:

『Developing Delphi Components』 作者:Ray Konopka 出版:Coriolis Group

下面這本書并不專注于元件寫作,但裡面提到許多元件撰寫者不可不知的資訊:

『Secrets of Delphi 2』 作者:Ray Lischner 出版:Waite Group

另外一本元件撰寫的好書,它有許多在『Developing Delphi Components』裡找不到的資訊:

『Programming Delphi Custom Components』 作者:Fred Bulback 出版:M&T Books

6.2 有哪些Web站台可以取得撰寫元件的資訊?

全世界最大的 Delphi Web 站台『Delphi SuperPage』

(譯注:亞洲地區使用者可以就近到位于日本的 Mirror Site )

我在下面這些站台中找到許多元件的原始程式代碼:

Temple of Delphi

Delphi Free Stuff

(譯注:『Your Delphi Programming Resource』整理元件也十分用心! )

(譯注:台灣地區目前維持最好的 Delphi 站台是『32 Bit Delphi 深度曆險』及其 Mirror Site)

你也可以使用一些搜尋引擎來尋找有關 Delphi 的站台:

Yahoo

Alta Vista

(譯注:Excite 搜尋引擎也别錯過啰!)

--------------------------------------------------------------------------------

第七部分 元件的儲存與加載

7.1 如何将包含其它對象的對象一起存入 DFM 檔?

我試過許多方法,包括改寫元件的 DefineProperties及 WriteComponents方法,但都還是失敗了。是以我隻能說要解決這個問題的話隻有使用 Delphi 原本的方法一途。

将包含其它對象的對象一起儲存起來的步驟大緻如下:

确定你要儲存的所有對象都是從 TComponent 類别衍生下來的。

将所有需要儲存的變量宣告在 published 區段。

在元件的 Register 程式中呼叫 RegisterComponents程式來注冊所有你要儲存起來的類别。

每個擁有子控件的類别必須改寫 GetChildren 方法以儲存每個子控件。(在 Delphi 1.0 中你必須改寫 WriteComponents 方法并且為每個子控件呼叫 WriteComponent方法)。

将對象加載的方法用了點小技巧。你必須改寫元件的 GetChildOwner 及 GetChildParent 方法,否則 Delphi會将所有對象的擁有者都設定為元件所在的表格。(在Delphi 1.0 中你必須改寫ReadState方法)。

7.2 如何得知元件是否正從資料流中讀出?

當元件正從資料流中讀出時,它的 ComponentState 屬性會包含csLoading 旗幟。

constructor TMyClass.Create(AOwner: TComponent);

begin

if csLoading in AOwner.ComponentState then

begin ... end

else

begin ... end;

end;

7.3 如何确定元件的屬性是否被正确地儲存?

有許多很簡單的方法可以驗證屬性是否被正确地儲存在檔案裡:

在整合環境中用滑鼠右鍵點選表格然後選擇『View as Text』。然而萬一 DFM 檔 有任何錯誤的話,你将什麼也看不到。

開個 DOS 視窗,利用 Delphi 所附的『Convert』程式将 DFM 檔案轉成文字格式。

Stefan Hoffmeister 指出複制或剪下元件後,到任何一個文書編輯器(如記事本)中貼上,你就可以看到此元件的文字表示。你甚至可以編輯這些文字表示後再将它貼回 Delphi整合環境的表格上。

--------------------------------------------------------------------------------

第八部分 Delphi 的工具

8.1 有沒有Delphi版本的 YACC 及 LEX?

有。Albert Graef 這位仁兄寫了 Turbo Pascal 版本的 YACC 及 LEX,也可以讓 Delphi 使用。

你可以在 ftp://ftp.simtel.net/pub/simtelnet/msdos/turbopas 下取得 tply30a1.zip 及 tply30a2.zip 這兩個檔案,其中還包含這兩個工具的原始程式哦!

8.2 如何秀出 JPEG 格式圖形檔案?

Jacques Nomssi Nzali 将 Independent JPEG Group 所發展的免費 JPEG 連結庫改寫成 Pascal 版本。你可以從下取得:

PASJPG10.ZIP

Independent JPEG Group 的免費 JPEG 函式庫 rev 6a 之 Pascal 版本 (1.0 版)。

--------------------------------------------------------------------------------

第九部分 基本程式設計技巧

9.1 如何建立不定數目的對象數組?

最簡單的方法是使用 TList 類别。我發現從 TList 衍生一個新類别很有用處。接下來的程式代碼示範如何為一個特定型态撰寫一個特别的 TList 類别,并且加進基本的錯誤檢查。

TListOfMyObject = class (TList)

private

function GetItems(Index: Ordinal): TMyObject;

public

property Items[Index: Ordinal]: TMyObject read GetItems;

procedure Add(AObject: TMyObject);

end;

function TListOfMyObject.GetItems (Index: Ordinal): TMyObject;

begin

if Index >= Count then

raise Exception.CreateFmt('Index(%d) outside range 1..%d', [Index, Count-1]);

Result := inherited Items[Index];

end;

procedure TListOfMyObject.Add (AObject: TmyObject);

begin

inherited Add(AObject);

end;

9.2 Delphi 2.0的 WinCrt單元到哪去了?

Delphi 2.0并沒有 WinCrt單元。 先别傷心,這是因為我們可以用其它方法來取代它。在 Project|Options 的 Linker 頁次中将『Generate console application』選項打開,你就可以像以前使用 WinCrt 單元一樣地寫程式了!

9.3 自制元件時該從哪個類别繼承?

VCL 中有一些『自訂』類别,而且有許多控件是直接由這些『自訂』類别繼承下來的。例如 TMemo 直接繼承自 TCustomMemo類别。這些自訂類别寫好了所有該控件所擁有的功能,隻是沒有将屬性公開出來而己。大部分情形下,你應該從那些自訂類别繼承而不是控件類别。

如果你要從頭撰寫自己的元件,那麼從 TCustomControl 類别繼承是個不錯的主意。撰寫出來的元件會具有 Window Handle 且可以接受輸入焦點。

另外根據你的需要也可以從這些類别繼承:

TGraphicControl:視覺元件,但是沒有window handle,也不能接受輸入焦點。

TComponent:不可視元件,你沒辦法在執行時期看到它。

TWinControl:将已存在的視窗元件包裝起來,如Windows标準控件或VBX元件。

-------------------------------------------------------------------------------

第十部分 進階程式設計技巧

10.1 Delphi 有與 C++ 一樣的 I/O Stream 類别嗎?

答案可以說有也可以說沒有。Delphi允許你建立自己的『文字檔案驅動程式』,它可以讓你使用Delphi 标準的 I/O 函式庫來處理非标準的 I/O,如處理 UNIX 格式的文字檔案或處理 Socket 所取得的資料。雖然沒有像 C++ 的 I/O Stream 類别那麼強大但應該也足夠一般用途使用了。

建立『文字檔案驅動程式』的方法在『Object Pascal Language Guide』中有明述。此 外你也可以參考 VCL 的 Printer 單元。

Delphi有 TStream 類别,不過是設計用來将對象寫入資料流的,不像 C++ 的 I/O Stream 類别那麼具有彈性。

10.2 如何取得列舉型态變量的文字表示?

使用 TypInfo單元中的 GetEnumName 函式:

type

TMyType = (Value1, Value2);

var

TypeValue: TMyType;

begin

Writeln (GetEnumName(TypeInfo(TMyType), Ord(TypeValue));

end;

TypInfo單元中還有許多與型别資訊有關的函式。

『Secrets of Delphi 2.0』這本書有許多關于TypInfo單元的資訊,值得參考。

--------------------------------------------------------------------------------

第十一部分 元件虛拟方法

11.1 如何得知元件的window handle是何時建立的?

控件的 window handle 是在 CreateWnd 方法中建立的。如果你想要在建立 window handle 後接着做某些動作那麼你應該改寫 CreateWnd 方法:

procedure TMyClass.CreateWnd;

begin

// 現在還沒取得 window handle

inherited CreateWnd;

// 呼叫 inherited 以取得 window handle

// 在這裡撰寫你想要執行的動作

end;

11.2 如何得知是否表格上所有元件都已加載完成?

Loaded 方法是在加載完成後接着被呼叫的。

procedure TMyClass.Loaded;

begin

inherited Loaded;

// 将ComponentState中的 csLoading 狀态清除

// 在這裡撰寫你想要執行的動作

end;

11.3 在哪裡繪制元件最适合?

你應該攔截 WM_PAINT 視窗訊息然後利用 Canvas 來繪制元件。然而 VCL 己經幫你攔 截好了,你隻須改寫元件的 Paint 方法即可。

procedure TMyClass.Paint;

begin

// 如果你的元件是己存在的元件繼承下來的,那麼必須在這裡呼叫 inherited Paint

inherited Paint ;

// 在這裡撰寫你想要執行的動作

end;

11.4 如何改變元件的視窗式樣?

CreateParams方法用來設定元件的視窗式樣及其它必須傳遞至 CreateWindowEx API 的 參數。要改變元件的視窗式樣,例如增加或拿掉元件的垂直滾動條隻要改寫 CreateParams 方法:

procedure TMyControl.CreateParams(var Params: TCreateParams);

begin

inherited CreateParams(Params);

if IWantAScrollBar then

Params.Style := Params.Style or WS_VSCROLL

else

Params.Style := Params.Style and not WS_VSCROLL;

end;

--------------------------------------------------------------------------------

第十二部分 Windows API

12.1 元件卷動時閃動的很厲害,如何克服這種情況?

要卷動元件本身最簡單的方法就是改變它的坐标然後重畫元件,但是這方法會導緻元件閃動的很厲害。

比較好的方法是呼叫 ScrollWindow 或 ScrollWindowEx Windows API。

閃動的另一個原因可能來自于 WM_PAINT 及 WM_ERASEBKGND。你可以試着攔截 WM_ERASEBKGND 及 WM_PAINT 訊息然後自己處理繪圖動作,包括繪制背景的動作,或許可以改善閃動的情況。

12.2 如何重新激活Windows?

使用 ExitWindowsEx Windows API。

12.3 如何快速大量地更改元件資料?

在進行大量資料更改前後,利用 WM_SETREDRAW 訊息來控制你的元件暫時不要重畫,這不但可以使資料設定速度增快也防止元件閃爍的情況。

--------------------------------------------------------------------------------

第十三部分 控件邊框

13.1 為什麼我的元件的 Ctl3D 屬性設為 True 之後,它依然沒有 3D 的邊框呢?

如果 ControlStyle 屬性内沒有包含 csFramed 旗幟那麼 Ctl3D 屬性就會沒有作用。在元件 的建構函式内加上:

ControlStyle := ControlStyle + [csFramed];

13.2 如何實作 BorderStyle 屬性?

在控件設定有沒有邊框之後要重建立立 window handle:

FBorderStyle: TBorderStyle;

procedure SetBorderStyle(Style: TBorderStyle);

property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle;

procedure CreateParams(var Params: TCreateParams); override;

procedure TMyControl.CreateParams(var Params: TCreateParams);

begin

inherited CreateParams(Params);

if FBorderStyle = bsSingle then

Params.Style := Params.Style or WS_BORDER

else

Params.Style := Params.Style and not WS_BORDER;

end;

procedure TMyControl.SetBorderStyle(Style: TBorderStyle);

begin

if Style <> FBorderStyle then

begin

FBorderStyle := Style;

// 重建立立window handle

RecreateWnd;

end;

end;

--------------------------------------------------------------------------------

第十四部分 控件式樣

14.1 當元件重繪時如何防止閃動的情況?

如果元件的 ComponentStyle 屬性沒有包含 csOpaque 旗幟的話,呼叫 Invalidate方法時 會導緻元件的背景先被擦掉再重繪。如果你在 Paint 方法中繪制背景,那你應該在元件的建構函式中加上:

ComponentStyle := ComponentStyle + [csOpaque];

Max Nilson的回答:

引起閃動另一個原因可能是 WM_ERASEBKGND 訊息的處理。當 VCL 控件收到一個 WM_ERASEBKGND 訊息時,它會将元件的背景擦掉然後設定成預設的顔色。如果你的元件衍生自 TWinControl,而且元件的顔色與背景顔色不同(例如圖形),每次重畫以前都會将元件先清成背景顔色再重繪,這就是造成閃動的原因了!

解決的方法不難,你必須告訴 Windows 你要自行解決『所有的』繪圖動作。不過有一個前提是,你一定要确定你的 Paint 方法将整個元件都畫過,如果你漏了什麼地方忘了畫,那個部分的資料會由随機數組成,你能想見這情況嗎?使用這個方法可以加速你的元件繪制動作(稍微快一點點),因為少了一個填滿背景顔色的動作。

type

TMyComponent = class (TWinControl)

...

protected

procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;

...

end;

procedure TBMyComponent.WMEraseBkgnd(var Message: TWMEraseBkgnd);

begin

// 不要重繪背景,這會造成元件閃動

Message.Result := 0

end;

--------------------------------------------------------------------------------

第十五部分 視窗訊息

15.1 為什麼我的元件得不到方向鍵的訊息?

你必須攔截 WM_GETDLGCODE 才能處理方向鍵的訊息,在 WM_GETDLGCODE 的訊息處理 者中傳回 DLGC_WANTARROWS。如果你不這樣做,那方向鍵的功用就隻能用來移動視窗焦點而己。

Max Nilson 的回答:

想要你的元件能夠處理方向鍵,你必須要攔截 CM_WANTSPECIALKEY 元件訊息。 CM_WANTSPECIALKEY 元件訊息提供你比攔截 WM_GETDLGCODE 視窗訊息更容易且靈活的判斷方法來決定是否需要某些特殊鍵的訊息。當控件收到任何一個特殊鍵時就會送出CM_WANTSPECIALKEY 元件訊息給控件。

特殊鍵包括:VK_TAB、VK_LEFT、VK_RIGHT、VK_UP、VK_DOWN、VK_RETURN、VK_EXECUTE 、VK_ESCAPE 及 VK_CANCEL。如果訊息傳回值是非零值,這個鍵就會被送至 KeyPress 方法以供處理,否則這個鍵的訊息會被送至元件的父控件,以預設方式來處理。

一個簡單的範例:

type

TMyComponent = class (TWinControl)

...

protected

procedure CMWantSpecialKey(var Message: TCMWantSpecialKey); message CM_WANTSPECIALKEY;

...

end;

procedure TMyComponent.CMWantSpecialKey(var Message: TCMWantSpecialKey); begin

inherited;

// 我們隻想處理向左方向鍵,其它的特殊鍵都給 Windows 處理

if Message.CharCode = VK_LEFT then

Message.Result := 1;

end;

CM_WANTSPECIALKEY 元件訊息比 WM_GETDLGCODE 訊息更具有彈性的地方在這兒。我們甚至可以根據是按下的是哪個特殊鍵才決定是否處理這個鍵。例如,你的控件有三張影像,你可以讓使用者利用左右方向鍵來回檢視它們,如果翻到最後一張影像再按向右鍵時,焦點就讓它離開元件,剩下的全部都讓 Delphi 來處理。

15.2 有沒有與 Visual Basic『DoEvents』同樣功能的函式?

有。Application.ProcessMessages方法。

繼續閱讀