MDI支援

多重檔案接口(MDI)是Microsoft Windows檔案處理應用程式的一種規範,該規範描述了視窗結構和允許使用者在單個應用程式中使用多個檔案的使用者接口(如文書處理程式中的文字檔案和電子表格程式中的電子表格)。簡單地說,就像Windows在一個螢幕上維護多個應用程式視窗一樣,MDI應用程式在一個顯示區域內維護多個檔案視窗。Windows中的第一個MDI應用程式是Windows下的Microsoft Excel的第一個版本。緊接著又出現了許多其它的應用程式。

基本介紹

  • 中文名:MDI支援
  • 定義:檔案處理應用程式的一種規範
  • 缺點:MDI應用程式寫起來很困難
  • 組成:一個標題列、一個選單
概念,組成,支援,範例,

概念

儘管MDI規範隨著Windows 2.0的推出已經很普及,但在那時,MDI應用程式寫起來很困難,並且需要一些非常複雜的程式設計工作。從Windows 3.0起,其中許多工作就都由Windows為您做好了。Windows 95中增強的支持也已經被添加進Windows 98和Microsoft Windows NT中。

組成

MDI程式的主應用程式視窗是很普通的:它有一個標題列、一個選單、一個縮放框線、一個系統選單圖示和最大化/最小化/關閉按鈕。顯示區域經常被稱為「工作空間」,它不直接用於顯示程式輸出。這個工作空間包括零個或多個子視窗,每個視窗都顯示一個檔案。
這些子視窗看起來與通常的應用程式視窗以及MDI程式的主視窗很相似。它們有一個標題列、一個縮放框線、一個系統選單圖示和最大化/最小化/關閉按鈕,可能還包括滾動條。但是檔案視窗沒有選單,主應用程式視窗上的選單適用於檔案視窗。
在任何時候都只能有一個檔案視窗是活動的(加亮標題列來表示),它出現在其它所有檔案視窗之前。所有檔案視窗都由工作空間區域加以剪裁,而不會出現在應用程式視窗之外。
初看起來,對Windows程式寫作者來說,MDI似乎是相當簡單。需要程式寫作者做的工作好像就是為每個檔案建立一個WS_CHILD視窗,並使程式的主應用程式視窗成為檔案視窗的父視窗。但對現有的MDI應用程式稍加研究,就會發現一些導致程式寫作困難的複雜問題。例如:
MDI檔案視窗可以最小化。它的圖示出現在工作空間的底部。一般來說,MDI應用程式可以將不同的圖示分別用於主應用程式視窗和每一類檔案套用。  MDI檔案視窗可以最大化。在這種情況下,檔案視窗的標題列(一般用來顯示視窗中檔案的檔案名稱稱)消失,檔案名稱稱出現在應用程式視窗標題列的應用程式名稱之後,檔案視窗的系統選單圖示成為應用程式視窗的頂層選單中的第一項。關閉檔案視窗按鈕變成頂層選單中的最後一項,且出現在最右邊。  用以關閉檔案視窗的系統鍵盤快捷鍵與關閉主視窗的系統鍵盤快捷鍵一樣,只是Ctrl鍵代替了Alt鍵。這也就是說,Alt+F4用於關閉應用程式視窗,而Ctrl+F4用於關閉檔案視窗。此外,Ctrl+F6可以在活動MDI應用程式的子檔案視窗之間切換。與平時一樣,Alt+空格鍵啟動主視窗的系統選單,Alt+-(減號)啟動活動子檔案視窗的系統選單。  當使用游標鍵在選單項間移動時,控制項權通常從系統選單轉到選單列中的第一項。在MDI應用程式中,控制項權是從應用程式系統選單轉到活動檔案系統選單,然後再轉到選單列中的第一項。
如果應用程式能夠支持若干種型態的子視窗(如Microsoft Excel中的工作表和圖表檔案),那么選單應能反映出與這種型態的檔案有關的操作。這就要求當不同的文字視窗變成活動視窗時,程式能更換選單。此外,當沒有檔案視窗存在時,選單應該被縮減到只剩下與打開新檔案有關的操作。  頂層選單上有一個叫做「視窗(Window)」的選單項。按照習慣,這是頂層選單上「Help」之前的那一項,即倒數第二項。「視窗」子選單上通常包含在工作空間內安排檔案視窗的選項。檔案視窗可以從左上方開始「平鋪」或「層迭」。在前一種方式下,可以完整地看到每一個檔案視窗。這個子選單同時也包含所有檔案視窗的列表。從中選擇一個檔案視窗,就可以把此檔案視窗移到前景。
Windows 98支持MDI的所有這些方面。當然,需要您做一些工作(如下面的範例程式所示),但是,這遠不是要您程式寫作來直接支持所有這些功能。

支援

探討Windows的MDI支持時需要發表一些新術語。主應用程式視窗稱為「框架視窗」,就像傳統的Windows程式一樣,它是WS_OVERLAPPEDWINDOW樣式的視窗。
MDI應用程式還根據預先定義的視窗類別MDICLIENT建立「客戶視窗」,這一客戶視窗是用這種視窗類別和WS_CHILD樣式呼叫CreateWindow來建立的。這一呼叫的最後一個參數是指向一個CLIENTCREATESTRUCT型態的結構的指針。這個客戶視窗覆蓋框架視窗的顯示區域,並提供許多MDI支持。此客戶視窗的顏色是系統顏色COLOR_APPWORKSPACE。
檔案視窗被稱為「子視窗」。通過初始化一個MDICREATESTRUCT型態的結構,以一個指向此結構的指針為參數將訊息WM_MDICREATE傳送給客戶視窗,就可以建立這些檔案視窗。
檔案視窗是客戶視窗的子視窗,而客戶視窗又是框架視窗的子視窗。父-子視窗分層結構如圖所示。
 您需要框架視窗的視窗類別(及視窗訊息處理程式)和一個由套用程式支援的每類子視窗的視窗類別(及視窗訊息處理程式)。由於已經預先註冊了視窗類別,所以不需要客戶視窗的視窗訊息處理程式
MDI支援
Windows 98的MDI支援包括一個視窗類別、五個函式、兩個資料結構和12個訊息。前面已經提到了MDI視窗類別,即MDICLIENT,以及資料結構CLIENTCREATESTRUCT和MDICREATESTRUCT。在MDI套用程式中,這五個函式中的兩個用於取代DefWindowProc:不再將DefWindowProc呼叫用於所有未處理的訊息,而是由框架視窗程式呼叫DefFrameProc,子視窗程式呼叫DefMDIChildProc。另一個MDI特有的函式TranslateMDISysAccel與第十章中討論的TranslateAccelerator的使用方式相同。MDI支援也包括ArrangeIconicWindows函式,但有一條專用的MDI訊息使得此函式對MDI程式來說不再必要。
第五個MDI函式是CreateMDIWindow,它使得子視窗可以在單獨的執行緒中被建立。這個函式不需要在單執行緒的程式中,我會展示這一點。
在下面的程式中,我將展示12條MDI訊息中的9條(其他三個訊息一般不用),這些訊息的字首是WM_MDI。框架視窗向客戶視窗傳送其中某個訊息,以便在子視窗上完成一項操作或者取得關於子視窗的資訊(例如,框架視窗傳送一個WM_MDICREATE訊息給客戶視窗,以建立子視窗)。訊息WM_MDIACTIVATE訊息有點特別:框架視窗可以傳送這個訊息給客戶視窗來啟動一個子視窗,而客戶視窗也把這個訊息傳送給將被啟動或者失去活動的子視窗,以便通知它們這一變化。

範例

三個選單
現在讓我們先看看MDIDEMO.RC資源描述檔案,它定義了程式所使用的三個選單模板。
當檔案視窗不存在時,程式顯示MdiMenuInit選單,這個選單只允許使用者建立新檔案或退出程式。
MdiMenuHello選單與顯示「Hello, World!」的檔案視窗相關聯。「File」子選單允許使用者打開任何一類新檔案、關閉活動檔案或退出程式。「Color」子選單允許使用者設定文字顏色。Window子選單包括以平鋪或者重疊的方式安排檔案視窗、安排檔案圖示或關閉所有視窗等選項,這個子選單也列出了它們建立的所有檔案視窗。
MdiMenuRect選單與隨機矩形檔案相關聯。除了不包含「Color」子選單外,它與MdiMenuHello選單一樣。
RESOURCE.H表頭檔案定義所有的選單標識符。另外,以下三個常數定義在MDIDEMO.C中:
#define INIT_MENU_POS 0 #define HELLO_MENU_POS 2 #define RECT_MENU_POS 1
這些標識符說明每個選單模板中Windows子選單的位置。程式需要這些信息來通知客戶視窗檔案列表應出現在哪裡。當然,MdiMenuInit選單沒有Windows子選單,所以如前所述,檔案列表應附加在第一個子選單中(位置0)。不過,實際上永遠不會在此看到檔案列表(在後面討論此程式時,您可以發現這樣做的原因)。
定義在MDIDEMO.C中的IDM_FIRSTCHILD標識符不對應於選單項,它與出現在Windows子選單上的檔案列表中的第一個檔案視窗相關聯。這個標識符的值應當大於所有其它選單ID的值。
程式初始化
在MDIDEMO.C中,WinMain是從註冊框架視窗和兩個子視窗的視窗類別開始的。視窗訊息處理程式是FrameWndProc、HelloWndProc和RectWndProc。一般來說,這些視窗類別應該與不同的圖示相關聯。為了簡單起見,我們將標準IDI_APPLICATION圖示用於框架視窗和子視窗。
注意,我們已經定義了框架視窗類別的WNDCLASS結構的hbrBackground欄位為COLOR_APPWORKSPACE系統顏色。由於框架視窗的顯示區域被客戶視窗所覆蓋並且客戶視窗具有這種顏色,所以上面的定義不是絕對必要的。但是,在最初顯示框架視窗時,使用這種顏色似乎要好一些。
這三種視窗類別中的lpszMenuName欄位都設定為NULL。對「Hello」和「Rect」子視窗類別來說,這是很自然的。對於框架視窗類別,我在建立框架視窗時在CreateWindow函式中給出選單句柄。
「Hello」和「Rect」子視窗的視窗類別將WNDCLASS結構中的cbWndExtra欄位設為非零值來為每個視窗配置額外空間,這個空間將用於儲存指向一個記憶體塊的指針(HELLODATA和RECTDATA結構的大小定義在MDIDEMO.C的開始處),這個記憶體塊被用於儲存每個檔案視窗特有的信息。
下一步,WinMain用LoadMenu載入三個選單,並把它們的句柄儲存到整體變數中。呼叫三次GetSubMenu函式可獲得Windows子選單(檔案列表將加在它上面)的句柄,同樣也把它們儲存到整體變數中。LoadAccelerators函式載入加速鍵表。
在WinMain中呼叫CreateWindow建立框架視窗。在FrameWndProc中WM_CREATE訊息處理期間,框架視窗建立客戶視窗。這項操作涉及到再一次呼叫函式CreateWindow。視窗類別被設定為MDICLIENT,它是預先註冊的MDI顯示區域視窗類別。在Windows中許多對MDI的支持被放入了MDICLIENT視窗類別中。顯示區域視窗訊息處理程式作為框架視窗和不同檔案視窗的中間層。當呼叫CreateWindow建立顯示區域視窗時,最後一個參數必須被設定為指向CLIENTCREATESTRUCT型態結構的指針。這個結構有兩個欄位:
hWindowMenu是要加入檔案列表的子選單的句柄。在MDIDEMO中,它是hMenuInitWindow,是在WinMain期間獲得的。後面將看到如何修改此選單。  idFirstChild是與檔案列表中的第一個檔案視窗相關聯的選單ID。它就是IDM_FIRSTCHILD.  再讓我們回過頭來看看WinMain。MDIDEMO顯示新建立的框架視窗並進入訊息循環訊息循環與正常的循環稍有不同:在呼叫GetMessage從訊息佇列中獲得訊息之後,MDI程式把這個訊息傳送給了TranslateMDISysAccel(以及TranslateAccelerator,如果像MDIDEMO程式一樣,程式本身也有選單快捷鍵的話)。
TranslateMDISysAccel函式把可能對應特定MDI快捷鍵(例如Ctrl-F6)的按鍵轉換成WM_SYSCOMMAND訊息。如果TranslateMDISysAccelTranslateAccelerator都傳回TRUE(表示某個訊息已被這些函式之一轉換),就不能呼叫TranslateMessage和DispatchMessage。
注意傳遞到TranslateMDISysAccelTranslateAccelerator的兩個視窗句柄:hwndClient和hwndFrame。WinMain函式通過用GW_CHILD參數呼叫GetWindow獲得hwndClient視窗句柄。
建立子視窗
FrameWndProc的大部分工作是用於處理通知選單選擇的WM_COMMAND訊息。與平時一樣,FrameWndProc中wParam參數的低字組包含著選單ID。
在選單ID的值為IDM_FILE_NEWHELLO和IDM_FILE_NEWRECT的情況下,FrameWndProc必須建立一個新的檔案視窗。這涉及到初始化MDICREATESTRUCT結構中的欄位(大多數欄位對應於CreateWindow的參數),並將訊息WM_MDICREATE傳送給客戶視窗,訊息的lParam參數設定為指向這個結構的指針。然後由客戶視窗建立子檔案視窗。(也可以使用CreateMDIWindow函式。)
MDICREATESTRUCT結構中的szTitle欄位一般是對應於檔案的檔案名稱稱。樣式欄位設定為視窗樣式WS_HSCROLL、WS_VSCROLL或這兩者的組合,以便在檔案視窗中包括滾動條。樣式欄位也可以包括WS_MINIMIZE或WS_MAXIMIZE,以便在最初時以最小化或最大化狀態顯示檔案視窗。
MDICREATESTRUCT結構的lParam欄位為框架視窗和子視窗共享某些變數提供了一種方法。這個欄位可以設定為含有一個結構的記憶體塊的記憶體句柄。在子檔案視窗的WM_CREATE訊息處理期間,lParam是一個指向CREATESTRUCT結構的指針,這個結構的lpCreateParams欄位是一個指向用於建立視窗的MDICREATESTRUCT結構的指針。
客戶視窗一旦接收到WM_MDICREATE訊息就建立一個子檔案視窗,並把視窗標題加到用於建立客戶視窗的MDICLIENTSTRUCT結構中所指定的子選單的底部。當MDIDEMO程式建立它的第一個檔案視窗時,這個子選單就是「MdiMenuInit」選單中的「File」子選單。後面將看到這個檔案列表將如何移到「MdiMenuHello」和「MdiMenuRect」選單的「Windows」子選單中。
選單上可以列出9個檔案,每個檔案的前面是帶有底線的數字1至9。如果建立的檔案視窗多於9個,則這個清單後跟有「More Windows」選單項。該項啟動帶有清單方塊的對話框,清單方塊列出了所有檔案。這種檔案列表的維護是Windows MDI支持的最好特性之一。
關於框架視窗的訊息處理
在把注意力轉移到子檔案視窗之前,我們先繼續討論FrameWndProc的訊息處理。
當從「File」選單中選擇「Close」時,MDIDEMO關閉活動子視窗。它通過把WM_MDIGETACTIVE訊息傳送給客戶視窗,而獲得活動子視窗的句柄。如果子視窗以WM_QUERYENDSESSION訊息來回響,那么MDIDEMO將WM_MDIDESTROY訊息傳送給客戶視窗,從而關閉子視窗。
處理「File」選單中的「Exit」選項只需要框架視窗訊息處理程式給自己傳送一個WM_CLOSE訊息。
處理Window子選單的「Tile」、「Cascade」和「Arrange」選項是極容易的,只需把訊息WM_MDITILE、WM_MDICASCADE和WM_MDIICONARRANGE傳送給客戶視窗。
處理「Close All」選項要稍微複雜一些。FrameWndProc呼叫EnumChildWindows,傳送一個引用CloseEnumProc函式的指標。此函式把WM_MDIRESTORE訊息傳送給每個子視窗,緊跟著發出WM_QUERYENDSESSION和WM_MDIDESTROY。對圖示平鋪視窗來說並不就此結束,用GW_OWNER參數呼叫GetWindow時,傳回的非NULL值可以顯示出這一點。
FrameWndProc沒有處理任何由「Color」選單中對顏色的選擇所導致的WM_COMMAND訊息,這些訊息應該由檔案視窗負責處理。因此,FrameWndProc把所有未經處理的WM_COMMAND訊息傳送到活動子視窗,以便子視窗可以處理那些與它們有關的訊息。
框架視窗訊息處理程式不予處理的所有訊息都要送到DefFrameProc,它在框架視窗訊息處理程式中取代了DefWindowProc。即使框架視窗訊息處理程式攔截了WM_MENUCHAR、WM_SETFOCUS或WM_SIZE訊息,這些訊息也要被送到DefFrameProc中。
所有未經處理的WM_COMMAND訊息也必須送給DefFrameProc。具體地說,FrameWndProc並不處理任何WM_COMMAND訊息,即使這些訊息是使用者在Windows子選單的檔案列表中選擇檔案時產生的(這些選項的wParam值是以IDM_FIRSTCHILD開始的)。這些訊息要傳送到DefFrameProc,並在那裡進行處理。
注意框架視窗並不需要維護它所建立的所有檔案視窗的視窗句柄清單。如果需要這些視窗句柄(如處理選單上的「Close All」選項時),可以使用EnumChildWindows得到它們。
子檔案視窗
現在看一下HelloWndProc,它是用於顯示「Hello, World!」的子檔案視窗的視窗訊息處理程式。
與用於多個視窗的視窗類別一樣,所有在視窗訊息處理程式(或從該視窗訊息處理程式中呼叫的任何函式)中定義的靜態變數由依據該視窗類別建立的所有視窗共享。
只有對於每個唯一於視窗的數據才必須採用非靜態變數的方法來儲存。這樣的技術要用到視窗屬性。另一種方法(我使用的方法)是使用預留的記憶體空間;可以在註冊視窗類別時將WNDCLASS結構的cbWndExtra欄位設定為非零值以便預留這部分記憶體空間。
MDIDEMO程式使用這個記憶體空間來儲存一個指標,這個指標指向一塊與HELLODATA結構大小相同的記憶體塊。在處理WM_CREATE訊息時,HelloWndProc配置這塊記憶體,初始化它的兩個欄位(它們用於指定目前選中的選單項和文字顏色),並用SetWindowLong將記憶體指針儲存到預留的空間中。
當處理改變文字顏色的WM_COMMAND訊息(回憶一下,這些訊息來自框架視窗訊息處理程式)時,HelloWndProc使用GetWindowLong獲得包含HELLODATA結構的記憶體塊的指針。利用這個結構,HelloWndProc清除原來對選單項的選擇,設定所選選單項為選中狀態,並儲存新的顏色。
當視窗變成活動視窗或不活動的時候,檔案視窗訊息處理程式都會收到WM_MDIACTIVATE訊息(lParam的值是否為這個視窗的句柄表示了該視窗是活動的還是不活動的)。您也許還能記起MDIDEMO程式中有三個不同的選單:當無檔案時為MdiMenuInit;當「Hello」檔案視窗是活動視窗時為MdiMenuHello;當「Rect」檔案視窗為活動視窗時為MdiMenuRect。
WM_MDIACTIVATE訊息為檔案視窗提供了一個修改選單的機會。如果lParam中含有本視窗的句柄(意味著本視窗將變成活動的),那么HelloWndProc就將選單改為MdiMenuHello。如果lParam中包含另一個視窗的句柄,那么HelloWndProc將選單改為MdiMenuInit。
HelloWndProc經由把WM_MDISETMENU訊息傳送給客戶視窗來修改選單,客戶視窗透過從目前選單上刪除檔案列表並把它添加到一個新的選單上來處理這個訊息。這就是檔案列表從MdiMenuInit選單(它在建立第一個檔案時有效)傳送到MdiMenuHello選單中的方法。在MDI應用程式中不要用SetMenu函式改變選單。
另一項工作涉及到「Color」子選單上的選中旗標。像這樣的程式選項對每個檔案來說都是不同的,例如,可以在一個視窗中設定黑色文字,在另一個視窗中設定紅色文字。選單選中旗標應能反映出活動視窗中選擇的選項。由於這種原因,HelloWndProc在視窗變成非活動視窗時清除選中選單項的選中旗標,而當視窗變成活動視窗時設定適當選單項的選中旗標。
WM_MDIACTIVATE的wParam和lParam值分別是失去活動和被啟動視窗的句柄。視窗訊息處理程式得到的第一個WM_MDIACTIVATE訊息的lParam參數被設定為目前視窗的句柄。而當視窗被消除時,視窗訊息處理程式得到的最後一個訊息的lParam參數被設定為另一個值。當使用者從一個檔案切換到另一個檔案時,前一個檔案視窗收到一個WM_MDIACTIVATE訊息,其lParam參數為第一個視窗的句柄(此時,視窗訊息處理程式將選單設定為MdiMenuInit);後一個檔案視窗收到一個WM_MDIACTIVATE訊息,其lParam參數是第二個視窗的句柄(此時,視窗訊息處理程式將選單設定為MdiMenuHello或MdiMenuRect中適當的那個)。如果所有的視窗都關閉了,剩下的選單就是MdiMenuInit。
當使用者從選單中選擇「Close」或「Close All」時,FrameWndProc給子視窗傳送一個WM_QUERYENDSESSION訊息。HelloWndProc將顯示一個訊息框並詢問使用者是否要關閉視窗,以此來處理WM_QUERYENDSESSION和WM_CLOSE訊息(在真實的應用程式中,訊息框會詢問是否需要儲存檔案)。如果使用者表示不能關閉視窗,那么視窗訊息處理程式傳回0。
在WM_DESTROY訊息處理期間,HelloWndProc釋放在WM_CREATE期間配置的記憶體塊。
所有未經處理的訊息必須傳送到用於內定處理的DefMDIChildProc(不是DefWindowProc)。不論子視窗訊息處理程式是否使用了這些訊息,有幾個訊息必須被傳送給DefMDIChildProc。這些訊息是:WM_CHILDACTIVATE、WM_GETMINMAXINFO、WM_MENUCHAR、WM_MOVE、WM_SETFOCUS、WM_SIZE和WM_SYSCOMMAND。
RectWndProc與HelloWndProc非常相似,但是它比HelloWndProc要簡單一些(不含選單選項並且無需使用者確認是否關閉視窗),所以這裡不對它進行討論了。但應該注意到,在處理WM_SIZE之後RectWndProc使用了「break」敘述,所以WM_SIZE訊息被傳給DefMDIChildProc。結束處理:在WinMain中,MDIDEMO使用LoadMenu載入資源描述檔中定義的三個選單。一般說來,當選單所在的視窗被清除時,Windows也要清除與之關聯的選單。對於Init選單,應該清除那些沒有聯繫到視窗的選單。由於這個原因,MDIDEMO在WinMain的末尾呼叫了兩次DestroyMenu來清除Hello和Rect選單。

相關詞條

熱門詞條

聯絡我們