I. Description▲
Une application MDI, c'est une application avec une fenêtre principale qui peut contenir
des fenêtres redimensionnables.
Le code présenté dans ce tutoriel est le code minimum pour une application MDI.
L'application de fait rien à par céer et gérer les fenêtres enfants.
Le procédé est sensiblement le même que pour une simple fenêtre redimensionnables,
mise à part que ce n'est pas la fenêtre principale qui contient les fenêtres enfants,
mais une fenêtre spéciale dont le nom de classe de fenêtre est MDICLIENT
que nous créerons lors de la création de la fenêtre principale.
Cette fenêtre est déjà programmée pour occuper toute la surface client de la fenêtre principale
et gérer les fenêtres enfants qui lui sont rattachées.
Les fenêtres enfants quand à elles, seront crées avec la fonction CreateMDIWindow, après avoir
préparer et enregistrer leur classe de fenêtre nommée "ChildWinClass" dans l'exemple.
La boucle de message est légèrement modifiée afin de traiter et d'envoyer les raccourcis clavier destinés
aux fenêtres enfants avec l'appel à la fonction TranslateMDISysAccel.
while
(
GetMessage
(&
msg, NULL
, 0
, 0
))
{
if
(!
TranslateMDISysAccel
(
hwnd_mdi, &
msg))
{
TranslateMessage
(&
msg);
DispatchMessage
(&
msg);
}
}
Pour les applications MDI, la procédure de fenêtre par défaut de la fenêtre principale est
DefFrameProc au lieu de DefWindowProc. Celle des fenêtres enfants est DefMDIChildProc.
Lors de la création de la fenêtre MDICLIENT on envoi des données supplémentaires
via une structure CLIENTCREATESTRUCT leur rôles est d'ajouter au menu les options d'activation des
fenêtres enfants.
case
WM_CREATE :
{
CLIENTCREATESTRUCT ccs;
ccs.hWindowMenu =
GetSubMenu
(
GetMenu
(
hwnd), 1
);
ccs.idFirstChild =
IDM_WINCHILD;
hwnd_mdi =
CreateWindow
(
"
MDICLIENT
"
, (
LPCTSTR) NULL
,
WS_CHILD |
WS_CLIPCHILDREN |
WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hwnd, NULL
, hinst, &
ccs);
return
0
;
}
Il faut donc le handle du sous menu et l'ID de menu de la première fenêtre IDM_WINCHILD dans l'exemple. L'ID de menu des suivantes sera l'ID de la précédente + 1. (Donc attention à ne pas les utiliser pour d'autre ID de menu ou commande). On obtient l'handle du sous menu à l'aide de la fonction GetSubMenu (mettre le numéro d'ordre du sous menu désiré en son second paramètre). Il faut envoyer l'adresse de la structure à CreateWindow (dernier paramètre).
Veiller à envoyer les commandes non traitées à la procédure de fenêtre par défaut.
Les fenêtres enfants seront crées avec la fonction CreateMDIWindow dont l'utilisation est fort semblable à CreateWindow. (Dans le code d'exemple je ne récupère pas les handles de fenêtre retourné par la fonction, mais
il va de soit que si vous en avez besoin il faudra le faire).
J'ai mis le code de la procédure de fenêtre des fenêtres enfants, nommé ChildWndProc dans l'exemple. Il ne fait rien
à part envoyer les messages à la procédure de fenêtre par défaut.
II. Code complet▲
#include <windows.h>
#include "menu.h"
#define IDM_WINCHILD 2000
HINSTANCE hinst;
HWND hwnd_mdi;
LRESULT CALLBACK MainWndProc
(
HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildWndProc
(
HWND, UINT, WPARAM, LPARAM);
int
WINAPI WinMain
(
HINSTANCE hinstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int
nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS wc={
0
}
;
hinst =
hinstance;
wc.lpfnWndProc =
MainWndProc;
wc.hInstance =
hinstance;
wc.hCursor =
LoadCursor
(
NULL
, IDC_ARROW);
wc.hbrBackground =
(
HBRUSH)(
COLOR_BTNFACE+
1
);
wc.lpszClassName =
"
MaWinClass
"
;
RegisterClass
(&
wc);
wc.lpfnWndProc =
ChildWndProc;
wc.lpszClassName =
"
ChildWinClass
"
;
RegisterClass
(&
wc);
HMENU hmenu =
LoadMenu
(
hinst, "
LEMENU
"
);
hwnd =
CreateWindow
(
"
MaWinClass
"
, "
MDI Application
"
,
WS_OVERLAPPEDWINDOW |
WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT, 600
, 400
,
NULL
, hmenu, hinstance, NULL
);
ShowWindow
(
hwnd, nCmdShow);
while
(
GetMessage
(&
msg, NULL
, 0
, 0
))
{
if
(!
TranslateMDISysAccel
(
hwnd_mdi, &
msg))
{
TranslateMessage
(&
msg);
DispatchMessage
(&
msg);
}
}
return
msg.wParam;
}
/**
***************************************************************************
*/
LRESULT CALLBACK MainWndProc
(
HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch
(
uMsg)
{
case
WM_COMMAND :
if
(
LOWORD
(
wParam) ==
IDM_QUIT)
{
PostMessage
(
hwnd, WM_CLOSE,0
,0
);
return
0
;
}
if
(
LOWORD
(
wParam) ==
IDM_ADD)
{
static
int
num =
0
;
num++
;
char
wtitle[256
];
wsprintf
(
wtitle, "
WinChild %d
"
, num);
CreateMDIWindow
(
"
ChildWinClass
"
, wtitle, 0
,
CW_USEDEFAULT, CW_USEDEFAULT, 400
, 300
, hwnd_mdi, hinst, 0
);
return
0
;
}
if
(
LOWORD
(
wParam) ==
IDM_CASCADE)
{
SendMessage
(
hwnd_mdi, WM_MDICASCADE, 0
,0
);
return
0
;
}
if
(
LOWORD
(
wParam) ==
IDM_TILE_H)
{
SendMessage
(
hwnd_mdi, WM_MDITILE, MDITILE_HORIZONTAL ,0
);
return
0
;
}
if
(
LOWORD
(
wParam) ==
IDM_TILE_V)
{
SendMessage
(
hwnd_mdi, WM_MDITILE, MDITILE_VERTICAL ,0
);
return
0
;
}
break
;
case
WM_CREATE :
{
CLIENTCREATESTRUCT ccs;
ccs.hWindowMenu =
GetSubMenu
(
GetMenu
(
hwnd), 1
);
ccs.idFirstChild =
IDM_WINCHILD;
hwnd_mdi =
CreateWindow
(
"
MDICLIENT
"
, (
LPCTSTR) NULL
,
WS_CHILD |
WS_CLIPCHILDREN |
WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
hwnd, NULL
, hinst, &
ccs);
return
0
;
}
case
WM_CLOSE :
DestroyWindow
(
hwnd);
return
0
;
case
WM_DESTROY :
PostQuitMessage
(
0
);
return
0
;
}
return
DefFrameProc
(
hwnd, hwnd_mdi, uMsg, wParam, lParam);
}
LRESULT CALLBACK ChildWndProc
(
HWND hwndchild, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch
(
uMsg)
{
default
:
return
DefMDIChildProc
(
hwndchild, uMsg, wParam, lParam);
}
}
#define IDM_QUIT 1
#define IDM_ADD 2
#define IDM_CASCADE 3
#define IDM_TILE_H 4
#define IDM_TILE_V 5
#include "menu.h"
LEMENU MENU
BEGIN
POPUP "
File
"
BEGIN
MENUITEM "
New
"
, IDM_ADD
MENUITEM SEPARATOR
MENUITEM "
Quit
"
, IDM_QUIT
END
POPUP "
Windows
"
BEGIN
MENUITEM "
Cascade
"
, IDM_CASCADE
MENUITEM "
Tile Horizontal
"
, IDM_TILE_H
MENUITEM "
Tile Vertical
"
, IDM_TILE_V
END
END
J'ai testé ce code avec MinGW et Visual C.
À vos PC.