📄 19. 多重文件界面.txt
字号:
多重文件界面
智能中国——游戏组 整理编译
--------------------------------------------------------------------------------
多重文件接口(MDI)是Microsoft Windows文件处理应用程序的一种规范,该规范描述了窗口结构和允许使用者在单个应用程序中使用多个文件的使用者接口(如文书处理程序中的文字文件和电子表格程序中的电子表格)。简单地说,就像Windows在一个屏幕上维护多个应用程序窗口一样,MDI应用程序在一个显示区域内维护多个文件窗口。Windows中的第一个MDI应用程序是Windows下的Microsoft Excel的第一个版本。紧接着又出现了许多其它的应用程序。
MDI 概念
尽管MDI规范随着Windows 2.0的推出已经很普及,但在那时,MDI应用程序写起来很困难,并且需要一些非常复杂的程序设计工作。从Windows 3.0起,其中许多工作就都由Windows为您做好了。Windows 95中增强的支持也已经被添加进Windows 98和Microsoft Windows NT中。
MDI 的组成
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的所有这些方面。当然,需要您做一些工作(如下面的范例程序所示),但是,这远不是要您程序写作来直接支持所有这些功能。
MDI支援
探讨Windows的MDI支持时需要发表一些新术语。主应用程序窗口称为「框架窗口」,就像传统的Windows程序一样,它是WS_OVERLAPPEDWINDOW样式的窗口。
MDI应用程序还根据预先定义的窗口类别MDICLIENT建立「客户窗口」,这一客户窗口是用这种窗口类别和WS_CHILD样式呼叫CreateWindow来建立的。这一呼叫的最后一个参数是指向一个CLIENTCREATESTRUCT型态的结构的指针。这个客户窗口覆盖框架窗口的显示区域,并提供许多MDI支持。此客户窗口的颜色是系统颜色COLOR_APPWORKSPACE。
文件窗口被称为「子窗口」。通过初始化一个MDICREATESTRUCT型态的结构,以一个指向此结构的指针为参数将消息WM_MDICREATE发送给客户窗口,就可以建立这些文件窗口。
文件窗口是客户窗口的子窗口,而客户窗口又是框架窗口的子窗口。父-子窗口分层结构如图19-1所示。
图19-1 Windows 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消息有点特别:框架窗口可以发送这个消息给客户窗口来启动一个子窗口,而客户窗口也把这个消息发送给将被启动或者失去活动的子窗口,以便通知它们这一变化。
MDI 的范例程序
程序19-1 MDIDEMO程序说明了编写MDI应用程序的基本方法。
程序19-1 MDIDEMO
MDIDEMO.C
/*---------------------------------------------------------------------------
MDIDEMO.C -- Multiple-Document Interface Demonstration
(c) Charles Petzold, 1998
---------------------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
#define INIT_MENU_POS 0
#define HELLO_MENU_POS 2
#define RECT_MENU_POS 1
#define IDM_FIRSTCHILD 50000
LRESULT CALLBACK FrameWndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK CloseEnumProc (HWND, LPARAM) ;
LRESULT CALLBACK HelloWndProc (HWND, UINT, WPARAM, LPARAM) ;
LRESULT CALLBACK RectWndProc (HWND, UINT, WPARAM, LPARAM) ;
// structure for storing data unique to each Hello child window
typedef struct tagHELLODATA
{
UINT iColor ;
COLORREF clrText ;
}
HELLODATA, * PHELLODATA ;
// structure for storing data unique to each Rect child window
typedef struct tagRECTDATA
{
short cxClient ;
short cyClient ;
}
RECTDATA, * PRECTDATA ;
// global variables
TCHAR szAppName[] = TEXT ("MDIDemo") ;
TCHAR szFrameClass[] = TEXT ("MdiFrame") ;
TCHAR szHelloClass[] = TEXT ("MdiHelloChild") ;
TCHAR szRectClass[] = TEXT ("MdiRectChild") ;
HINSTANCE hInst ;
HMENU hMenuInit, hMenuHello, hMenuRect ;
HMENU hMenuInitWindow, hMenuHelloWindow, hMenuRectWindow ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
HACCEL hAccel ;
HWND hwndFrame, hwndClient ;
MSG msg ;
WNDCLASS wndclass ;
hInst = hInstance ;
// Register the frame window class
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = FrameWndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE + 1) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szFrameClass ;
if (!RegisterClass (&wndclass))
{
MessageBox ( NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
// Register the Hello child window class
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = HelloWndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = sizeof (HANDLE) ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szHelloClass ;
RegisterClass (&wndclass) ;
// Register the Rect child window class
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = RectWndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = sizeof (HANDLE) ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szRectClass ;
RegisterClass (&wndclass) ;
// Obtain handles to three possible menus & submenus
hMenuInit = LoadMenu (hInstance, TEXT ("MdiMenuInit")) ;
hMenuHello = LoadMenu (hInstance, TEXT ("MdiMenuHello")) ;
hMenuRect = LoadMenu (hInstance, TEXT ("MdiMenuRect")) ;
hMenuInitWindow = GetSubMenu (hMenuInit, INIT_MENU_POS) ;
hMenuHelloWindow = GetSubMenu (hMenuHello, HELLO_MENU_POS) ;
hMenuRectWindow = GetSubMenu (hMenuRect, RECT_MENU_POS) ;
// Load accelerator table
hAccel = LoadAccelerators (hInstance, szAppName) ;
// Create the frame window
hwndFrame = CreateWindow (szFrameClass, TEXT ("MDI Demonstration"),
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, hMenuInit, hInstance, NULL) ;
hwndClient = GetWindow (hwndFrame, GW_CHILD) ;
ShowWindow (hwndFrame, iCmdShow) ;
UpdateWindow (hwndFrame) ;
// Enter the modified message loop
while (GetMessage (&msg, NULL, 0, 0))
{
if ( !TranslateMDISysAccel (hwndClient, &msg) &&
!TranslateAccelerator (hwndFrame, hAccel, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -