可以什麼按鈕都沒有,也可以使關閉按鈕變灰不能用,但是不能只沒有關閉按鈕,除非自己畫標題列。
什麼按鈕都沒有的方法:
BorderIcons屬性中biSystemMenu設為false。
關閉按鈕變灰的方法:
執行下面代碼
HMENU hMenu = NULL;
hMenu = GetSystemMenu(this->Handle, false);
EnableMenuItem(hMenu, 6, MF_BYPOSITION | MF_DISABLED);
收藏內容:
窗 體 設 計
11.3 窗 體 特 性
我們曾經對表單的特性、方法等進行過較為詳細的討論。這裡,我們再對兩個關於表單類型及邊界類型的表單特性進行介紹。
設定表單類型使用特性FormStyle。該特性允許使用者有兩種選擇:一種是普通的SDI表單——SDI意思是Single Document Interface(單文檔介面),另一種是組成MDI應用程式的表單——MDI的意思是Multiple Document Interface(多文檔介面)。
下面列出了FormStyle特性的可能值:
?fsNorma1:表單是普通的SDI視窗或對話方塊。
?fsMDIChild:表單是MDI子視窗。
?fsMDIForm:表單是MDI父視窗——也就是 MDI應用程式的架構視窗。
?fsStayOnTop:表單是SDI視窗, 但它總處於其它所有視窗的前面, 除了那些也被設定為“stay-on-top”(留在最前面)的視窗。
設定邊框樣式使用特性BorderStyle。該特性引用表單的一個可視化元素TFormBorderStyle,有6個可能值:
?bsSizeable:表單有標準的粗邊框,可以拖動邊框來調整表單的大小。這是預設的樣式。
?bsDialog:表單有標準的對話方塊邊框,它也是粗的,但不能改變大小。擁有這類邊框的表單也就是對話方塊。
?bsSingle:表單有一個細邊框而且不能改變大小;它也叫作固定邊框。與Windows 3.1不同,Windows 95在細邊框與粗邊框之間沒有顯示明顯的區別。
?bsToolWindow:表單採用較細的標題列(使用較小的字型),而且只有一個最小化Close按鈕。這是一種不能改變大小的工具列的特殊類型。
?bsSizeToolWin:表單採用較細的標題列,與上一類型相似,但可以改變大小。
?bsNone:表單沒有邊框或任何傳統的元素(標題、最大化與最小化按鈕、系統功能表)。
11.4 固 定 窗 體
現在,我們來討論表單編程的技巧之一──固定視窗的位置。在Delphi的程式設計中,只需將表單(Form)的特性BorderStyle取值為 bsSizeable,使用者就可隨意變更表單的大小和位置的移動。但在應用程式中使用Windows API函數同樣可以改變表單的這個特性值,並且還可以實現其它目的。
當我們在進行表單移動和大小變更時,在此動作的前一時刻,Windows系統首先向表單送出WM_WINDOWPOSCHANGING訊息。由此訊息產生 的WM_WindowPosChanging事件控制代碼傳遞來的TWMWindowPosChanging訊息記錄型的WindowPos,是包含有變更動 作內容的TWindowPos記錄的指標。因此,我們可以根據對其內容的變更,來改變動作內容。
type TWindowPosMsg = record
Msg:Cardinal;
Unusrd:Integer;
WindowPos:PwindowPos;//lparam 以下記錄的指標
Result:Longint;
end;
該TWindowPos記錄包含以下內容:
PWindowPos = ^TWindowPos;
TWindowPos = Packed record;
hwnd:HWND;//發生動作表單的控制代碼
hwndInsertAfter:HWND;//變更Z順序時最上層的表單控制代碼
x:Integer;/表單左端的位置
y:Integer;//表單上端的位置
cx:Integer;//表單的寬
cy:Integer;//表單的高
flags:UINT;//變更的內容
在flags中,與此話題相關的標誌如下:
?SWP_NOMOVE——保持現在位置(無視x成員和y成員)
?SWP_NOOWNERZORDER——不變更主表單Z順序的位置
?SWP_NOSIZE——保持現在的大小(無視cx成員和cy成員)
?SWP_NOREDEAW——即使有變更,但不重寫畫面
?SWP_NOZORDER——保持現在Z順序(無視hwndInsertAfter成員)
據此當我們不想讓表單的大小變更時,只要在TWindowPos記錄的flags變數中設定SWP_NOSIZE標誌即可。同樣不想讓表單移動時,則設定SWP_NOMOVE標誌。
為了達到以上目的,首先要處理WM_WINDOWPOSCHANGING訊息,修改flags變數的內容。作為表單的處理過程(WMWindowPosChanging)如下:
{禁止表單移動、禁止大小變更處理}
porcedure Tform1.WMWindowPosChanging(var Msg:TWMWindowPosChanging);
begin
inherited
if bWinStat [1] then
Msg.WindowPos^.flags := Msg.WindowPos^.flags or SWP_NOMOVE;
if bWinStat [2] then
Msg.WindowPos^.flags := Msg.WindowPos^.flags or SWP_NOSIZE;
end;
此例中使用bWinStat變數(Boolean類型數組),來禁止表單的移動和大小的修改。
11.5 固定表單的橫寬
這裡我們繼續詳細介紹關於在表單的大小修改中固定橫寬的方法。
?當我們修改表單的大小或按下最大化按扭時,Window系統嚮應用程式表單送出WM_GETMINMAXINFO訊息。由於這個訊息中包含有表單尺寸的 最大和最小值,如果我們適當利用此訊息,則可以指定表單的大小範圍。首先定義WM_GETMINMAXINFO訊息的控制代碼。
(1)WM_GETMINMAXINFO訊息控制代碼的說明:
type
TForm1 = Class(Tform)
procedure WMGetMinMaxInfo(var Msg:TWMGetMinMaxInfo);
message WM_GETMINMAXINFO;
(2)以TWMGetMinMaxInfo記錄為參數:
TWMGetMinMaxInfo = record
Msg:Cardinal;
Unused:Integer;
MinMaxInfo:PMinMaxInfo;
Result:Longint;
end ;
TWMGetMinMaxInfo中的MinMaxInfo是具有關於表單大小資訊的TMinMaxInfo記錄的指標。
該記錄在Delphi的Windows.Pas檔案中是如下說明的:
type
{Struct pointed to by WM_GETMINMAXINFO Iparam}
PMinMaxInfo = ^TMinMaxInfo;
TMinMaxInfo = packed record ptReserved:Tpoint; //預留
ptMaxSize:Tpoint; //最大化時的大小
ptMaxPosition:Tpoint ; //最大化時的位置
ptMinTrackSize:Tpoint ; //尺寸變更時最小尺寸
ptMaxTrackSize:Tpoint ; //尺寸變更時最大尺寸
end;
當按下最大化按扭時,在ptMaxSize和ptMinSize中指定表單的大小和位置。當拖動表單架構時,ptMinTrackSize和ptMaxTrackSize確定其變化的最大和最小尺寸。
假如表單的大小隻在從 (100,100) 到 (500,400) 的範圍內變化,那麼,使用以下處理過程即可完成:
//設定表單大小變化範圍
procedure TForm1.WMGetMinMaxInfo(var Msg:TWMGetMinMaxInfo);
begin
inherited;
Msg.MinMaxInfo^.ptMaxTrackSize:= Point(500,400);
Msg.MinMaxInfo^.ptMinTrackSize:= Point(100,100);
end;
首先調用inherited方法,接著對MinMaxInfo構造體設定初始值,即設定ptMaxTrackSize和ptMinTrackSize的值。
這裡並沒有對按下最大化按鈕時的大小進行設定,僅僅只是對拖動架構的大小的動作進行了限制。因此當按下最大化按鈕時,表單與平常一樣佔滿全部畫面。
當我們絕對不想讓表單的尺寸發生變更時,則採用以下處理過程:
inherited;
Msg.MinMaxInfo^.ptMaxSize:= Point(300,200);
Msg.MinMaxInfo^.ptMaxTrackSize:= Point(300,200);
同樣,橫寬固定,縱長可自由改變的時候,則採用以下處理過程。
inherited;
Msg.MinMaxInfo^.ptMaxSize.x= Width;
Msg.MinMaxInfo^.ptMaxTrackSize.x = Width;
Msg.MinMaxInfo^.ptMinTrackSize.x = Width;
上面代入的是該事件控制代碼所屬的表單的橫寬,因此其表單的橫寬不能變更。
到此從功能上講,我們已經能夠對表單的尺寸進行控制了。可是,現在你一定會注意到以下的問題:一個是全屏化按鈕和表徵圖化按鈕的表現方法,另一個是對應尺寸變化的游標形狀的變化。
關於全屏化按鈕和表徵圖化按鈕的使用或不使用的控制方法,在Delphi的程式設計中,可通過直接設定表單(Form)的BorderIcons特性值來控 制。但是游標形狀的變換控制稍微有點麻煩。當游標在表單架構上時,游標顯示為尺寸可變更,但是實際上表單已經被固定。
為解決此問題產生適當的表單,而後用WinSignt32(Delphi中含有)或Spy++等Windows監視系統對向該表單傳送的訊息進行監視。當 滑鼠器在該表單上移動時,Windows監視系統向表單傳送WM_MOUSEMOVE訊息的同時,還傳送WM_NCHITTEST訊息。當移動和敲擊滑鼠 時,系統對表單傳送WM_NCHITTEST訊息,詢問現在滑鼠游標在表單的什麼位置,如果傳回游標在表單的架構上或表單內,系統則相應自動改變游標的形 狀。
因此,我們先定義WM_NCHITTEST訊息控制代碼,從該控制代碼傳回值。就是說即使游標在架構上,傳回值也不在架構上,給Windows系統一個小騙局。該訊息控制代碼說明如下:
interface
type
Tform1 = class(Tform)
private
bWinStat:array [1..4] of Boolean;
procedure WMNCHitTest(var Msg:TWMNCHitTest);
message WM_NCHITTEST;
該訊息控制代碼的處理過程:
//尺寸變更的游標形狀不顯示
porcedure Tform1.WMNCHitTest(var Msg:TWMNChitTest);
begin
inherited
//當移動禁止時,則使其上端、左端尺寸不變更
if bWinStat [1] and
(Msg.Result in [ HTTOP,HTLEFT,HTTOPLEFT,HTTOPRIGHT;HTBOTTOMLEFT])
then Msg.Result:= Windows.HTNOWHERE;
//整體表單尺寸變更禁止時
if bWinStat [2]
?and (Msg.Result in { HTLEFT,HTRIGHT,HTTOP,
HTBOTTOM,HTTOPLEFT,HTTOPRIGHT,HTBOTTOMLEFT,HTBOTTOPMRIGHT])
then Msg.Result:= Windows.HTNOWHERE;
//橫寬方向尺寸變更禁止時
if bWinStat [3] and (Msg.ResultIn{
HTLEFT,HTRIGHT,HTTOPLEFT,HTBOTTOM,HTTOPLEFT,HTBOTTOPMRIGHT])
then Msg.Result:= Windows.HTNOWHERE;
end;
單純就表單的架構而言,可分為上下左右4條邊和4個角共8個模式。當移動禁止時,上端、左端的位置尺寸不得變更。
11.6 無標題列視窗
現在,我們來討論表單編程的另一種技巧──建立無標題列視窗。
為了使標題列不顯示,我們可以在表單BorderStyle特性中選取bsNone值即可。但選取bsNone值後,表單周圍什麼也沒有。當我們只想去掉標題列時又怎麼辦呢?
實際上表單的位置、大小、顯示風格等均由Windows API的CreateWindowEx函數所指定,這個API的參數是在Delphi的CreateParams過程中被初始化的。Delphi是依據以 下的TCreateParams記錄的值產生表單的。TCreateParams記錄包含表單的位置、大小以及其他各種特性,在Delphi的 Control.Pas單元中記錄的定義為:
TCreateParams = record
Caption: PChar; //標題
Style: DWORD; //表單的顯示風格
ExStyle: DWORD; //表單的擴充顯示風格
X, Y: Integer; //顯示位置
Width, Height: Integer; //表單的大小
WndParent: HWnd; //父表單
Param: Pointer; //指向視窗產生參數(WM_CREATE訊息的 //LParam)的指標
WindowClass: TWndClass; //表單的類別
WinClassName: array[0..63] of Char; //類別名
end;
該記錄中包含表單的位置、大小和表單顯示風格的標誌。因此,一般通過其值的改變來控制產生表單的位置、大小和表單顯示風格。由於我們要設定表單初始的風 格,所以在Params.Style變數中設定表單的風格。又因為該變數是取標誌形式,所以使用and或or布爾運算來演算,從而完成各個位的on或 off。
例如,當BorderStyle特性為bsNone時,再給表單加上可變更大小的粗架構,則對CreateParams方法進行重載。處理過程如下即可:
Procedure TForm1.CreateParams(var Params:TCreateParams);
begin
inherited CreateParams(Params);
if BorderStyle = bsNone then
with Params do Style:= Style or WS_THICKFRAME;
end;
需要注意,一般情況下的繼承關係,單純使用inherited,則衍生類別的方法即可被調出執行;但繼承使用了CreateParams方法時,必須明確按inheritedCreateParams(Params)編製程式。
關於表單風格標誌值用以下定量來指定,這裡列舉的只是具有代表性的一部分。詳情請參考Windows API的CreateWindow的help。
WS_BORDER邊框
WS_CAPTION標題列
WS_CHILD子表單
WS_HSCROLL水平捲軸
WS_MAXIMIZE全屏化表單
WS_MAXIMIZEBOX全屏按鈕
WS_MINIMIIE表徵圖化表單
WS_MINIMIZEBOX表徵圖化按鈕
WS_SYSMENU系統功能表
WS_THICKFRAME可變更表單大小的粗邊框
WS_VISIBLE初始為可視的表單
到此,沒有標題列的,大小可以變更的表單已完成。
此表單由於沒有標題列所以關閉、全屏化等按鈕的操作均不可能,並且想通過拉拖標題列移動表單也不可能,下面我們先討論關於移動的問題。
關於WM_NCHITTEST訊息前面已經作瞭解說。當拖拉標題列移動表單時,表單伴隨滑鼠器的移動,系統向表單傳送WM_NCHITTEST訊息,當確認游標在標題列上,並同時檢測出滑鼠器左按鈕被按下時,移動成開始狀態。
只要游標在表單,則使表單的移動成為可能。為此,WMNCHitTest處理過程如下。
porcedure TForm1.WMNCHitTest(var Msg:TWMNCHitTest);
begin inherited; //當標題列非顯示時,游標在表單領域即可移動
if (BorderStyle = brNone) and
(Msg.Result = HTCLIENT) then
Msg.Result:= HTCAPTION;
end;
當滑鼠游標在表單領域內時,並且無標題列,WM_NCHITTEST訊息傳回值為HTCLIENT時,將傳回值作為HTCAPTION處理,系統則判斷滑鼠游標在標題列上,這樣就完成了表單領域可移動的處理。這裡再次給Windows系統一個騙局。
另外,討論一下標題列的消除和追加的方法。從而解決關閉、全屏化、表徵圖化的問題。首先想到的方法是在表單(Form)被單擊或雙擊後,改變 BorderStyle特性的值。就是說對Form的OnClick事件進行定義,建立子程式將bsNone變為bsSizeable。
可是我們測試一下,它並不動作。實際上這是因為在前面為了處理移動,在WMNCHitTest過程中,將表單領域的傳回值變更成了標題列的傳回值,因此系統只判斷滑鼠游標在標題列上單擊。
要想移動和單擊同時起作用需要下一點功夫。當用WinSight32來確認顯示正常的表單返回給系統的訊息時,與通常表單領域返回WM_LBUTTONDOWN訊息相對,在標題上單擊時返回WM_NCLBUTTONDOWN訊息。
這裡不使用表單(Form)的OnClik事件,而是通過WM_NCLBUTTONDOWN訊息的處理來得到希望的動作。如下編程即可 :
interface
type
TForm1 = Class(TForm);
:
protected WMNCLButtonDown(var Msg:TWMNCLButtonDown);
message WM_NCLBUTTONDOWN;
implemention //按一下滑鼠表單追加標題列
porcedure TForm1.WMNCLButtonDown(var Msg:TWMNCLButtonDown);
begin
if BorderStyle = bsNone then
borderStyle = bsSizeble;
end;
當然,如果在表單上配置其他可視構件,從而可以簡單地通過對構件的單擊來完成以上動作。
11.7 表單間相互連動
下面我們來討論兩個表單的設計技巧。設其中一個為主表單,另一個為子表單。 在Delphi程式設計中,表單(Form)的Position特性取值為poDesigned,則表單的顯示位置和大小以特性Left、Top、 Width、Height的取值為依據來決定。這樣在建立子表單時,以獲得主表單的顯示位置和大小(主表單的Left、Top、Width、Height 的特性值)為基準,然後確定子表單的位置和大小即可。
表單的初始顯示位置,可用CreateParams方法進行設定,之後用重載子表單(Form1)的CreateParams方法,來改變子表單的顯示位 置和大小。主表單和子表單之間是怎樣互相引用的呢?首先設定表單(Form1)的單元檔案名稱為unit1.pas,子表單(Form2)的單元檔案名稱為 unit2.pas。通常要引用某表單單元檔案的特性和內容時,應該在interface的uses處插入其表單(Form)的單元名(unit)。本實 例中根據主表單動態產生子表單,因此在主表單(Form1)的interface的uses處插入子表單的單元名(unit2)。這樣主表單即可引用子窗 體的各種特性值。另一方面要實現子表單引用主表單的各種特性值又怎麼辦呢?由於Pascal語言不允許相互循環參考,即A到B,並且B到A。這時,必須在 imptementation的uses處插入主表單的單位名(unit1),這樣即可對主表單(Form1)的特性值進行引用。
引用方法如下:
implementation
{$R *.DFM}
uses Unit1;//主表單單元名
{產生表單時參考數的初始化}
procedure TForm2.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
//當沒有表單題目時,添加細框
if BorderStyle = bsNone then
Params.Style := Params.Style or WS_THICKFRAME;
//根據主表單的顯示位置和大小決定子表單的顯示位置和大小
with Form1 do
begin
Params.X := Left + Width;
Params.Y := Top;
Params.Height := Height;
Params.Width := 100;
end;
end;
這樣即完成了對應主表單的子表單在其右側顯示的程式設計。現在當我們移動主表單時,子表單並不隨之移動,自然,移動子表單時,主表單也不會移動。雖然通常的Windows的應用程式則到此為止,但是我們的主題是要討論連動,下面我們繼續介紹這一問題。
首先要搞清楚,怎樣把握自身表單的移動和大小變化。在表單的移動和大小變化時Windows系統要送出WINDOWSPOSCHANGED訊息,為了捕捉此訊息,對以下方法(Method)進行說明:
type
Tform1 = class(Tform)
protected
procedure WMWindowPosChanged(vax Msg:TWMWindowPosChanged);
message WM_WINDOWPOSCHANGED;
WM_WINDOWPOSCHANGED訊息是在表單的位置和大小變化後送出的。上面是對WM_WONDOWPOSCHANGED訊息控制代碼的說明,接著是TWMWindowsPosChanged訊息記錄型和TWindowPos記錄內容的定義。
type
TWMWindowPosMsg = record
Msg:Cardind;
Unused:Integer;
WindowPos:PwindowPos;
Resukt:Longint;
end;
PwindowPos = ∧TWindowPos;
TwindowPos = packed record
hwnd:HWND; //自身的表單控制代碼
hwndInsertAfeer:HWND;//變更Z的順序時,最上層表單控制代碼
x:Integer//新表單左端的位置
y:Integer//新表單上端的位置
cx:Integer//新表單上端的寬
cy:Integer//新表單上端的高
flags:UINT//變更內容(移動、大小 、Z順序等)
end;
下面具體介紹此方法的使用,首先要確認子表單是否存在,而後確認自身的顯示位置,一旦發生變更就使用WindowsAPI的SetWindowPos,以變更子表單的顯示位置和大小。要注意的是只是主表單發生變更時才使用。
這樣即可使子表單隨主表單的變更而變更。接著我們還希望主表單隨子表單的變更而變更。以下先是主表單變更使子表單變更的處理過程。
const
uFlag = SWP_NOACTIVATE or SWP_NOZORDER or SWP_NOMOVE orSWP_NOSIZE;
//大小變更、位置移動結束通知
porcedure TForm1.WMWindowPosChanged(var Msg:TWMWindowPosChanged);
var Rect:TRect;
x,y,cx,cy:Integer;
uFlag2:UINT;
begin
inherited;
if Form2 <> nil then
//如果子表單存在
begin
GetWindowRoct(Form2.Handle,Rect); //擷取子表單的位置
With Msg do
begin
x:= WindowPos^.x + WindowPos^.cx; //新x座標
y:= Window^.y; //新y座標
cx:= 100; //寬固定在100
cy:= WindowPos^.cy //新高度
end;
uFlag2:= uFlag; //需要移動時
if(Rect.Top <> Y) or(Rect.Left<>X) then
uFlag2:= uFlag2 and(not SWP_NOMOVE);
if(Rect.Bottom -Rect.Top <>cy) or
//需要變更大小時
(Rect.Right -Rect.Left<>cx) then
uFlag2:= uFlag2 and(not SWP_NOSIZE);
if uFlag <> uFlag2 then
//對子表單(Form)進行變更
setWindowPos(Form2.Handle,0,x,y,cx,cy,uFlag2);
以上處理是從主表單的新位置來計運算元表單的新位置,然後與現在的子表單的位置資訊進行比較,如果不同則變更子表單。單純用Wondow API的SetWindowPos對位置進行變更時,Z順序和使用中視窗(Active Window)發生轉換,因此指定SWP_NOACTIVATE標誌和SWP_NOZORDER標誌,阻止其轉換。
從子表單來變更主表單的過程大致與上相同。不同之處在於主表單一定存在,而不需要對其進行檢驗。