多文件介面(MDI)(轉)

heying1229發表於2007-07-28
多文件介面(MDI):

  本教程告訴你怎樣建立MDI應用程式.事實上並不是很困難.下載例子.

理論:
多文件介面(MDI)是同一時刻處理多個文件的應用程式的一個規範. 你很熟悉記事本.它是單文件介面(SDI)的一個例子.記事本在一個時候只能處理一個文件.假如你希望開啟另一個文件,你首先必須關閉你前面開啟的那一個.你可以想象這有多麻煩. 和Microsoft Word相比:Word可以隨心所欲的在同一時刻開啟任意多個文件,而且可以讓使用者選擇使用哪一個文件.Microsoft Word 是多文件介面(MDI)的一個例子.

MDI應用程式有幾個顯著的特徵:我列舉其中的一些:

有主視窗,在客戶區可以有多個子視窗.所有的子視窗都位於客戶區.
最小化一個子視窗,它最小化到了主視窗的客戶區的左下角.
最大化一個子視窗,它的標題和主視窗的標題合併到了一起.
你可以透過按Ctrl+F4鍵來關閉子視窗,還可以透過按Ctrl+Tab鍵在子視窗之間來切換.
包含子視窗的主視窗被稱為框架視窗.主視窗的客戶區是子視窗活動的地方,因此有了'框架'這個名字.主視窗的任務要比普通視窗精細一些,因為它需要為MDI處理一些協調工作.

為了在你的客戶區控制任意多個數目的子視窗,你需要一個特殊的視窗:客戶視窗.你可以把客戶視窗看成是覆蓋框架視窗的整個客戶區的一個透明的視窗.客戶視窗才是所有MDI子視窗的實際的父親.客戶視窗是MDI子視窗的真實的監督者.

框架視窗


|

客戶視窗

|

--------------------------------------------------------------------------------

|
|
|
|
|

MDI 自視窗1

MDI 自視窗 2

MDI自視窗3

MDI自視窗4

MDI 自視窗 n

圖1.一個MDI應用程式的層次結構

建立框架視窗
現在我們將注意力放到細節上來.首先你需要建立框架視窗. 建立框架視窗的方法和普通視窗是相同的:呼叫CreateWindowEx. 和普通視窗相比,有兩個主要的不同.

第一個不同是你必須呼叫DefFrameProc來處理你的視窗不想處理的視窗資訊而不是呼叫DefWindowProc.這是讓Windows為你作的保持一個MDI應用程式的垃圾任務的一個方法.假如你忘記使用DefFrameProc,你的應用程式將不具有MDI的功能. DefFrameProc具有下列語法:

DefFrameProc proc hwndFrame:DWORD,
hwndClient:DWORD,
uMsg:DWORD,
wParam:DWORD,
lParam:DWORD

假如你將 DefFrameProc 和 DefWindowProc作一個對比,你將會注意到它們之間的唯一的不同在於DefFrameProc有5個引數,而DefWindowProc只有4個.這個增加的引數是客戶視窗的控制程式碼.這個控制程式碼是必須的,有了它Windows才可以傳送MDI-相關的訊息給客戶視窗.

第二個不同是你必須在你的框架視窗的訊息迴圈中呼叫 TranslateMDISysAccel .假如你希望Windows為你處理MDI相關的加速鍵,比如Ctrl+F4,Ctrl+Tab,那麼這是必須的.它具有下列語法:

TranslateMDISysAccel proc hwndClient:DWORD,
lpMsg:DWORD
第一個引數是客戶視窗的控制程式碼.對此你應該不會覺得驚訝.因為客戶視窗是所有MDI子視窗的父親. 第二個引數是你透過呼叫GetMessage獲得的MSG框架的地址. 我們的想法是傳遞MSG結構給客戶視窗,這樣客戶視窗可以檢測在MSG結構中所包含的MDI相關的按鍵是不是按下去了.假如是的話, 客戶視窗處理這個資訊,然後返回一個非零值,否則返回一個假值..

建立框架視窗的步驟總結如下:

像平常一樣填寫 WNDCLASSEX 結構.
透過呼叫 RegisterClassEx註冊框架視窗類.
透過呼叫CreateWindowEx建立框架視窗.
在訊息迴圈中呼叫TranslateMDISysAccel.
在視窗過程中,將未處理的訊息傳遞給 DefFrameProc 而不是DefWindowProc.
建立客戶視窗
現在我們有了框架視窗,我們可以開始建立客戶視窗了. 客戶視窗類是由Windows預先註冊的. 類的名稱為"MDICLIENT". 你同樣也需要將 CLIENTCREATESTRUCT 的地址傳遞給 CreateWindowEx. 這個結構具有以下定義:

CLIENTCREATESTRUCT struct
hWindowMenu dd ?
idFirstChild dd ?
CLIENTCREATESTRUCT ends
hWindowMenu 是子選單的控制程式碼,這個子選單顯示Windows將要新增的MDI子視窗名稱列表. 我們需要對這一功能進行一點解釋.假如你以前曾經使用過類似Microsoft Word的MDI 應用程式,你將會注意到有一個名稱為"視窗"的子選單. 這個選單一旦啟用的話,將會在底部顯示出和視窗管理相關的各種各樣的選單項, 還有當前開啟的MDI子視窗的列表. 這個列表是由Windows自己內部保持的. 你不需要為它作任何特殊的事情. 僅僅只在需要在hWindowMenu 中傳遞你所希望顯示列表的子選單的控制程式碼, Windows 將會處理剩下的事情. 注意這個子選單可以是任何的子選單:它並不一定要是名稱為"視窗"的子選單. 重要的是你應該傳遞你希望顯示視窗列表的子選單的控制程式碼. 假如你不想要這個列表,你就給 hWindowMenu 賦一個NULL的值就行了. 你可以透過呼叫GetSubMenu來獲得子選單的控制程式碼.

idFirstChild 是第一個MDI子視窗的標識號. Windows為應用程式所建立的每一個新的MDI子視窗相應的增加標識號. 舉個例子, 假如你傳遞100給這個域, 第一個MDI子視窗將會有一個值為100的識別符號, 那麼第二個MDI子視窗也就會有一個值為101的識別符號, 如此這樣下去. 當從視窗列表中選擇MDI子視窗時, 被選擇的MDI子視窗的識別符號透過WM_COMMAND傳遞給框架視窗. 正常情況下,你將傳遞"未處理"的WM_COMMAND訊息給DefFrameProc. 我用"未處理"這個詞語,是因為視窗列表中的選單項不是由你的應用程式建立的, 這樣你的應用程式不知道它們的識別符號,而且也沒有它們的控制程式碼. 這是MDI框架視窗又一個特殊的地方. 假如你有視窗列表的話,你必須像這樣來修改你的WM_COMMAND控制程式碼:

.elseif uMsg==WM_COMMAND
.if lParam==0 ; 這條訊息是由選單產生的
mov eax,wParam
.if ax==IDM_CASCADE
.....
.elseif ax==IDM_TILEVERT
.....
.else
invoke DefFrameProc, hwndFrame, hwndClient, uMsg,wParam, lParam
ret
.endif
一般來說,你可以忽略未處理的訊息. 但是在MDI的情況下,如果你忽略它們, 當使用者點選視窗列表中的一個MDI子視窗的名稱時,,這個視窗不會被啟用. 你需要將這些訊息傳遞給DefFrameProc 這樣它們才會得到適當的處理.

idFirstChild 賦值的注意之處: 你不能使用0. 你視窗列表將會表現的不正常. 舉個例子, 即使某一個MDI子視窗被啟用的話, 視窗列表中的這個MDI子視窗名字前的複選標記也不會顯現. 我們可以選擇一個的值,比如100或是一個比100大的值.

給 CLIENTCREATESTRUCT 結構賦值後,你可以透過呼叫 CreateWindowEx 用預先註冊好的類名"MDICLIENT", 在lParam中傳遞CLIENTCREATESTRUCT結構的地址來建立客戶視窗. 你同樣需要在hWndParent引數中指定框架視窗的控制程式碼, 這樣Windows才可以知道框架視窗和客戶視窗之間的父-子關係. 你可以使用的視窗風格有:WS_CHILD ,WS_VISIBLEHE WS_CLIPCHILDREN . 假如你忘了WS_VISIBLE的話, 即使MDI子視窗成功地建立了,你也看不到它們.

以下是建立客戶視窗的步驟:

獲取你所希望顯示視窗列表的子選單的控制程式碼.
將這個選單控制程式碼的值和你希望作為第一個MDI子視窗識別符號的值一起傳送給CLIENTCREATESTRUCT 結構.
呼叫 CreateWindowEx 用類名"MDICLIENT" ,lParam引數為CLIENTCREATESTRUCT結構的地址,
建立MDI子視窗
現在我們既有了框架視窗,也有了客戶視窗. 下一階段可以開始建立MDI子視窗了.有兩種方法:

你可以傳送 WM_MDICREATE訊息給客戶視窗,在wParam引數中傳遞型別MDICREATESTRUCT的結構的地址. 這是常用的也是最簡單的MDI子視窗的建立方法.
.data?
mdicreate MDICREATESTRUCT <>
....
.code
.....
[fill the members of mdicreate]
......
invoke SendMessage, hwndClient, WM_MDICREATE,addr mdicreate,0
假如建立成功的話, SendMessage 將會返回新建立的MDI子視窗的控制程式碼. 你並不需要儲存這個控制程式碼. 如果你需要的話, 你可以透過其它的方法來獲得它. MDICREATESTRUCT有如下定義.

MDICREATESTRUCT STRUCT
szClass DWORD ?
szTitle DWORD ?
hOwner DWORD ?
x DWORD ?
y DWORD ?
lx DWORD ?
ly DWORD ?
style DWORD ?
lParam DWORD ?
MDICREATESTRUCT ENDS

szClass 你作為MDI自視窗模板的視窗類的地址
szTitle 你希望出現在子視窗的標題欄的文字的地址
hOwner 應用程式的例程控制程式碼
x,y,lx,ly 子視窗的左上角的座標以及寬度和高度
style 子視窗風格. 假若你用MDIS_ALLCHILDSTYLES建立子視窗的話,你可以使用任何視窗風格.
lParam 一個應用程式定義的32位值. 這是在MDI視窗中共享值的一種方法. 如果你不需要它, 將它設為NULL.

你可以呼叫 CreateMDIWindow. 這一個功能具有下列語法:
CreateMDIWindow proto lpClassName:DWORD
lpWindowName:DWORD
dwStyle:DWORD
x:DWORD
y:DWORD
nWidth:DWORD
nHeight:DWORD
hWndParent:DWORD
hInstance:DWORD
lParam:DWORD
如果你仔細地看一下這些引數, 你將會發現它們和MDICREATESTRUCT結構的成員是相同的, 除了 hWndParent.以外. 本質上它和你用WM_MDICREATE傳送的引數數目是相同的. MDICREATESTRUCT不需要hWndParent 域, 因為你必須用Sendmessage傳送整個結構給正確的子視窗. .

在這裡,你也許會有一些問題: 我應該使用哪一種方法? 在這兩者之間有什麼區別? 答案如下:

WM_MDICREATE方法建立的MDI子視窗作為呼叫程式碼是同一個執行緒.這意味這假如這個應用程式只有一個主執行緒的話, 所有的MDI子視窗都在這個主執行緒中執行. 這並不是一個大的問題. 但是如果一個或是多個你的MDI子視窗執行一些較長的操作的話, 問題就來了. 想象一下你的整個的應用程式突然之間停止了,對任何事情都沒有反應, 一直持續到MDI子視窗的操作結束.

這個問題正是CreateMDIWindow 設計了所要解決的. CreateMDIWindow 為每一個MDI子視窗建立了一個單獨的執行緒. 這樣假如一個MDI子視窗忙的話, 它不會拖累整個應用程式..

有關MDI子視窗的視窗過程還有一點需要說明的地方. 對於框架視窗, 你不能呼叫DefWindowProc來處理未處理的訊息. 與之相反你必須在自視窗的視窗過程中使用DefMDIChildProc . 這個函式具有和DefWindowProc相同的引數.

除了WM_MDICREATE以外,還有其它的MDI相關的視窗訊息. 列表如下:

WM_MDIACTIVATE 這條訊息由應用程式傳送給客戶視窗,告訴客戶視窗啟用所選擇的MDI子視窗. 當客戶視窗受到訊息後, 它將啟用所選擇的MDI子視窗和傳送WM_MDIACTIVATE訊息給將被啟用的子視窗和將變為非活動視窗的子視窗. 這條訊息的用途是雙方面的:應用程式可以用它來啟用所希望的子視窗.同時它又可以被MDI子視窗本身用作活動/非活動視窗的指示器.舉個例子,假如每一個MDI子視窗都有不同的選單, 那麼當它變為活動或是非活動視窗的時候,它可以利用這個機會來改變框架視窗的選單
WM_MDICASCADE
WM_MDITILE
WM_MDIICONARRANGE 這些訊息處理如何排列MDI子視窗. 舉個例子, 假如你希望MDI子視窗排列成層疊的樣式,傳送WM_MDICASCADE訊息給客戶視窗.
WM_MDIDESTROY 傳送這條訊息給客戶視窗來關閉一個MDI子視窗. 你應該使用這條訊息而不是呼叫DestroyWindow 因為假如這個MDI子視窗最大化的話, th這條訊息將會恢復框架視窗的標題. 假如你使用DestroyWindow, 框架視窗的標題將不會被恢復.
WM_MDIGETACTIVE 傳送這條訊息檢索當前活動MDI子視窗的控制程式碼.
WM_MDIMAXIMIZE
WM_MDIRESTORE 傳送 WM_MDIMAXIMIZE來最大化MDI子視窗和WM_MDIRESTORE來將它恢復成以前的狀態. 對於這些操作總是使用這些訊息. 假如你使用引數為SW_MAXIMIZE來呼叫ShowWindow時,MDI子視窗最大化並沒有問題, 但是當你試圖將它恢復成以前的狀態時,問題就來了. 但是你可以用呼叫ShowWindow來最小化MDI子視窗.
WM_MDINEXT 傳送這條訊息給客戶視窗,根據wParam和lParam裡相應的值來啟用下一個或是前一個MDI子視窗.
WM_MDIREFRESHMENU 傳送這條訊息給客戶視窗來重新整理框架視窗的選單. 注意在傳送了這條訊息之後, 你必須呼叫DrawMenuBar 來更新選單條.
WM_MDISETMENU 傳送這條訊息給客戶視窗來取代框架視窗的整個選單或是視窗子選單. 你必須使用這條訊息而不是用SetMenu. 在傳送了這條訊息之後, 你必須呼叫DrawMenuBar來更新選單條. 正常情況下當活動的MDI子視窗有它自己的選單而且你希望用這個活動的子視窗自身的選單來取代框架視窗的選單時,你將使用這條訊息.

我們將建立一個MDI應用程式的步驟回顧一遍.

註冊視窗類, 既有框架視窗類也有MDI子視窗類.
呼叫CreateWindowEx建立框架視窗.
在訊息迴圈中呼叫TranslateMDISysAccel 來處理MDI相關的加速鍵.
在框架視窗的視窗過程中, 呼叫DefFrameProc 處理所有你的程式碼沒有處理的訊息.
用預選定義好的視窗類名 "MDICLIENT"呼叫CreateWindowEx來建立客戶視窗, 在lParam引數中傳遞CLIENTCREATESTRUCT結構的地址. 正常情況下,你可以用框架視窗過程中的WM_CREATE控制程式碼來建立一個客戶視窗.
相應的要建立MDI子視窗,你可以透過呼叫CreateMDIWindow 來傳送WM_MDICREATE訊息給客戶視窗.
在MDI子視窗的視窗過程中, 我們把所有未處理的訊息傳送給傳遞給DefMDIChildProc.
假如某一條訊息有它的MDI的版本,那我們就使用它的MDI版本. 舉個例子, 我們使用WM_MDIDESTORY訊息, 而不是使用DestroyWindow來關閉一個MDI子視窗.
例子:
.386
.model flat,stdcall
option casemap:none
include masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
includelib masm32libuser32.lib
includelib masm32libkernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU equ 101
IDR_CHILDMENU equ 102
IDM_EXIT equ 40001
IDM_TILEHORZ equ 40002
IDM_TILEVERT equ 40003
IDM_CASCADE equ 40004
IDM_NEW equ 40005
IDM_CLOSE equ 40006

.data
ClassName db "MDIASMClass",0
MDIClientName db "MDICLIENT",0
MDIChildClassName db "Win32asmMDIChild",0
MDIChildTitle db "MDI Child",0
AppName db "Win32asm MDI Demo",0
ClosePromptMessage db "Are you sure you want to close this window?",0

.data?
hInstance dd ?
hMainMenu dd ?
hwndClient dd ?
hChildMenu dd ?
mdicreate MDICREATESTRUCT <>
hwndFrame dd ?

.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
;=============================================
; 註冊框架視窗類
;=============================================
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc,OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
;================================================
; 註冊MDI子視窗類
;================================================
mov wc.lpfnWndProc,offset ChildProc
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszClassName,offset MDIChildClassName
invoke RegisterClassEx,addr wc
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,
WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,0,
hInst,NULL
mov hwndFrame,eax
invoke LoadMenu,hInstance, IDR_CHILDMENU
mov hChildMenu,eax
invoke ShowWindow,hwndFrame,SW_SHOWNORMAL
invoke UpdateWindow, hwndFrame
.while TRUE
invoke GetMessage,ADDR msg,NULL,0,0
.break .if (!eax)
invoke TranslateMDISysAccel,hwndClient,addr msg
.if !eax
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endif
.endw
invoke DestroyMenu, hChildMenu
mov eax,msg.wParam
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL ClientStruct:CLIENTCREATESTRUCT
.if uMsg==WM_CREATE
invoke GetMenu,hWnd
mov hMainMenu,eax
invoke GetSubMenu,hMainMenu,1
mov ClientStruct.hWindowMenu,eax
mov ClientStruct.idFirstChild,100
INVOKE CreateWindowEx,NULL,ADDR MDIClientName,NULL,
WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,
hInstance,addr ClientStruct
mov hwndClient,eax
;=======================================
; 初始化MDICREATESTRUCT結構
;=======================================
mov mdicreate.szClass,offset MDIChildClassName
mov mdicreate.szTitle,offset MDIChildTitle
push hInstance
pop mdicreate.hOwner
mov mdicreate.x,CW_USEDEFAULT
mov mdicreate.y,CW_USEDEFAULT
mov mdicreate.lx,CW_USEDEFAULT
mov mdicreate.ly,CW_USEDEFAULT
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_EXIT
invoke SendMessage,hWnd,WM_CLOSE,0,0
.elseif ax==IDM_TILEHORZ
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
.elseif ax==IDM_TILEVERT
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
.elseif ax==IDM_CASCADE
invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0
.elseif ax==IDM_NEW
invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate
.elseif ax==IDM_CLOSE
invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
invoke SendMessage,eax,WM_CLOSE,0,0
.else
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_MDIACTIVATE
mov eax,lParam
.if eax==hChild
invoke GetSubMenu,hChildMenu,1
mov edx,eax
invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx
.else
invoke GetSubMenu,hMainMenu,1
mov edx,eax
invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx
.endif
invoke DrawMenuBar,hwndFrame
.elseif uMsg==WM_CLOSE
invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
.if eax==IDYES
invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
.endif
.else
invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
ChildProc endp
end start

分析:
程式所做的第一件事情就是註冊框架視窗類和MDI子視窗類. 作完這個以後, 程式呼叫CreateWindowEx來建立框架視窗.用框架視窗的WM_CREATE控制程式碼來建立客戶視窗:


LOCAL ClientStruct:CLIENTCREATESTRUCT
.if uMsg==WM_CREATE
invoke GetMenu,hWnd
mov hMainMenu,eax
invoke GetSubMenu,hMainMenu,1
mov ClientStruct.hWindowMenu,eax
mov ClientStruct.idFirstChild,100
invoke CreateWindowEx,NULL,ADDR MDIClientName,NULL,
WS_CHILD or WS_VISIBLE or WS_CLIPCHILDREN,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,NULL,
hInstance,addr ClientStruct
mov hwndClient,eax

我們呼叫GetMenu來獲得框架視窗的選單的控制程式碼, 這個控制程式碼在呼叫GetSubMenu時將會被用到. 注意我們將1傳遞給 GetSubMenu ,因為我們希望顯示視窗列表的子選單是第二個子選單. 然後我們給CLIENTCREATESTRUCT結構的成員賦值.
下一步我們初始化MDICLIENTSTRUCT 結構. 注意我們不需要在這裡做.要初始化MDICLIENTSTRUCT結構的話,用WM_CREATE訊息比較方便.

mov mdicreate.szClass,offset MDIChildClassName
mov mdicreate.szTitle,offset MDIChildTitle
push hInstance
pop mdicreate.hOwner
mov mdicreate.x,CW_USEDEFAULT
mov mdicreate.y,CW_USEDEFAULT
mov mdicreate.lx,CW_USEDEFAULT
mov mdicreate.ly,CW_USEDEFAULT

在框架視窗建立之後(也包括客戶視窗), 我們呼叫LoadMenu從資源中獲取子視窗的選單.我們需要獲取這個選單的控制程式碼,這樣當一個MDI子選單出現時,我們就可以用這個控制程式碼來取代框架視窗的選單. 在這個應用程式退出到Windows之前不要忘記呼叫DestroyMenu來去掉這個控制程式碼. 正常情況下當一個應用程式退出的時候,Windows將會自動釋放和視窗相關的選單. 但是在這種情況下, 子視窗的選單沒有和任何視窗相關聯, 這樣即使當應用程式退出後半部 子視窗的選單仍然會佔用寶貴的記憶體資源.

 

invoke LoadMenu,hInstance, IDR_CHILDMENU
mov hChildMenu,eax
........
invoke DestroyMenu, hChildMenu

在訊息迴圈中我們呼叫TranslateMDISysAccel.

.while TRUE
invoke GetMessage,ADDR msg,NULL,0,0
.break .if (!eax)
invoke TranslateMDISysAccel,hwndClient,addr msg
.if !eax
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endif
.endw
假如TranslateMDISysAccel 返回一個非零值, 它以為著Windows已經在處理這條訊息.這樣你就不需要為這條訊息做任何事情了.假如返回的值為零, 那麼這條訊息就不是MDI相關的訊息, 因此就必須按照通常情況來處理.


WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.....
.else
invoke DefFrameProc,hWnd,hwndClient,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
注意到在框架視窗的視窗過程中, 我們呼叫DefFrameProc來處理我們不感興趣的訊息.

視窗過程的重要之處在 WM_COMMAND控制程式碼. 當使用者從檔案選單中選擇 "New"時, 我們就建立了一個MDI子視窗.

.elseif ax==IDM_NEW
invoke SendMessage,hwndClient,WM_MDICREATE,0,addr mdicreate

在我們的例子中,我們透過傳送WM_MDICREATE訊息給客戶視窗, 同時還要在lParam引數中傳遞MDICREATESTRUCT結構的地址來建立一個MDI子視窗.

