1.VCL組件開發的幾個主要基類TComponent、TControl、TWinControl、TGraphicControl、TCustomControl的主要功能及各類用途?
TComponent:因封裝了設計期的一些信息而使得可以在設計時使用,當然要在設計時使用還必須註冊到Delphi的IDE控件面板,所以TComponent是所有可以放在控件面板上的類的基類。
實現了IInterface, IInterfaceComponentReference接口。
TControl:顧名思義,這才是真正的Delphi的"控件"。我們常說的Delphi控件應該就是從它來繼承,它是所有可視類的基類,可以在運行時看見。它封裝了幾封了所有的鼠標消息和部份界面的消息、外殼消息。
另外最重要的是它填充裝了所有的消息分發。
TWinControl:當然它就是窗口控件,對應著VC裡的窗口類(我不太懂VC),也是在Windows中的具有唯一標識Handle的類,所有容器類和可獲得焦點的類都是從它上面繼承。
作為窗口控件,它當然封裝了所有的窗口消息,比如鍵盤消息。
TGraphicControl:從TControl上繼承下來,沒有窗口句柄,但是增加了畫布Canvas,通過Canvas封裝了主要的圖形繪製API函數。
TCustomControl:類似於TGraphicControl,不過它是從TWinControl繼承下來的,有了窗口句柄.
它和TGraphicControl一樣,只處理了WM_PAINT消息。
2.ComponentState屬性的用途
用於判斷TComponent繼承下來的類的狀態,比如是否是設計期或者裝載期等等。
3.如果有一個屬性為指向另一個類的實例(如TDataSource的Dataset屬性), 要注意的事項
我想最重要應該是在寫接品方法中調用另一個類實例的FreeNotification方法,經以便在另一個類實例析構的時候清空本類實例的相關屬性(將相關成員字段設為nil)。
4.property宣告的具體心得及注意事項、Keyword Default及Stored的意義
這方面的東東其實主要看個人喜好,我的感覺在宣告一個屬性時應儘可能避免這個屬性可以不能過寫接口而改寫,那樣可能為這個屬性改變量所作的努力將不起作用,
比如動態數組類型的屬性:最好不要聲時動態數組類型的屬性,因為在Delphi中動態數組類型的數組其實和指針差不多,它可以直接操作這個屬性而不會調用寫接口,除非你用賦值符號為動態數組賦值(這在Delphi中應儘量避免使用),
如果確實有動態數組類型的屬性,可以通過定義屬性下標來將其轉換成簡單類型或復合類型的屬性
而一些類屬性,一般在寫接口中需要使用Assign方法來賦值,比如大多數可視控件的Font屬性,或部份控件的TPicture類型屬性,而且這樣的屬 性因為本身有Assign方法,可能不會使用賦值運算為其賦值(在Delphi中應儘量避免使用賦值運算對類實例賦值,
除非真的是要兩個實例變量指向同一個實例),所有一般不僅需要處理寫接品,還需要給這個屬性所對應的成員字段定義OnChange事件。
至於Default指示,不用看樓上也具定說得很多,關鍵是要注意,它並不對屬性對應的私有成員初始化,如果需要初始化,則需要在構造函數中進行。
它僅僅用來指示屬性是否需要被流化以保存在dfm文件中,編譯以資源的形式進入EXE,可以有效的減小EXE文件的容量,並提示程序加載速度。
Stored指示的作用與Default指示字相仿,不過Default只是指定一個值,如果屬性值在設計期間與這個值相同,就不流化,而且Default只支持有限的幾種簡單定長類型數據。
而Stored而可以通過一個Boolean函數來檢查設計期間的屬性值是否需要流化,這個函數可以非常複雜。
5.TControl與TWinControl的差異
TControl的和TWinControl其實在上面已經比較清楚了,主要就是一一個是父類,一個是子類,TWinControl比TControl增加了Handle和相關的方法和屬性,以用相應的消息處理函數,
最主要的是,除是多了句柄之外,因為可以獲得焦點,所以多了鍵盤相關的消息和方法。
6.TWinControl中如何封裝Windows窗體(或者說, Windows的消息通知(message call), 經過了什麼過程, 成為Delphi的Method Call) ? Windows的消息處理機制?
或者另一個問法, 在一個Control中, 要處理消息(message), 除了定義消息函數 (procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;) 外, 尚有那些方式?
這個可複雜了點,首先,TControl有於個屬性WindowProc,其實它是一個事件,只不過Delphi中的所有類都沒有把它放到published域,所以我們從沒有在設計期間看到這個屬性,
但是它的確存在,而且作用非常大,在TControl中的構造函數中,在重用了父類的構造代碼後的第一行就是給這個事件賦了值,這個值是成員方法WndProc,這是一個動態方法,
可以重載,它完成了幾乎所有鼠標消息和界面消息的分發.我們如果需要自己來處理這些分發,一種方法可以重載這個方法,另一種是自己定WindowProc事件句柄。
不過我便能願意重載一些。其實到這裡就可以明白,除了重載消息處理函數外,我們也可以載重WndProc方法來處理消息和重寫WindowProc事件來處理消息。共至少有三種方法。
其他程序向Delphi編譯的程序的窗口發的消息Delphi是如何能接收到的?請看CreateWnd方法中有一句 WindowClass.lpfnWndProc := @InitWndProc;就是句告訴Windows這個窗口的消息由InitWndProc這個回調函數來處理,
而在InitWndProc這個函數中的一段彙編把它和TWinControl.FObjectInstance聯繫起來,再轉到 TWinControl.Create(AOwner: TComponent)中,第二行的FObjectInstance := Classes.MakeObjectInstance(MainWndProc);
才真正引出主角MainWndProc,它個函數很簡單,就是觸發WindowProc事件,把接收到的消息傳給這個事件,再由個這事件來分發。好了說了一大通,但是太亂,可能不會有太多人能明白。
好在我們一般情況下不需要關心這些,只需要知道處理消息的三種方法就行了。
7.設計覆合式控件的注意事項
說得簡單點,就是注意誰是容器,誰是子對象就行了,在構告的時候把子對象的Parent屬性和Owner設為容器,設計時就不會出現兩個或者多個可操作縱的對象(而是只有一個唯一的對象可供用戶操作)。
第一部分 簡介
1.1 此份文檔的目的為何?
第二部份 整合環境
2.1 在整合環境中如何找出構件所產生的問題?
2.2 如何檢視Delphi所產生的彙編碼?
2.3 我可以在運行時刻動態建立構件,但在設計時刻就會發生錯誤。為什麼?
2.4 如何撰寫一個無法放置到表格上的構件?
2.5 在程序碼編輯器中快速切換程序區段最簡單的方法是什麼?
2.6 如何使我的構件在按下鼠標右鍵時出現快速功能選單?
2.7 為什麼構件在設計時刻會出現『I/O 103』的錯誤?
2.8 為什麼構件編輯器不會將構件屬性的變動儲存起來?
第三部分 在構件中使用其它構件
3.1 如何在構件中加入捲軸構件並讓它在設計時刻能動作?
3.2 如何建立Windows95式樣的捲軸?
第四部分 Bound Controls
4.1 哪裡可以找得到有關TDataLink類的說明文檔?
4.2 如何得知一個dataset中有幾筆記錄?
第五部分 VCL
5.1 使用整合環境調試時如何追蹤檢視VCL構件的程序碼?
5.2 我的構件參考到其它構件,如何得到參考構件被消滅的訊息?
5.3 什麼是構件訊息?
5.4 我的構件得到輸入焦點後仍不能接受鍵盤訊息,為什麼?
第六部分 其它信息
6.1 有哪些書介紹或講解如何撰寫構件?
6.2 有哪些Web站台可以取得撰寫構件的信息?
第七部分 構件的儲存與載入
7.1 如何將包含其它對象的對象一起存入DFM檔?
7.2 如何得知構件是否正從數據流中讀出?
7.3 如何確定構件的屬性是否被正確地儲存?
第八部分 工具程序
8.1 有沒有Delphi版本的 YACC 及 LEX?
8.2 如何秀出JPEG格式圖形檔?
第九部分 基本編程技巧
9.1 如何建立不定數目的對象數組?
9.2 Delphi 2.0的 WinCrt 單元到哪去了?
9.3 自制構件時該從哪個類繼承?
第十部分 進階編程技巧
10.1 Delphi有與 C++ 一樣的 I/O Stream 類嗎?
10.2 如何取得列舉型態變量的文字表示?
第十一部分 構件虛擬方法
11.1 如何得知構件的 window handle 是何時建立的?
11.2 如何得知是否表格上所有構件都已載入完成?
11.3 在哪裡繪製構件最適合?
11.4 如何改變構件的窗口式樣?
第十二部分 Windows API
12.1 構件捲動時閃動的很厲害,如何克服這種情況?
12.2 如何重新啟動Windows?
12.3 如何快速大量地更改構件數據?
第十三部分 控制項邊框
13.1 為什麼我的構件的 Ctl3D 屬性設為 True 之後,它依然沒有 3D 的邊框呢?
13.2 如何實作 BorderStyle 屬性?
第十四部分 控制項式樣
14.1 當構件重繪時如何防止閃動的情況?
第十五部分 窗口消息
15.1 為什麼我的構件得不到方向鍵的訊息?
15.2 有沒有與 Visual Basic『DoEvents'同樣功能的函式?
--------------------------------------------------------------------------------
第一部分 簡介
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_USER2.0』下新增一個字符串機碼『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整合環境的表格上。
--------------------------------------------------------------------------------
TComponent:因封裝了設計期的一些信息而使得可以在設計時使用,當然要在設計時使用還必須註冊到Delphi的IDE控件面板,所以TComponent是所有可以放在控件面板上的類的基類。
實現了IInterface, IInterfaceComponentReference接口。
TControl:顧名思義,這才是真正的Delphi的"控件"。我們常說的Delphi控件應該就是從它來繼承,它是所有可視類的基類,可以在運行時看見。它封裝了幾封了所有的鼠標消息和部份界面的消息、外殼消息。
另外最重要的是它填充裝了所有的消息分發。
TWinControl:當然它就是窗口控件,對應著VC裡的窗口類(我不太懂VC),也是在Windows中的具有唯一標識Handle的類,所有容器類和可獲得焦點的類都是從它上面繼承。
作為窗口控件,它當然封裝了所有的窗口消息,比如鍵盤消息。
TGraphicControl:從TControl上繼承下來,沒有窗口句柄,但是增加了畫布Canvas,通過Canvas封裝了主要的圖形繪製API函數。
TCustomControl:類似於TGraphicControl,不過它是從TWinControl繼承下來的,有了窗口句柄.
它和TGraphicControl一樣,只處理了WM_PAINT消息。
2.ComponentState屬性的用途
用於判斷TComponent繼承下來的類的狀態,比如是否是設計期或者裝載期等等。
3.如果有一個屬性為指向另一個類的實例(如TDataSource的Dataset屬性), 要注意的事項
我想最重要應該是在寫接品方法中調用另一個類實例的FreeNotification方法,經以便在另一個類實例析構的時候清空本類實例的相關屬性(將相關成員字段設為nil)。
4.property宣告的具體心得及注意事項、Keyword Default及Stored的意義
這方面的東東其實主要看個人喜好,我的感覺在宣告一個屬性時應儘可能避免這個屬性可以不能過寫接口而改寫,那樣可能為這個屬性改變量所作的努力將不起作用,
比如動態數組類型的屬性:最好不要聲時動態數組類型的屬性,因為在Delphi中動態數組類型的數組其實和指針差不多,它可以直接操作這個屬性而不會調用寫接口,除非你用賦值符號為動態數組賦值(這在Delphi中應儘量避免使用),
如果確實有動態數組類型的屬性,可以通過定義屬性下標來將其轉換成簡單類型或復合類型的屬性
而一些類屬性,一般在寫接口中需要使用Assign方法來賦值,比如大多數可視控件的Font屬性,或部份控件的TPicture類型屬性,而且這樣的屬 性因為本身有Assign方法,可能不會使用賦值運算為其賦值(在Delphi中應儘量避免使用賦值運算對類實例賦值,
除非真的是要兩個實例變量指向同一個實例),所有一般不僅需要處理寫接品,還需要給這個屬性所對應的成員字段定義OnChange事件。
至於Default指示,不用看樓上也具定說得很多,關鍵是要注意,它並不對屬性對應的私有成員初始化,如果需要初始化,則需要在構造函數中進行。
它僅僅用來指示屬性是否需要被流化以保存在dfm文件中,編譯以資源的形式進入EXE,可以有效的減小EXE文件的容量,並提示程序加載速度。
Stored指示的作用與Default指示字相仿,不過Default只是指定一個值,如果屬性值在設計期間與這個值相同,就不流化,而且Default只支持有限的幾種簡單定長類型數據。
而Stored而可以通過一個Boolean函數來檢查設計期間的屬性值是否需要流化,這個函數可以非常複雜。
5.TControl與TWinControl的差異
TControl的和TWinControl其實在上面已經比較清楚了,主要就是一一個是父類,一個是子類,TWinControl比TControl增加了Handle和相關的方法和屬性,以用相應的消息處理函數,
最主要的是,除是多了句柄之外,因為可以獲得焦點,所以多了鍵盤相關的消息和方法。
6.TWinControl中如何封裝Windows窗體(或者說, Windows的消息通知(message call), 經過了什麼過程, 成為Delphi的Method Call) ? Windows的消息處理機制?
或者另一個問法, 在一個Control中, 要處理消息(message), 除了定義消息函數 (procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;) 外, 尚有那些方式?
這個可複雜了點,首先,TControl有於個屬性WindowProc,其實它是一個事件,只不過Delphi中的所有類都沒有把它放到published域,所以我們從沒有在設計期間看到這個屬性,
但是它的確存在,而且作用非常大,在TControl中的構造函數中,在重用了父類的構造代碼後的第一行就是給這個事件賦了值,這個值是成員方法WndProc,這是一個動態方法,
可以重載,它完成了幾乎所有鼠標消息和界面消息的分發.我們如果需要自己來處理這些分發,一種方法可以重載這個方法,另一種是自己定WindowProc事件句柄。
不過我便能願意重載一些。其實到這裡就可以明白,除了重載消息處理函數外,我們也可以載重WndProc方法來處理消息和重寫WindowProc事件來處理消息。共至少有三種方法。
其他程序向Delphi編譯的程序的窗口發的消息Delphi是如何能接收到的?請看CreateWnd方法中有一句 WindowClass.lpfnWndProc := @InitWndProc;就是句告訴Windows這個窗口的消息由InitWndProc這個回調函數來處理,
而在InitWndProc這個函數中的一段彙編把它和TWinControl.FObjectInstance聯繫起來,再轉到 TWinControl.Create(AOwner: TComponent)中,第二行的FObjectInstance := Classes.MakeObjectInstance(MainWndProc);
才真正引出主角MainWndProc,它個函數很簡單,就是觸發WindowProc事件,把接收到的消息傳給這個事件,再由個這事件來分發。好了說了一大通,但是太亂,可能不會有太多人能明白。
好在我們一般情況下不需要關心這些,只需要知道處理消息的三種方法就行了。
7.設計覆合式控件的注意事項
說得簡單點,就是注意誰是容器,誰是子對象就行了,在構告的時候把子對象的Parent屬性和Owner設為容器,設計時就不會出現兩個或者多個可操作縱的對象(而是只有一個唯一的對象可供用戶操作)。
第一部分 簡介
1.1 此份文檔的目的為何?
第二部份 整合環境
2.1 在整合環境中如何找出構件所產生的問題?
2.2 如何檢視Delphi所產生的彙編碼?
2.3 我可以在運行時刻動態建立構件,但在設計時刻就會發生錯誤。為什麼?
2.4 如何撰寫一個無法放置到表格上的構件?
2.5 在程序碼編輯器中快速切換程序區段最簡單的方法是什麼?
2.6 如何使我的構件在按下鼠標右鍵時出現快速功能選單?
2.7 為什麼構件在設計時刻會出現『I/O 103』的錯誤?
2.8 為什麼構件編輯器不會將構件屬性的變動儲存起來?
第三部分 在構件中使用其它構件
3.1 如何在構件中加入捲軸構件並讓它在設計時刻能動作?
3.2 如何建立Windows95式樣的捲軸?
第四部分 Bound Controls
4.1 哪裡可以找得到有關TDataLink類的說明文檔?
4.2 如何得知一個dataset中有幾筆記錄?
第五部分 VCL
5.1 使用整合環境調試時如何追蹤檢視VCL構件的程序碼?
5.2 我的構件參考到其它構件,如何得到參考構件被消滅的訊息?
5.3 什麼是構件訊息?
5.4 我的構件得到輸入焦點後仍不能接受鍵盤訊息,為什麼?
第六部分 其它信息
6.1 有哪些書介紹或講解如何撰寫構件?
6.2 有哪些Web站台可以取得撰寫構件的信息?
第七部分 構件的儲存與載入
7.1 如何將包含其它對象的對象一起存入DFM檔?
7.2 如何得知構件是否正從數據流中讀出?
7.3 如何確定構件的屬性是否被正確地儲存?
第八部分 工具程序
8.1 有沒有Delphi版本的 YACC 及 LEX?
8.2 如何秀出JPEG格式圖形檔?
第九部分 基本編程技巧
9.1 如何建立不定數目的對象數組?
9.2 Delphi 2.0的 WinCrt 單元到哪去了?
9.3 自制構件時該從哪個類繼承?
第十部分 進階編程技巧
10.1 Delphi有與 C++ 一樣的 I/O Stream 類嗎?
10.2 如何取得列舉型態變量的文字表示?
第十一部分 構件虛擬方法
11.1 如何得知構件的 window handle 是何時建立的?
11.2 如何得知是否表格上所有構件都已載入完成?
11.3 在哪裡繪製構件最適合?
11.4 如何改變構件的窗口式樣?
第十二部分 Windows API
12.1 構件捲動時閃動的很厲害,如何克服這種情況?
12.2 如何重新啟動Windows?
12.3 如何快速大量地更改構件數據?
第十三部分 控制項邊框
13.1 為什麼我的構件的 Ctl3D 屬性設為 True 之後,它依然沒有 3D 的邊框呢?
13.2 如何實作 BorderStyle 屬性?
第十四部分 控制項式樣
14.1 當構件重繪時如何防止閃動的情況?
第十五部分 窗口消息
15.1 為什麼我的構件得不到方向鍵的訊息?
15.2 有沒有與 Visual Basic『DoEvents'同樣功能的函式?
--------------------------------------------------------------------------------
第一部分 簡介
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_USER2.0』下新增一個字符串機碼『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整合環境的表格上。
--------------------------------------------------------------------------------
請先 登入 以發表留言。