程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 將窗體從屬於主窗體

將窗體從屬於主窗體

編輯:關於C++

幾乎所有正式一點的C++ Builder程序除了主窗體外都還有從屬窗體,有時是對話框,有時是無模式窗口。VCL使得創建和顯示從屬窗體都易如反掌。但不是所有程序都適於采用無模式窗體,有些程序需要在一個主窗體內顯示不同的內容。本文討論如何將一個從屬窗體“寄居”於主窗體中,從屬窗體看上去是主窗體的一部分,用戶甚至不知道一個從窗體正被顯示。圖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();
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved