幾乎所有正式一點的C++ Builder程式除了主表單外都還有從屬表單,有時是對話方塊,有時是無強制回應視窗。VCL使得建立和顯示從屬表單都易如反掌。但不是所有程式都適於採用無模式表單,有些程式需要在一個主表單內顯示不同的內容。本文討論如何將一個從屬表單“寄居”於主表單中,從屬表單看上去是主表單的一部分,使用者甚至不知道一個從表單正被顯示。圖A顯示了一個主表單,其客戶區是一個從表單。
圖A 客戶區包含從屬表單的主表單
理解子/父聯絡
這類程式的基本思路是讓所有從屬表單都作主表單的子表單,這種設計在其他架構(如OWL或MFC)中很常見,但在VCL程式中卻不常見。VCL不允許簡單地指定一下屬性就使一個表單從屬於另一表單,要做到這一點還得付出點小小的勞動。你得告訴Microsoft Windows從屬表單是主表單的子物件,在C++ Builder編程中一般趨於認為表單是視窗,元件是子物件,實際上從Windows的觀點來看,表單和元件都是視窗。可以將任一視窗
(表單和元件)指定為另一視窗的子物件,只要你暫時跳出VCL圈子。
更好的“鼠夾”
將一個表單附屬於一個主表單的一個好處是你可以象設計任何其他從屬表單一樣設計子表單,就是說你建立一個新的表單,在其上添加元件並書寫這個表單的代碼。這樣使得設計你的子表單變得容易,並將所有操縱子表單的代碼集中在一個地方。
程式設計範例
先給出一些程式的背景,程式名叫PARENTING,有一個主表單,主表單的頂部和底部各有一個工具條(Tool Bar)和狀態條(Status bar),除主表單外,還有兩個子表單,一個叫TTableForm,用柵格顯示ANIMAL.DBF資料表,ANIMAL表是C++ Builder帶的資料庫樣本的一個表。另一個子表單TChartForm用TChart顯示ANIMAL表。(如果你購買的C++ Builder是標準版則沒有資料庫元件)你可以通過點擊功能表項目或工具按鈕來選擇顯示表單還是圖形表單,在你作出選擇時,活動表單被摧毀而被選表單被顯示,子表單在主表單的工具條下方、狀態條上方的客戶區顯示,而且隨主表單大小變動而隨時保持充滿客戶區。
重載CreateParams()
如前所述,為讓主表單控制從屬表單,需要將主表單設為從表單的“父”,這可以通過重載CreateParams()方法來完成。CreateParams()在VCL建立與表單聯絡的視窗時調用,CreateParams()的聲明如下:
void __fastcall CreateParams(TCreateParams& Params);
CreateParams()的唯一參數是對一個TCreateParams結構體的引用。在VCL中TCreateParams定義如下:
struct TCreateParams
{
char *Caption;
int Style;
int ExStyle;
int X;
int Y;
int Width;
int Height;
HWND WndParent;
void *Param;
tagWNDCLASSA WindowClass;
char WinClassName[64];
};
此結構包含了Windows建立一個視窗所需的所有資訊(如果你曾用API進行Windows編程,你一定能意識到TCreateParams的成員對Windows CREATESTRUCT結構的映射)。在重載CreateParams()時,首先調用基類的CreateParams()方法,然後修改TCreateParams結構的個別成員變數。一個重載過的CreateParams()方法看起來大致如下:
void __fastcall TChartForm::CreateParams(TCreateParams& Params)
{
TForm::CreateParams(Params);
Params.Style=WS_CHILD|WS_CLIPSIBLINGS;
Params.WndParent=MainForm->Handle;
Params.X=0;
Params.Y=0;
Params.Width=MainForm->ClientRect.Right;
Params.Height=MainForm->ClientRect.Bottom;
}
程式的關鍵是設定TCreateParams結構體的Style和WndParent成員,Style設WS_CHILD 和WS_CLIPSIBLINGS視窗式樣,WS_CHILD指定視窗為另一視窗的子視窗。根據定義,一個子視窗是沒有標題棒的,設計時的標題棒在Windows在運行時建立表單會被去掉。WS_CLIPSIBLINGS保證主視窗的不同子視窗在表單繪製時不互相干涉。很明顯,一個子視窗必須得有一個父物件,通過將父視窗控制代碼指定給TCreateParams結構體WndParent 成員就指定了父物件,正如前面代碼所示,WndParent成員設定為主表單的Handle屬性。鑒於指定父物件屬性相對直接明了,我不在這個主題上深入更多。
設定子表單的屬性
除了CreateParams()方法中的代碼外,還得設定一些子表單的屬性,多數屬性可以保留預設值,但AutoScroll屬性應設為false,當然,前提是你的表單要設計為不必捲動的表單風格。因為子視窗的大小和位置在CreateParams()中設定,所以Position屬性可置為poDefault。Caption和BorderIcon屬性將被忽略,故而無需指定。確保BorderStyle置為bsSizeable,還有BorderWidth置為0,如果此二屬性設成其它值,子表單與主表單會不協調。
表單的其它元件
很多時候,除了從屬表單外,主表單還包含別的元件,比如工具條和狀態條,此時設定TCreateParams結構體的X、Y、Width和Height成員時得考慮到工具條和狀態條,子表單應與頂部的工具條和底部隊狀態條協調。因此設定TCreateParams結構體的成員的代碼應該是:
Params.X=0;
Params.Y=MainForm->ToolBar->Height+1;
Params.Width=MainForm->ClientRect.Right;
Params.Height=(MainForm->StatusBar->Top-1)-Params.Y;
注意Y設為工具條底部加1,子表單寬度置為主表單客戶區寬度,高度根據子表單和狀態條的頂部計算,基本上為界於工具條底部和狀態條底部之間的主表單客戶區。這些就是使從屬表單“寄居”於主表單的所有要求,你可能還有其它想在子表單中實現的特徵,我將把這些特徵的討論留到後面。
設定主表單
主表單也得進行設定以控制其“收留”的子表單。首先需要將子表單從自動建立表單列表中去掉,需要時再建立它們。如果不從列表中去掉,則當你的應用程式啟動時它們會自動顯示。你還需要一個變數來跟蹤當前的活動子表單,在主表單的Public區聲明如下變數,TForm * ActiveChild; ActiveChild是公有的,因為子表單要訪問這個變數。我馬上會示範如何使用這一變數。現在來書寫顯示子表單的代碼,先看下面程式行,然後我來解釋。
void __fastcall TMainForm::Chart1Click(TObject *Sender)
{
if(ActiveChild)
delete ActiveChild;
TChartForm * form=new TChartForm(this);
ActiveChild=form;
form->Show();
Chart1->Checked=true;
Table1->Checked=false;
}
此方法是主表單的功能表項目的OnClick處理控制代碼,猜的沒錯,這是在顯示TChartForm子表單。首先檢查ActiveChild變數是否非0,如果有啟用動子表單ActiveChild將非0。如果ActiveChild不為0,刪除此變數關聯的指標以摧毀活動子表單,否則程式會將子表單一個接一個地堆疊在一起。接著建立一個TChartForm類的執行個體,將new操作返回到指標賦予ActiveChild變數,這樣ActiveChild總是包含一個指向當前子表單的指標。最後調用Sow()方法顯示子表單。最後兩行代碼確保代表表單或圖形顯示的功能表項目顯示一個選中標記。為完成ActiveChild變數的討論,我得帶你回到子表單單元一會兒。每個子表單都含有一個如下的OnClose事件的事件控制代碼:
void __fastcall TChartForm::FormClose(TObject *Sender,
TCloseAction &Action)
{
MainForm->ActiveChild=0;
MainForm->Chart1->Checked=false;
Action=caFree;
}
注意當表單被摧毀時,主表單的ActiveChild被置為0,並將與子表單關聯的功能表項目置為未選中。Action參數置為caFree,以通知VCL釋放與此表單關聯的記憶體。你或許會疑惑為何FormClose控制代碼包含上面最後兩行。答案是每個子表單都有一個Close按鈕用來關閉表單,如果用Close按鈕來關閉表單,就需要釋放記憶體和uncheck功能表項目。
額外特徵
例子程式至少還有一個尚未討論的特徵,就是如果子表單比主表單大,要重新調整主表單大小以容納子表單。這些語句放在子表單的CreateParams()方法中。之前我展示過一個簡單的CreateParams()例子,但沒有放進調整主表單大小的語句。列表B中有完整的CreateParams()方法,和先前展示唯一不同的是包含以下語句:
if(Width>MainForm->ClientWidth)
MainForm->ClientWidth=Width;
if(Height>(MainForm->StatusBar->Top-MainForm->ToolBar->Height))
MainForm->ClientHeight=Height+
MainForm->ToolBar->Height+
MainForm->StatusBar->Height ;
這幾條語句檢查子表單的寬度是否大於主表單的ClientWidth屬性,如果是,主表單的ClientWidth置為子表單的寬度。餘下的幾行作的是同樣的事情,只不過針對的是主表單的客戶區高度而已。這些語句的結果是主表單總是被調整到能完全容納被顯示的子表單。範常式序還考慮了主表單調整大小時的情況。如果主視窗大小有變化,子表單大小也必須變化以進行充滿主表單的客戶區。以下語句示範了主表單的OnResize事件控制代碼:
void __fastcall TMainForm::FormResize(TObject *Sender)
{
if(ActiveChild)
{
ActiveChild->Width=ClientRect.Right;
ActiveChild->Height=(MainForm->StatusBar->Top-1)-
ActiveChild->Top;
}
}
這些語句相當直接,無需我逐條解釋。注意首先檢查ActiveChild變數確保非0(即指向某子表單),很明顯如果當前沒有啟用任何子表單,在OnResize中就什麼都不用幹。其餘語句是CreateParams()中看到語句的變種,只是簡單地計運算元表單的新尺寸並設定相應的Width和Height屬性。
結語
列表A包含了例子程式主表單的代碼。列表B示出了TChartForm單元的源碼。標頭檔都沒有給出因為沒有什麼有意義的語句,也未給出TTableForm單元的語句,因為與ChatForm單元類似。你可以在www.reisdorph.com下載例子程式。將子表單“寄居”於主表單提供了一種清晰的替代MDI的方法,對那些只能以無模式表單形式向使用者顯示資料的程式也是一種替代,使用子表單允許你使用表單設計器設計你的從視窗,並將操縱子表單的代碼放在一個地方。
列表A:MAINU.CPP
#include <vcl.h>
#pragma hdrstop
#include /"MainU.h/"
#include /"ChartU.h/"
#include /"TableU.h/"
#pragma resource /"*.dfm/"
TMainForm *MainForm;
__fastcall TMainForm::TMainForm(TComponent* Owner)
: TForm(Owner)
{
//清0以防包含隨機數
ActiveChild=0;
//開啟資料表
Table->Active=true;
}
void __fastcall TMainForm::Table1Click(TObject *Sender)
{
if(Table1->Checked) return;
if(ActiveChild){
delete ActiveChild;
ActiveChild=0;
}
TTableForm *form=new TTableForm(this);
//將DBGrid的DBGrid::DataSource屬性賦給資料來源
form->DBGrid->DataSource=DataSource;
//跟蹤活動子表單
Active=form;
form->Show();
Table1->Checked=true;
Chart1->Checked=false;
}
void __fastcall TMainForm::Chart1Click(TObject *Sender)
{
if(Chart1->Checked) return;
if(ActiveChild){
delete ActiveChild;
ActiveChild=0;
}
TChartForm *form=new TChartForm(this);
Active=form;
form->Show();
Chart1->Checked=true;
Table1->Checked=false;
}
void __fastcall TMainForm::FormResize(TObject *Sender)
{
if(ActiveChild){
ActiveChild->Width=ClientRect.Right;
ActiveChild->Height=(MainForm->StatusBar->Top-1)-
ActiveChild->Top;
}
}
列表B:CHARTU.CPP
#include <vcl.h>
#pragma hdrstop
#include /"ChartU.h/"
#include /"MainU.h/"
#pragma resource /"*.dfm/"
TChartForm *ChartForm;
__fastcall TChartForm::TChartForm(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TChartForm::CreateParams(TCreateParams& Params)
{
//調用基類CreateParams方法
TForm::CreateParams(Params);
//子視窗類型
Params.Style=WS_CHILD|WS_CLIPSIBLINGS;
//設定父為主表單
Params.WndParent=MainForm->Handle;
Params.X=0;
if(Width>MainForm->ClientWidth)
MainForm->ClientWidth=Width;
if(Height>(MainForm->StatusBar->Top-MainForm->ToolBar->Height))
MainForm->ClientHeight=Height+
MainForm->ToolBar->Height+
MainForm->StatusBar->Height;
Params.Y=MainForm->ToolBar->Height+1;
Params.Width=MainForm->ClientRect.Right;
Params.Height=(MainForm->StatusBar->Top-1)-Params.Y;
}
void __fastcall TChartForm::FormClose(TObject *Sender,
TCloseAction &Action)
{
MainForm->ActiveChild=0;
MainForm->Chart1->Checked=false;
Action=caFree;
}
void __fastcall TChartForm::CloseBtnClick(TObject *Sender)
{
Close();
}