程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 匯編語言 >> 匯編教程:多文檔界面(MDI)

匯編教程:多文檔界面(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 \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.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子窗口,然後恢復框架窗口的標題.

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