ChildProc proc hChild:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
.if uMsg==WM_MDIACTIVATE
mov eax,lParam
.if eax==hChild
invoke GetSubMenu,hChildMenu,1
mov edx,eax
invoke SendMessage,hwndClient,WM_MDISETMENU,hChildMenu,edx
.else
invoke GetSubMenu,hMainMenu,1
mov edx,eax
invoke SendMessage,hwndClient,WM_MDISETMENU,hMainMenu,edx
.endif
invoke DrawMenuBar,hwndFrame
當MDI子視窗建立後, 我們可以監視 WM_MDIACTIVATE以察看它是不是一個活動視窗.具體的方法是比較將某一個MDI子視窗的控制程式碼和引數lParam的值進行比較, 引數lParam中包含的是活動子視窗的控制程式碼. 這樣如果兩者匹配的話, 證明這個MDI子視窗就是活動子視窗. 下一步就是將框架視窗的選單替換成MDI子視窗自身的選單. 因為最初的選單將會被取代, 你比學趕幫超再一次告訟Windows視窗列表將顯示在哪一個子選單. 這就是我們必須再一次呼叫GetSubMenu 來檢索子選單的控制程式碼的原因. 我們傳送 WM_MDISETMENU訊息給客戶視窗來獲得想要的結果. WM_MDISETMENU中的wParam引數包含了你希望取代最初的選單的選單控制程式碼. lParam引數包含的是你希望用來顯示視窗列表的子選單的控制程式碼.在傳送了WM_MDISETMENU之後, 我們呼叫l DrawMenuBar 來重新整理選單, 否則的話你的選單將會是一片混亂.

.else
invoke DefMDIChildProc,hChild,uMsg,wParam,lParam
ret
.endif
在MDI子視窗的視窗過程中, 你必須傳送所有未處理的訊息給DefMDIChildProc而不是DefWindowProc.
.elseif ax==IDM_TILEHORZ
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_HORIZONTAL,0
.elseif ax==IDM_TILEVERT
invoke SendMessage,hwndClient,WM_MDITILE,MDITILE_VERTICAL,0
.elseif ax==IDM_CASCADE
invoke SendMessage,hwndClient,WM_MDICASCADE,MDITILE_SKIPDISABLED,0
當使用者在視窗子選單中選擇一個選單項時, 我們傳送相應的訊息給客戶視窗. 假如使用者選擇平鋪視窗, 我們傳送 WM_MDITILE 給客戶視窗, 在wParam引數中指定哪一種型別的平鋪. 選擇重疊的話是類似的, 相應的傳送WM_MDICASCADE..

.elseif ax==IDM_CLOSE
invoke SendMessage,hwndClient,WM_MDIGETACTIVE,0,0
invoke SendMessage,eax,WM_CLOSE,0,0
假如使用者選擇 "Close" 選單項, 我們首先必須透過傳送WM_MDIGETACTIVE給客戶視窗來獲得當前活動的MDI子視窗的控制程式碼, 返回的值儲存在eax暫存器中, 這個值就是當前活動MDI子視窗的控制程式碼. 獲得控制程式碼之後, 我們就可以傳送WM_CLOSE給那個視窗了.

.elseif uMsg==WM_CLOSE
invoke MessageBox,hChild,addr ClosePromptMessage,addr AppName,MB_YESNO
.if eax==IDYES
invoke SendMessage,hwndClient,WM_MDIDESTROY,hChild,0
.endif
在MDI子視窗的視窗過程中, 當收到WM_CLOSE的訊息時, 就會顯示一個訊息框詢問使用者是否確實想關閉著這個視窗. 假如回答是"是"的話, 我們傳送WM_MDIDESTROY給客戶視窗. WM_MDIDESTROY關閉MDI子視窗,然後恢復框架視窗的標題.

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10172717/viewspace-928789/,如需轉載,請註明出處,否則將追究法律責任。

相關文章