http://www.codeproject.com/KB/winsdk/Sigma.aspx
使用Win32 API 实现MDI程序
介绍
这篇文章讲解了如何使用Win32 API创建一个基本的MDI程序。在这个例子中,被创建的MDI子窗体显示了如下
操作系统信息:计算机名、操作系统版本、补丁版本,CPU个数。
此外,这篇文章还包括:
*当打开子窗体时,更新菜单。
*创建多分割区域的状态栏。
*添加、移除托盘。
*使用邮槽在进程间通讯。
基于Win32 API 创建MDI程序
在WinMain()函数中,创建MDI框架窗体。在WM_CREATE处理代码中,创建了MDI的客户区。所有的子窗体
在客户区中浮动。
CLIENTCREATESTRUCT MDIClientCreateStruct;
// Structure to be used for MDI client area
switch(message)
{
case WM_CREATE:
// On creation of main frame, create the MDI client area
MDIClientCreateStruct.hWindowMenu = NULL;
MDIClientCreateStruct.idFirstChild = IDM_FIRSTCHILD;
ghMDIClientArea = CreateWindow(TEXT("MDICLIENT"), // predefined value for
// MDI client area
NULL, // no caption required
WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
0, // No need to give any x/y or height/width.
0,
0,
0,
hwnd,
NULL,
ghInstance,
(void*) &MDIClientCreateStruct);
MDICLIENT 是第一个参数,预先被Windows定义。CreateWindow的最后一个参数是CLIENTCREATESTRUCT类型
的变量的指针。CLIENTCREATESTRUCT有两个成员:一个是程序窗体的菜单句柄,另一个是第一个子窗体的标识符。
当得到了命令消息ID_INFORMATION_SYSTEMINFORMATION 后,,创建显示系统信息的MDI子窗体。在堆上创
建CSystemInfo对象。G_pSystemInfo对象被用来创建窗体和填充树控件。CSystemInfo::CreateSystemInfoWindow()
注册了子窗体并创建了MDI子窗体。
MDICREATESTRUCT MDIChildCreateStruct;
MDIChildCreateStruct.szClass = TEXT("SigmaSystemInfoWnd");
MDIChildCreateStruct.szTitle = TEXT("System Information");
MDIChildCreateStruct.hOwner = ghInstance;
MDIChildCreateStruct.x = CW_USEDEFAULT;
MDIChildCreateStruct.y = CW_USEDEFAULT;
MDIChildCreateStruct.cx = CW_USEDEFAULT;
MDIChildCreateStruct.cy = CW_USEDEFAULT;
MDIChildCreateStruct.style = 0;
MDIChildCreateStruct.lParam = 0;
//
m_hwndSystemInformation = (HWND) SendMessage(ghMDIClientArea,
WM_MDICREATE,
0,
(LPARAM) (LPMDICREATESTRUCT) &MDIChildCreateStruct);
// return if its not possible to create the child window
if(NULL == m_hwndSystemInformation)
{
return 0;
}
MDICREATESTRUCT用来设置子窗体参数。SendMessage最后一个参数是它的指针。SendMessage发送WM_MDICREATE消息到主框架窗体的客户区。
一些类的功能
CSystemInfo:这个类有两个成员:CSystemInfoData成员,CSystemInfoView成员。在这个类中,创建了这个子窗体。
CSystemInfoData;这个类储存了树控件要显示的数据。
CSystemInfoView:这个类初始化了树控件,然后将CSystemInfoData中的数据填充到Tree中。
当子窗体被激活时,创建并改变菜单
框架窗体的菜单的资源是IDR_MAINFRAME_MENU.这个菜单在WinMain中加载。
ghMainFrameMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAINFRAME_MENU));
ghSysInfoMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_SYSINFO_MENU));
DWORD derror = GetLastError();
//Create the main MDI frame window
ghwndMainFrame = CreateWindow(gszSigmaFrameClassName,
gszAppName,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, // allows system choose an x position
CW_USEDEFAULT, // allows system choose a y position
CW_USEDEFAULT, // width, CW_USEDEFAULT allows system to
// choose height and width
CW_USEDEFAULT, // height, CW_USEDEFAULT ignores heights
// as this is set by setting
// CW_USEDEFAULT in width above.
NULL, // handle to parent window
ghMainFrameMenu, // handle to menu
hInstance, // handle to the instance of module
NULL); // Long pointer to a value to be passed
// to the window through the
// CREATESTRUCT structure passed in the
// lParam parameter the WM_CREATE message
子窗口菜单是IDR_SYSINFO_MENU。在子窗体的窗口函数中(SigmaSystemInfoWndProc)中有WM_MDIACTIVATE,此段代码用来修改子窗的菜单,
名为Window的菜单被添加。当子窗体失去焦点时,它将设置而框架窗体的菜单。DrawMenuBar()需要调用SendMessage发送WM_MDISETMENU
消息。一旦子窗体被打开,它的菜单由于调用EnableMenuItem而变灰。
case WM_MDIACTIVATE:
{
HWND hwndClient = GetParent(hWnd);
HWND hwndFrame = GetParent(hwndClient);
HMENU hSysInfoWindowMenu = GetSubMenu(ghSysInfoMenu, SIGMA_SYSINFO_WINDOW_MENU_POS) ;
// Set the system info menu when getting activated
if (lParam == (LPARAM) hWnd)
{
SendMessage(hwndClient,
WM_MDISETMENU,
(WPARAM) ghSysInfoMenu,
(LPARAM) hSysInfoWindowMenu);
//Gray out the system information menu item
EnableMenuItem(ghSysInfoMenu, ID_INFORMATION_SYSTEMINFORMATION, MF_GRAYED);
}
// Set the frame window menu when losing focus
if (lParam != (LPARAM) hWnd)
{
SendMessage(hwndClient,
WM_MDISETMENU,
(WPARAM) ghMainFrameMenu,
(LPARAM) NULL) ;
}
// call DrawMenuBar after the menu items are set
DrawMenuBar(hwndFrame);
状态栏和重排列的MDI客户区
在WinMain函数中,CreateStatusBar函数创建了状态栏。CreateWindowEx使用STATUSCLASSNAME创建了状态栏,如下所示
//Create the status bar
ghwndStatusBar = CreateWindowEx(0, // extended not required
STATUSCLASSNAME, // status bar class name, equivalent to
// "msctls_statusbar32"
"", //caption not required
WS_CHILD | WS_VISIBLE,
-100, // x
-100, // y
10, // width
10, // height
ghwndMainFrame,
NULL,
(HINSTANCE) GetWindowLong (ghwndMainFrame, GWL_HINSTANCE),
NULL);
调用SendMessage(ghwndStatusBar, SB_SETPARTS, (WPARAM)nParts, (LPARAM)lpParts).详细过程如下:
//Create parts to the status bar
RECT rcClient;
LPINT lpParts = NULL;
int nWidth = 0;
int nParts = SIGMA_STATUSBAR_PARTS;
// Get the coordinates of the parent window's client area.
GetClientRect(ghwndMainFrame, &rcClient);
// Allocate an array for holding the right edge coordinates.
HLOCAL hloc = NULL;
hloc = LocalAlloc(LHND, sizeof(int) * nParts);
lpParts = (int *)LocalLock(hloc);
// Calculate the right edge coordinate for each part, and
// copy the coordinates to the array.
nWidth = rcClient.right / nParts;
for (int i = 0; i < nParts; i++)
{
lpParts[i] = nWidth;
nWidth += nWidth;
}
// Create status bar parts.
SendMessage(ghwndStatusBar, SB_SETPARTS, (WPARAM)nParts, (LPARAM)lpParts);
// Free the array
LocalUnlock(hloc);
LocalFree(hloc);
添加系统托盘
在WinMain函数中,调用AddSysTrayIcon函数创建系统托盘。Shell_NotifyIcon添加了系统托盘的图标。
NOTIFYICONDATA nid = {0};
nid.cbSize = sizeof(nid);
nid.uID = SIGMA_SYSTRAY_ICON_ID; // 0 to 12 are reserved and should not be used.
nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
nid.hIcon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_SIGMA_MAIN_ICON));
strcpy(nid.szTip, "Sigma");
nid.hWnd = ghwndMainFrame;
nid.uCallbackMessage = WM_SIGMA_SYSTRAY_MSG;
//Add the notification to the tray
Shell_NotifyIcon(NIM_ADD, &nid);
当关闭程序时,调用Shell_Notify(NIM_DELETE,&nid)移除托盘图标。
使用邮槽进行进程间通讯(IPC with Mailslot IPC = inter-process communication)
Me:与MDI窗体没有关系,不翻译了。
额外信息
*调用InitCommonControlsEx()函数初始化通用控件。这里创建了tree控件。
*框架窗体WM_SIZE的消息处理,不能调用DefFrameProc。因为调用DefFrameProc函数将引起MDI客户
区改变大小且重写状态栏。
*程序仅在XP SP3下通过测试。