二、由程序員用new的方法創建的窗體事件驅動鏈
在程序中無數次地見到過以下代碼:
TfrmConstant *pForm=new TfrmConstant(this); //創建一個窗體
pForm->Show();
……
delete pForm;
我們經常需要動態創建一個Form,再顯示它,然後在合適的地方銷燬它。
請注意上面代碼中的this參數,它被傳送給TfrmConstant窗體的構造函數,這一構造函數有一個唯一的形參——Owner。這個參數決定了由誰負責銷燬它。
在BCB的幫助文件中對Owner參數的描述如下:
Owner描述了誰負責銷燬這個組件。XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />
使用Owner參數來存取component所有者的接口。當一個組件擁有另一個組件時,當父組件內存被釋放時,子組件所佔的內存資源也被釋放。這就是說,當一個窗體被銷燬時,窗體上的所有組件也會被刪除。
缺省情況下,一個窗體擁有放在其上的所有組件,同樣地,Application對象則擁有所有的Form對象。這樣,當程序結束時,Application對象會釋放所有Form佔用的資源。
下面是我對Owner參數的實驗記錄
實驗一:主窗體(Form1)new一個普通窗體(Form2),Owner=this
Form1中的代碼示例:
p2=new TForm2(this);
p2->Show();
創建:
先創建完主窗體後,只調用普通窗體Form2的構造函數,不管顯不顯示,事件驅動鏈與以前的實驗一樣。
關閉:
主窗體:OnCloseQueryàOnCloseàOnHide()àOnDestory()à主窗體析構函數àForm2析構函數
關閉次序是先主窗體再普通窗體,如果創建了多個普通窗體,根據創建的先後順序按後創建先銷燬的原則逐個銷燬窗體:
普通窗體是顯示還是隱藏對事件驅動鏈無影響
實驗二:主窗體(Form1) new一個普通窗體Form2,Owner=Application
創建:
同實驗一
關閉:
首先是Form1:OnCloseQuery()àOnClose()àForm2析構函數(如果有多個,按照後進先出的次序調用) àForm1:OnHide()àOnDestory()àForm1析構函數.
實驗三:主窗體(Form1) new一個繼承窗體(Form2繼承自Form1),Owner=this
創建:
創建主窗體Form1,事件鏈同單窗體的創建;
接着在代碼中開始new Form2:
Form2 OnCreate()àForm1的構造函數àForm2 的構造函數à創建結束
我們看到要先調用父類的構造函數
關閉:
1.Form2未顯示:
Form1: OnCloseQuery()àOnClose()àOnHid()eàOnDestory()àForm1的析構函數
àForm2的析構函數à Form1的析構函數à Form2 OnDestory()
注意:並無Form1的OnDestory,同樣地:Form1的析構函數被執行了兩次
2.Form2顯示情況下::
Form1: OnCloseQuery()àOnClose()àOnHide()àOnDestory()àForm1的析構函數
Form2 OnHide()à Form2的析構函數à Form1的析構函數à Form2 OnDestory
僅多了一個Form2 OnHide()過程
實驗四:主窗體new一個繼承窗體(Form2繼承自Form1),Owner=Application:
創建(Form2不顯示情況):
Form2 OnCreate()àForm1的構造函數à Form2的構造函數
關閉(Form2不顯示情況)
Form1 OnCloseQuery()àOnClose()àForm2的析構函數à Form1的析構函數à Form2 OnDestory()àMainFoRM HideàMainForm的析構函數à MainForm 的析構函數
創建(Form2顯示情況):
Form2 OnCreate()àForm1的構造函數à Form2的構造函數à Form2 OnShow()à ……à Form2:OnResize()
多了一個顯示的事件
關閉(Form2顯示情況)
Form1: OnCloseQuery()àOnClose()à Form2 :OnHide()àForm2的析構函數à Form1的析構函數à Form2 OnDestory()
àForm1 OnHide()àForm1 OnDestoryà MainForm1析構函數
三、MDI型應用程序事件發生次序
主窗體爲MainForm,子窗體爲MDIChild
創建
MainForm構造函數àOnCreate()à……à OnResize()
新建一個MDI子窗體時,Owner=Application或MainForm
MDIChild 構造函數àOnCreateàshowàActivate
關閉
Owner=Application時:
MDIChild OnCloseQuery()(有幾個子窗體就執行幾次)àMainForm OnCloseQuery()àMainForm OnClose()à MDIChild OnDestory()à MDIChild 析構函數àMainForm OnDestory()àMainForm析構函數
Owner=MainForm時:
MDIChild CloseQuery()(有幾個子窗體就執行幾次)àMainForm OnCloseQuery()àMainForm OnClose()àMainForm OnDestory()à MainForm析構函數à MDIChild OnDestory()à MDIChild 析構函數
顯然,Owner參數決定了主窗體與MDIChild的析構次序。
有用的結論
從實驗中我們可以得出一些有用的結論:
關於OnCreate和OnDestory事件:
1. Form的OnCreate事件是不可靠的,並不像名字所說的當窗體一創建時就執行,而是當窗體是工程的主窗體或是一個繼承其它窗體的子窗體時才執行.所以,在OnCreate()中初始化變量和創建對象是不合適的;
2. 在有繼承情況下,OnCreate事件先於構造函數發生,對應地OnDestroy事件後於析構函數發生。
關於窗體的構造函數和析構函數
1. 對於任何窗體,構造函數和析構函數是可靠的,因爲它每一次創建和銷燬窗體時都會執行。所以我們應在構造函數中進行程序的初始化操作,在析構函數中清理資源;而不要在OnCreate和OnDestory事件中進行處理;
2. 在繼承情況下BCB子窗體對象的構造過程是先構造父窗體,再構造子窗體,這點與C++完全一樣,銷燬過程則反之;
3. 在有繼承的情況下,父類窗體的構造函數和析構函數則會被執行多次,所以,在這兩個地方不要new和delete對象,而應將這類代碼放到具體的子窗體類中。如果不這樣做,可能會由於多次刪除同一對象而引發錯誤,除非將父類窗體的對象指針聲明爲static的,保證所有子類共享此new的唯一對象!
4.析構函數總是最後調用的,而構造函數總是第一個調用。
關於Owner參數:
當Owner=Application時,先銷燬new出來的窗體,再銷燬主窗體;
而當Owner=this(指主窗體)時,先銷燬主窗體,再銷燬子窗體;
這個原則對於在窗體內部動態地生成VCL控件時也適用。特別地,當程序中用到了數據模塊,或需要動態地生成DataSet等對象時更要注意。因爲一個DataSet可能會向多個窗體提供數據(應該避免這樣做,合適的方法是new TDataSet時Owner參數不設爲Application)。
其它:
1. 一個Visible=true的窗體一定要先Hide,才能被銷燬。
2. 程序事件鏈的源頭爲工程中的主窗體的構造函數
3. 在有繼承情況下,子類窗體實例一定要先Hide纔可以被銷燬。
4. 多窗體的管理BCB採用了棧的方法,按創建的次序,先創建的後銷燬,千萬要避免先創建的窗體含有對後創建的窗體(或其上的控件)的指針。
5. 關閉時一定會發生OnCloseQuery事件,如果是MDI,此事件還會在MDIChild窗體中傳播,所以,在這個地方提示用戶改變是合適的。
6. 有些事件一發生,則定然會跟着一系列的事件,這些事件發生的次序是不變的,比如,只要OnShow事件發生,則一定會接着發生以下事件:
àOnActivate()àOnCanResize()àOnConstrainedResize()àOnResize()
OnCloseQuery之後一定是OnClose()