《Mastering Delphi6》学习笔记下Word文档格式.docx
《《Mastering Delphi6》学习笔记下Word文档格式.docx》由会员分享,可在线阅读,更多相关《《Mastering Delphi6》学习笔记下Word文档格式.docx(13页珍藏版)》请在冰点文库上搜索。
![《Mastering Delphi6》学习笔记下Word文档格式.docx](https://file1.bingdoc.com/fileroot1/2023-5/6/dbc9c879-5c7b-4779-80ab-741f02103737/dbc9c879-5c7b-4779-80ab-741f021037371.gif)
,AComponent.FName);
Insert(AComponent);
AComponent.SetReference(True);
ifcsDesigninginComponentStatethen
AComponent.SetDesigning(True);
Notification(AComponent,opInsert);
雖然有些地方還不大明白,不過關鍵的部分看來是Insert(AComponent)一句。
再找到Insert:
procedureTComponent.Insert(AComponent:
ifFComponents=nilthenFComponents:
=TList.Create;
FComponents.Add(AComponent);
AComponent.FOwner:
=Self;
我們所猜想的內部列表終於現身,它是一個通用的,可以容納任何TObject或者Pointer的TList類型。
另外,從上面的代碼段也可以看到VCL中比較典型的一種處理方法,那就是物件“只有在需要的時候才建立”,比如上面建立FComponents就是屬於這種情況。
當然,在每次建立一個構件時都檢查一遍FComponent的有效性將會稍稍影響程式的運行速度,但是考慮到程式所佔用的記憶體(Form和Application一般有應該有Components列表,而一般的構件則完全沒有必要,如果每個構件都建立一個List的話,那麽佔用的記憶體是相當可觀的),這種處理方法也有它的道理。
現在再來看看析構時的情況:
destructorTComponent.Destroy;
Destroying;
ifFFreeNotifies<
nilthen
whileAssigned(FFreeNotifies)and(FFreeNotifies.Count>
0)do
TComponent(FFreeNotifies[FFreeNotifies.Count-1]).Notification(Self,opRemove);
FreeAndNil(FFreeNotifies);
DestroyComponents;
ifFOwner<
nilthenFOwner.RemoveComponent(Self);
inheritedDestroy;
這裏有兩個重要的地方:
(1)DestroyComponents一句,顯然是刪除其所擁有的Components;
(2)FOwner.RemoveComponents(Self),看來是把自身從父構件的FComponents列表中移走。
procedureTComponent.DestroyComponents;
var
Instance:
TComponent;
whileFComponents<
nildo
Instance:
=FComponents.Last;
if(csFreeNotificationinInstance.FComponentState)
or(FComponentState*[csDesigning,csInline]=[csDesigning,csInline])then
RemoveComponent(Instance)
else
Remove(Instance);
Instance.Destroy;
procedureTComponent.RemoveComponent(AComponent:
ValidateRename(AComponent,AComponent.FName,'
);
Notification(AComponent,opRemove);
AComponent.SetReference(False);
Remove(AComponent);
RemoveComponent內部又調用了Remove,再看看這個函數:
procedureTComponent.Remove(AComponent:
=nil;
FComponents.Remove(AComponent);
ifFComponents.Count=0then
FComponents.Free;
FComponents:
很簡單,是麽?
通過這一番遊歷,相信“構件在何種情況下會被自動清除和如何被清除”這個問題應該有了明確的答案,以後寫起代碼來也應該放心多了。
附記:
通過上面的例子,我才體會到Delphi中的導航鍵是多麽方便!
不論是自己的單元還是VCL內部單元,用Ctrl+點擊和Ctrl+Shift+Up/Down,以及CodeEditor中的Back/Forword按鈕,三兩下就可以定位到任何地方。
在VisualC++中就沒有這樣的方便,不論是用Trace還是用SourceBrowser,都必須先編譯通過才行,即使用FindInFiles也不見得快多少。
相比起來,在Delphi中跟蹤源代碼要方便多了。
雖說Delphi的啓動速度確實慢了點,不過它在其他方面提供的快捷方便還是應該肯定的。
☆☆☆☆☆☆☆☆☆☆☆☆☆☆
無名稱構件的使用
我曾在許多Delphi資料中看到這樣的說法:
每一個Component都必須有一個不爲空的Name,用於和其他Component相區分。
過去我也對這種說法深信不疑。
但是看過《MasteringDelphi6》後,我知道我錯了。
Component的Name屬性可以是空的,特別是對於MenuSeparator,StaticLabel,Bevel,Shape和Panel等等。
雖然IDE會給所有的構件起一個默認的名稱,不過你可以在ObjectInspector中把它的Name清空,這樣的後果是:
1.構件在ObjectInspector和ObjectTreeView中將變成Unnamed或者Component<
0>
這樣的表示方法;
2.該構件的聲明將從Form的聲明中完全刪除;
3.如果用ViewAsText看看.DFM文件的定義,發現無名稱的Component物件定義爲這樣的形式:
object:
TMenuItem
caption=‘-’
end
而一般的聲明爲:
objectFile1:
caption=‘File’
這個特點的真正好處在於,可以讓Form的聲明簡潔許多,再也不會充斥大量無用的N1,N2之類識別字,從而混淆你的視線。
當然,可執行文件的大小也會稍有減少。
不過,假如將Form上面所有Label的Name都設爲空,那麽運行該程式將産生一個錯誤,因爲編譯器看到聲明中沒有Label,所以不會將TLabel的有關TLabel的庫函數連接進來。
(編譯器在絕大多數時候都很聰明,不過這個時候只能說它是自作聰明。
)解決辦法是至少保留一個有名字的Label。
我們甚至可以手動將Form中某個Component的聲明刪除。
不用擔心,在Form建立起來的時候這個Component不會消失,只是不能夠用通常的辦法來引用它了。
真的有需要的時候,可以用Form1.FindComponent(‘Button1’)這樣的方法找到特定的構件。
如果Form中的構件實在太多,可以用這個技巧來刪除一部分不太重要的聲明,保持單元的簡潔。
其實,要減少Label聲明的數量,用Delphi6中新增的LabledEdit是一個行之有效的辦法,而且LabeledEdit可以簡化表單的佈局管理。
不過,LabeledEdit只對Edit有效。
要減少其他不需要回應事件和處理輸入的構件(如MenuSeparator,Panel和Bevel等等)的聲明,就只能用這種方法。
不同於一般的procedure或者function,在Delphi中屬於某個類的方法稱爲method,它們的聲明一般爲
AProcMethod:
procedure(Sender:
TObject)ofobject;
爲什麽一定要加上ofobject呢?
實際上,ObjectPascal中一般的函數指標就相當於普通的指標,而AProcMethod則是一個對指標,它在Delphi中有一個對應的類型:
TMethod。
TMethod=record
Data,Code:
Pointer;
所以,我們在單元中可以這樣:
procedureTForm1.Button1Click2(Sender:
TObject);
ShowMessage(‘Click2!
’);
procedureTForm1.FormCreate(Sender:
Method:
TMethod;
Method.Data:
Method.Code:
=MethodAddress(‘Button1Click2’);
Button1.OnClick:
=TNotifyEvent(Method);
當然,實際上只需要Button1.OnClick=Button1Click2即可;
不過,這樣可以讓你明白在幕後發生的事情。
值得注意的一點是,Button1Click2應該聲明在published段(或者和其他構件的聲明放在一起,因爲默認作用域就是published),以便編譯器爲它産生RTTI資訊,否則程式可能達不到預期的效果。
Delphi中的Serialiation
熟悉MFC的人對Serialize這個虛擬函數大概不會陌生。
在MFC中,如果要用Serialization機制讀取和保存資料,大概是這樣:
voidCMyDoc:
:
Serialize(CArchive&
ar)
{
if(ar.IsStoring())
ar<
<
myString;
myInt;
}
ar>
ObjectPascal中有沒有類似的機制呢?
接觸到TReader和TWriter這兩個類之後,我知道我找到答案了。
不需多講理論,來看一個實際的例子。
在Form上面放三個Edit和兩個Button,另外再加一個OpenDialog和SaveDialog。
代碼如下:
procedureTForm1.Button1Click(Sender:
TObject);
ifSaveDialog1.Executethen
Serialize(SaveDialog1.FileName,True);
procedureTForm1.Button2Click(Sender:
ifOpenDialog1.Executethen
Serialize(OpenDialog1.FileName,False);
procedureTForm1.Serialize(constFileName:
string;
bSave:
Boolean);
Reader:
TReader;
Writer:
TWriter;
Stream:
TFileStream;
ifbSavethenbegin
=TFileStream.Create(FileName,fmOpenWriteorfmCreate);
=TWriter.Create(Stream,4096);
Writer.WriteString(Edit1.Text);
Writer.WriteInteger(StrToInt(Edit2.Text));
Writer.WriteFloat(StrToFloat(Edit3.Text));
Writer.Free;
Stream.Free;
end
elsebegin
=TFileStream.Create(FileName,fmOpenRead);
=TReader.Create(Stream,4096);
Edit1.Text:
=Reader.ReadString;
Edit2.Text:
=IntToStr(Reader.ReadInteger);
Edit3.Text:
=FloatToStr(Reader.ReadFloat);
Reader.Free;
如果願意的話,完全可以將Serialize包裝成一個virtualmethod,從而讓派生類中的實現更加簡潔。
TReader和TWriter不僅能夠讀取和寫入ObjectPascal中絕大部分標準資料類型,而且能夠讀寫Collection/List/Variant這些高級類型,甚至能夠讀寫Perperties和Component。
不過,TReader/TWriter自身實際上提供的功能很有限,大部分實際的工作是由TStream這個非常強大的類來完成的。
從TReader和TWriter的聲明中可以看到一些特別爲Component而設計的方法,不難猜想,Delphi開發環境本身很可能就是利用TReader/TWriter,將構件的屬性寫入.DFM文件以及從.DFM文件中讀取屬性值的。
下面的例子很有用也非常有趣,它的效果完全相當於FormDesigner中的ViewAsText命令:
DFMBuf,TextBuf:
TStream;
DFMBuf:
=TMemoryStream.Create;
DFMBuf.WriteComponent(Self);
TextBuf:
DFMBuf.Seek(0,soFromBeginning);
ObjectBinaryToText(DFMBuf,TextBuf);
TextBuf.Seek(0,soFromBeginning);
Memo1.Lines.LoadFromStream(TextBuf);
TextBuf.Free;
DFMBuf.Free;
甚至可以從可執行文件中讀取Form的資訊:
buf:
pointer;
=TResourceStream.Create(HInstance,'
TForm1'
RT_RCDATA);
DFMBuf.Position:
=0;
(說明:
如果表單不是TForm1,那麽請將TResourceStream.Create一句中的第二個參數改爲相應的表單類名。
)
好好讀懂這些代碼,相信自己做一個DFMViewer也不是遙不可及的事情了吧!
CLX及其它
《MasteringDelphi6》中提到ObjectPascal中有一個TBits類,用來進行二進位位元操作。
我看過這一段之後馬上去找,果然在Classes單元中找到了它。
這個類的聲明相當簡單,不過我看過後感慨很多。
如果不知道這個類的存在,我以後可能還要在需要操作二進位位元的時候辛辛苦苦的寫一大串代碼(我以前也寫過不少這樣的代碼),要是能夠早點知道有這個類的話該有多好!
我相信還可能有很多人和我一樣,從來不知道TBits的存在。
因此,我決定以後一定要抽個時間,好好把Classes和System這些單元瀏覽一遍;
雖然在沒有多少文檔的情況下去讀這些文件不會輕鬆,但是很可能會發現一些有用的東西,從而避免去做重新發明輪子的傻事。
我過去總是覺得,Delphi的幫助系統比起MSDN來是有很大差距的。
這次我倒是發現了一個相反的例證。
通過對Classes單元一個走馬觀花的瀏覽,我發現一個ExtractStrings函數,毫無疑問是非常有用的。
雖然在一般的教材中都找不到關於它的介紹,不過在線幫助中確實有關於它的完整描述。
自己作個例子試試看,和猜想的用法相差不遠。
這讓我想起MFC中也有一個類似的AfxExtractSubString,同樣有用,但龐大的MSDN中就是找不到它的幫助(其實,MFC中類似的Afx函數,只有很少的一部分才有文檔資料,其他的都屬於Undocumented。
)雖然這一個小地方不能證明什麽,但至少說明Borland在幫助系統的完整性方面還是用心了的,這讓我對Delphi幫助系統稍稍有了一些正面印象。
Delphi6中幫助系統的主題一般都有VCLReference和CLXReference兩種,如果按F1的話,首先會彈出一個對話方塊詢問你到底選擇那一個。
時間長了就很煩人。
這裏有一個方法可以避免:
如果你希望一直使用VCL的話,選擇Help->
Custom,將列表中所有有關CLX的專案一概刪除,然後保存專案(當然,最好先將原先的設置備份一個,留給以後恢復用。
)最後關閉再重新打開Delphi就行了。
Delphi6現在支援VCL和CLX兩個類庫。
CLX在底層是基於Qt這個類庫的,可能有些朋友還沒有聽說過它的名字,不過在Linux系統上面Qt的名字可是非常響亮哦。
你可以在新建一個專案的時候指定是使用VCLApplication還是CLXApplication,構件面板會自動變化,只顯示對當前專案可用的構件。
CLX構件和VCL構件從外觀上總的來說是驚人的相似,當然在背後它們還是很不同的。
CLX中有一些有意思的構件在VCL中是找不到的,比如TextViewer和TextBrowser。
而VCL中有一些構件在CLX中也沒有對應,主要是Win32面板上的構件,不過CLX中也有類似的構件,雖然叫法不同。
(《MasteringDelphi6》中提到,和Windows系統通過消息機制通信的方法不同,Qt中採用的是更高層的面向物件封裝,比如要操作一個按鈕,不用像按鈕發送消息,直接調用按鈕的相應方法即可。
應該把Qt也看作一種ApplicationFramework,而且這種結構一定很值得學習,有機會的話應該看看。
)雖然VCL和CLX都有不少好的構件,但它們是無法共同使用的,也就是說,沒有辦法在VCLApplication中使用CLX構件,反之亦然。
《MasteringDelphi6》中有一個很妙的例子,用到CLX中的TextViewer。
這個構件的特點是完全基於HTML的,而它有Text屬性,因而又和Memo相相容。
所以,只要建立一個CLXApplication,在表單上面放上一個Memo和一個TextViewer,並修改Memo的OnChange事件:
procedureTForm1.Memo1Change(Sender:
TextViewer1.Text:
=Memo1.Text;
簡簡單單的一句話,産生出來的可是一個非常強的工具,你可以在Memo中按照HTML格式寫內容,比如“<
AHREF=””>
IMGSRC=”viewme.gif”>
/A>
”在TextViewer中立刻就能夠看到效果,非常好的HTML學習工具哦。
不過要說明的一點是,TextViewer的顯示效果和我們通常在IE中看到的會有一些區別,有些網頁效果是顯示不出來的。
但即便如此,這個程式還是讓我興奮了半天。
由於Qt的內置功能和Linux的可定制特性,因而CLX程式的外觀可以是千變萬化的,這就比Windows的單一外觀要有趣一些。
《MasteringDelphi6》中舉了一個例子。
建立一個CLXApplication,其中添加一個列表框和其他一些常用構件。
在ListBox中增加幾個表項,分別是Windows,Motif,Motif+,CDE,SGI,Platinum,SystemDefault。
爲ListBox增加OnDblClick處理:
procedureTForm1.ListBox1DblClick(Sender:
caseListBox1.ItemIndexof
0:
Application.Style.DefaultStyle:
=dsWindows;
1:
=dsMotif;
2:
Application.Style.Defau