📄 19. 多重文件界面.txt
字号:
SendMessage (hwndClient, WM_MDISETMENU, (WPARAM) hMenuInit, (LPARAM) hMenuInitWindow) ;
DrawMenuBar (hwndFrame) ;
return 0 ;
case WM_DESTROY:
pRectData = (PRECTDATA) GetWindowLong (hwnd, 0) ;
HeapFree (GetProcessHeap (), 0, pRectData) ;
KillTimer (hwnd, 1) ;
return 0 ;
}
// Pass unprocessed message to DefMDIChildProc
return DefMDIChildProc (hwnd, message, wParam, lParam) ;
}
MDIDEMO.RC (摘录)
//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Menu
MDIMENUINIT MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New &Hello", IDM_FILE_NEWHELLO
MENUITEM "New &Rectangle", IDM_FILE_NEWRECT
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_APP_EXIT
END
END
MDIMENUHELLO MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New &Hello", IDM_FILE_NEWHELLO
MENUITEM "New &Rectangle", IDM_FILE_NEWRECT
MENUITEM "&Close", IDM_FILE_CLOSE
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_APP_EXIT
END
POPUP "&Color"
BEGIN
MENUITEM "&Black", IDM_COLOR_BLACK
MENUITEM "&Red", IDM_COLOR_RED
MENUITEM "&Green", IDM_COLOR_GREEN
MENUITEM "B&lue", IDM_COLOR_BLUE
MENUITEM "&White", IDM_COLOR_WHITE
END
POPUP "&Window"
BEGIN
MENUITEM "&Cascade\tShift+F5", IDM_WINDOW_CASCADE
MENUITEM "&Tile\tShift+F4", IDM_WINDOW_TILE
MENUITEM "Arrange &Icons", IDM_WINDOW_ARRANGE
MENUITEM "Close &All", IDM_WINDOW_CLOSEALL
END
END
MDIMENURECT MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "New &Hello", IDM_FILE_NEWHELLO
MENUITEM "New &Rectangle", IDM_FILE_NEWRECT
MENUITEM "&Close", IDM_FILE_CLOSE
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_APP_EXIT
END
POPUP "&Window"
BEGIN
MENUITEM "&Cascade\tShift+F5", IDM_WINDOW_CASCADE
MENUITEM "&Tile\tShift+F4", IDM_WINDOW_TILE
MENUITEM "Arrange &Icons", IDM_WINDOW_ARRANGE
MENUITEM "Close &All", IDM_WINDOW_CLOSEALL
END
END
/////////////////////////////////////////////////////////////////////////////
// Accelerator
MDIDEMO ACCELERATORS DISCARDABLE
BEGIN
VK_F4, IDM_WINDOW_TILE, VIRTKEY, SHIFT, NOINVERT
VK_F5, IDM_WINDOW_CASCADE, VIRTKEY, SHIFT, NOINVERT
END
RESOURCE.H (摘录)
// Microsoft Developer Studio generated include file.
// Used by MDIDemo.rc
#define IDM_FILE_NEWHELLO 40001
#define IDM_FILE_NEWRECT 40002
#define IDM_APP_EXIT 40003
#define IDM_FILE_CLOSE 40004
#define IDM_COLOR_BLACK 40005
#define IDM_COLOR_RED 40006
#define IDM_COLOR_GREEN 40007
#define IDM_COLOR_BLUE 40008
#define IDM_COLOR_WHITE 40009
#define IDM_WINDOW_CASCADE 40010
#define IDM_WINDOW_TILE 40011
#define IDM_WINDOW_ARRANGE 40012
#define IDM_WINDOW_CLOSEALL 40013
MDIDEMO支持两种型态的非常简单的文件窗口:第一种窗口在它的显示区域中央显示"Hello, World!",另一种窗口显示一系列随机矩形(在原始码列表和标识符名中,它们分别叫做「Hello」文件和「Rect」文件)。这两类文件窗口的菜单不同,显示"Hello, World!"的文件窗口有一个允许使用者修改文字颜色的菜单。
三个菜单
现在让我们先看看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消息。如果TranslateMDISysAccel或TranslateAccelerator都传回TRUE(表示某个消息已被这些函数之一转换),就不能呼叫TranslateMessage和DispatchMessage。
注意传递到TranslateMDISysAccel和TranslateAccelerator的两个窗口句柄: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」菜单。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -