| auteur : Farscape | La gestion de plusieurs fenêtres dans un projet MDI passe par la déclaration dans la fonction InitInstance (classe d'application CWinApp ) d'objets de la classe CMultiDocTemplate.
BOOL CTestMdiApp:: InitInstance ()
{
AfxEnableControlContainer ();
# ifdef _AFXDLL
Enable3dControls ();
# else
Enable3dControlsStatic ();
# endif
SetRegistryKey (_T (" Local AppWizard-Generated Applications " ));
LoadStdProfileSettings ();
m_pTplMdiView = new CMultiDocTemplate (
IDR_TESTMDTYPE,
RUNTIME_CLASS (CTestMdiDoc),
RUNTIME_CLASS (CChildFrame),
RUNTIME_CLASS (CTestMdiView));
AddDocTemplate (m_pTplMdiView);
m_pTplEditView= new CMultiDocTemplate (
IDR_TESTMDTYPE,
RUNTIME_CLASS (CTestMdiDoc),
RUNTIME_CLASS (CChildFrame),
RUNTIME_CLASS (CEditView));
AddDocTemplate (m_pTplEditView);
CMainFrame* pMainFrame= new CMainFrame;
if (! pMainFrame- > LoadFrame (IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
CCommandLineInfo cmdInfo;
ParseCommandLine (cmdInfo);
if (! ProcessShellCommand (cmdInfo)) return FALSE;
pMainFrame- > ShowWindow (m_nCmdShow);
pMainFrame- > UpdateWindow ();
return TRUE;
}
|
Ci-dessus on a une déclaration classique pour gérer deux fenêtres différentes au sein du même programme.
La création de la deuxième fenêtre pourra se faire comme suit :
((CTestMdiApp * )AfxGetApp ())- > m_pTplEditView- > OpenDocumentFile (NULL ) ;
|
|
| auteur : Farscape | D'abord pour fixer les idées, dans quel cas a-t-on besoin d'avoir des fenêtres différentes pour un même document ?
Chaque fois que l'on voudra avoir un lien entre plusieurs fenêtres pour des échanges de données ou des représentations de données d'une manière différente, le document donnant, par ses méthodes, un point d'accès pratique et facile aux différentes fenêtres.
Exemple :
Dans une « CFormView » je saisis des données pour l'affichage d'un graphe,dans une CView je les dessine sous forme de tableau récapitulatif , et dans une autre « CView » je les représente sous forme de diagrammes ,histogrammes etc..
L'association de plusieurs fenêtres sur un seul document s'effectue sur la déclaration du document template dans la fonction InitInstance de la classe d'application.
m_pFirstTemplate= new CMultiDocTemplate (IDR_TESTMDTYPE,
RUNTIME_CLASS (CTestMdiDoc),
RUNTIME_CLASS (CChildFrame),
RUNTIME_CLASS (CTestMdiView));
AddDocTemplate (m_pFirstTemplate);
m_pTwoDocTemplate= new CMultiDocTemplate (IDR_TESTMDTYPE,
RUNTIME_CLASS (CTestMdiDoc),
RUNTIME_CLASS (CChildFrame),
RUNTIME_CLASS (CMyEditView));
|
Ici le premier document template comprend une « CView » et la fonction AddocTemplate est appelée
le deuxième juste une déclaration du template sans faire l'appel à AddDocTemplate .
Pour rajouter un objet de la class CMyEditView par exemple dans la fonction OnInitialUpdate de la classe CTestMdiView on procédera comme suit:
void CTestMdiView:: OnInitialUpdate ()
{
CView:: OnInitialUpdate ();
CTestMdiApp * TheApp= (CTestMdiApp* )AfxGetApp ();
CFrameWnd * pFrame = TheApp- > m_pTwoDocTemplate- > CreateNewFrame (GetDocument (),GetParentFrame ());
TheApp- > m_pFirstTemplate- > InitialUpdateFrame (pFrame,GetDocument ());
}
|
Le même appel pouvant être fait en réponse à une commande sur un menu sur la view en cours.( CTestMdiView).
Les étapes du traitement:
Récupération de la classe application pour accéder au pointeur sur document template (m_pFirstTemplate)
Création d'une « Mdi » à partir du deuxième document template (m_pTwoDocTemplate)
Rattacher la MDI avec sa fenêtre CMyEditView au premier document template avec la fonction InitialUpdateFrame .
On répétera le même processus pour la description et la création d'une fenêtre supplémentaire.
Au final on aura bien un objet document avec plusieurs Fenêtres.
C'est vérifiable par exemple en parcourant les fenêtres à partir de l'objet document :
void CMyEditView:: OnInitialUpdate ()
{
CEditView:: OnInitialUpdate ();
POSITION pos= GetDocument ()- > GetFirstViewPosition ();
CView * pView;
do
{
pView= GetDocument ()- > GetNextView (pos);
}
while (pView);
}
|
Cette méthode permet de gérer l'apparition de fenêtres rattachées au même document à la demande..
Notes:
On n'est pas pour autant obligé de stocker des données communes dans le document.
Le document n'a pas de partie graphique visible.
|
| auteur : Farscape | En passant par les fonctions de la CMDIChildWnd .
A partir de la fenêtre fille dans la fonction OnInitialUpdate() on commence par récupérer le pointeur sur la MDI avec la fonction GetParentFrame() pour appeler MDIMaximize( );
((CMDIChildWnd * )GetParentFrame ())- > MDIMaximize ( );
|
|
| auteur : Farscape |
En passant par les fonctions de la CMDIChildWnd .
A partir de la fenêtre fille dans la fonction OnInitialUpdate() on commence par récupérer le pointeur sur la MDI avec la fonction GetParentFrame() pour appeler MDIRestore( );
((CMDIChildWnd * )GetParentFrame ())- > MDIRestore ( );
|
|
| auteur : Farscape |
CMDIFrameWnd:: MDIGetActive
CMDIChildWnd* MDIGetActive ( BOOL* pbMaximized = NULL ) const ;
|
Exemple d'application maximiser la child active .
On commence par récupérer le pointeur sur la MainFrame avec AfxGetMainWnd().
BOOL maximized;
CMDIChildWnd* child = ((CMDIFrameWnd * )AfxGetMainWnd ())- > MDIGetActive (& maximized);
if (child & & (! maximized))
child- > MDIMaximize ();
|
|
| auteur : Farscape | En utilisant la fonction IsIconic() de la classe CWnd.
CWnd:: IsIconic
BOOL IsIconic ( )
const ;
|
Exemple de restauration d'une MDI iconisée
BOOL maximized;
CMDIChildWnd* child = ((CMDIFrameWnd * )AfxGetMainWnd ())- > MDIGetActive (& maximized);
if (child & & (maximized | | child- > IsIconic ()))
child- > MDIRestore ();
|
|
| auteur : Farscape | En passant par les fonctions de la CMDIChildWnd .
A partir de la fenêtre fille dans la fonction OnInitialUpdate() on commence par récupérer le pointeur sur la MDI avec la fonction GetParentFrame() pour appeler MDIActivate( );
GetParentFrame ()- > MDIActivate ( );
|
|
| auteur : Farscape | En passant par les fonctions de la CMDIChildWnd .
A partir de la fenêtre fille dans la fonction OnInitialUpdate() on commence par récupérer le pointeur sur la MDI avec la fonction GetParentFrame() pour appeler ActivateFrame:
CFrameWnd:: ActivateFrame
virtual void ActivateFrame (
int nCmdShow = ? 1 );
|
le paramètre nCmdShow correspond à la fonction ShowWindow.
GetParentFrame ()- > ActivateFrame (SW_RESTORE);
|
Cette fonction modifie le z order des fenêtres en faisant passer la fenêtre en avant plan.
|
| auteur : Farscape | On rajoutera la séquence SetWindowPos dans la fonction OnCreate de la classe CMainFrame juste après les initialisations classiques toolbar dialogbar etc...
int CMainFrame:: OnCreate (LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWnd:: OnCreate (lpCreateStruct) = = - 1 )
return - 1 ;
if (! m_wndToolBar.CreateEx (this , TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) | |
! m_wndToolBar.LoadToolBar (IDR_MAINFRAME))
{
TRACE0 (" Failed to create toolbar\n " );
return - 1 ;
}
if (! m_wndStatusBar.Create (this ) | |
! m_wndStatusBar.SetIndicators (indicators,
sizeof (indicators)/ sizeof (UINT)))
{
TRACE0 (" Failed to create status bar\n " );
return - 1 ;
}
m_wndToolBar.EnableDocking (CBRS_ALIGN_ANY);
EnableDocking (CBRS_ALIGN_ANY);
DockControlBar (& m_wndToolBar);
CRect rect;
GetWindowRect ( rect );
:: SetWindowPos (m_hWnd ,
HWND_TOPMOST,
rect.left,
rect.top,
rect.Width (),
rect.Height (),
SWP_SHOWWINDOW);
return 0 ;
}
|
|
| auteur : Farscape |
CRect RectFrame;
GetParentFrame ()- > GetWindowRect (& RectFrame);
GetParentFrame ()- > SetWindowPos (NULL ,0 ,0 ,RectFrame.Width (),lParam,SWP_NOMOVE | SWP_NOZORDER);
|
où lParam représente ici ma nouvelle hauteur.
BOOL SetWindowPos (
const CWnd* pWndInsertAfter,
int x,
int y,
int cx,
int cy,
UINT nFlags );
|
|
| auteur : Farscape | Dans une application MFC il n' y a pas de mécanismes standard pour la mémorisation des fenêtres.
Pour remédier à ce problème il faudra mettre en place une sauvegarde de la position et taille de la fenêtre principale (MainFrame) et des fenêtres Filles (MDIChild).
Le code qui suit est une implémentation de ces fonctionnalités avec une classe template permettant l'insertion dans un projet existant sans modifications excessives de code :
# if ! defined ( AFX_TPLMDIFRAME_H__59DC7C67_79D6_4082_A38B_4CD17E8257C1__INCLUDED_ )
# define AFX_TPLMDIFRAME_H__59DC7C67_79D6_4082_A38B_4CD17E8257C1__INCLUDED_
# if _MSC_VER > 1000
# pragma once
# endif / / _MSC_VER > 1000
template < class GENERIC_FRAME>
class CTplMDIFrame : public GENERIC_FRAME
{
public :
CTplMDIFrame ()
{
m_rectFrame= CRect (0 ,0 ,0 ,0 );
m_bMaximized= FALSE;
m_bInitPos= false ;
SetSectionName ();
}
virtual ~ CTplMDIFrame (){ SaveFramePos ();}
void SetSectionName (const char * szSection= " AppPos " )
{
m_strSection= szSection;
}
void SaveFramePos ()
{
MakeSection ();
AfxGetApp ()- > WriteProfileInt (m_strSection, " Maximized " , m_bMaximized);
AfxGetApp ()- > WriteProfileBinary (m_strSection, " FrameRect " , (BYTE* )& m_rectFrame,sizeof (CRect));
AfxGetApp ()- > WriteProfileBinary (m_strSection, " InitialFrameRect " , (BYTE* )& m_rectInitialFrame,sizeof (CRect));
}
virtual void MakeSection (){ }
voidLoadFramePos ()
{
BYTE* pb = NULL ;
UINT nLen = 0 ;
MakeSection ();
m_bMaximized = AfxGetApp ()- > GetProfileInt (m_strSection, " Maximized " , (int )FALSE);
m_rectFrame.SetRect (0 ,0 ,0 ,0 );
m_rectInitialFrame.SetRect (0 ,0 ,0 ,0 );
if (AfxGetApp ()- > GetProfileBinary (m_strSection, " FrameRect " , & pb, & nLen))
{
ASSERT (nLen = = sizeof (CRect));
memcpy (& m_rectFrame, pb, sizeof (CRect));
delete pb;
}
if (AfxGetApp ()- > GetProfileBinary (m_strSection, " InitialFrameRect " , & pb, & nLen))
{
ASSERT (nLen = = sizeof (CRect));
memcpy (& m_rectInitialFrame, pb, sizeof (CRect));
delete pb;
}
CRect rectScreen (0 , 0 , GetSystemMetrics (SM_CXSCREEN),
GetSystemMetrics (SM_CYSCREEN));
CRect rectInt;
rectInt.IntersectRect (& rectScreen, & m_rectFrame);
if (rectInt.Width () < 10 | | rectInt.Height () < 10 ) m_rectFrame.SetRect (0 , 0 , 0 , 0 );
}
voidShowMainFrame (int nCmdShow)
{
ASSERT (this - > IsKindOf ( RUNTIME_CLASS (CMDIFrameWnd)));
AfxGetApp ()- > m_nCmdShow = nCmdShow;
if (m_bMaximized) AfxGetApp ()- > m_nCmdShow = SW_SHOWMAXIMIZED;
ShowWindow (AfxGetApp ()- > m_nCmdShow);
UpdateWindow ();
}
virtual BOOL PreCreateWindow (CREATESTRUCT& cs)
{
if (cs.lpszClass & & ! m_bInitPos)
{
LoadFramePos ();
m_bInitPos= true ;
if (m_rectFrame.Width () > 0 & & m_rectFrame.Height () > 0 )
{
cs.x = m_rectFrame.left;
cs.y = m_rectFrame.top;
cs.cx = m_rectFrame.Width ();
cs.cy = m_rectFrame.Height ();
}
}
if (this - > IsKindOf ( RUNTIME_CLASS (CMDIFrameWnd)))
{
if (! GENERIC_FRAME:: PreCreateWindow (cs) ) return FALSE;
}
else
{
if (GENERIC_FRAME:: PreCreateWindow (cs) ) return FALSE;
}
return TRUE;
}
CRect & GetInitialRectFrame (){ return m_rectInitialFrame;}
void SetInitialRectFrame (CRect Rect){ m_rectInitialFrame= Rect;}
void GetWndPos ()
{
WINDOWPLACEMENT wp;
wp.length = sizeof (wp);
GetWindowPlacement (& wp);
m_rectFrame = wp.rcNormalPosition;
}
virtual LRESULT DefWindowProc (UINT nMsg, WPARAM wParam, LPARAM lParam)
{
if (nMsg= = WM_DESTROY )
{
m_bMaximized = (GetStyle () & WS_MAXIMIZE);
GetWndPos ();
}
if (nMsg= = WM_MOVE)
{
GetWndPos ();
}
if (nMsg= = WM_SIZE)
{
GetWndPos ();
}
return GENERIC_FRAME:: DefWindowProc ( nMsg, wParam,lParam);
}
public :
bool m_bInitPos;
BOOL m_bMaximized;
CRect m_rectFrame;
CRect m_rectInitialFrame;
CString m_strSection;
} ;
class CMDIChildEx : public CTplMDIFrame< CMDIChildWnd>
{
public :
CMDIChildEx ()
{
m_nPosCurrent= - 1 ;
SetSectionName (" Child " );
}
~ CMDIChildEx ()
{
POSITION pos= m_ListSection.Find (m_strSection);
if (pos) m_ListSection.RemoveAt (pos);
}
virtual void ActivateFrame ( int nCmdShow = - 1 )
{
if (m_bMaximized ) nCmdShow = SW_MAXIMIZE;
CMDIChildWnd:: ActivateFrame (nCmdShow);
}
virtual void MakeSection ()
{
CString str;
str.Format (" %s-%d " ,(const char * )m_strSection,m_nIDHelp);
m_strSection= str;
if (m_nPosCurrent= = - 1 )
{
int i= 0 ;
while (1 )
{
str.Format (" %s:%d " ,(char * )(const char * )m_strSection,i);
if (m_ListSection.Find (str)) i+ + ;
else
{
m_ListSection.AddTail (str);
break ;
}
}
m_nPosCurrent= i;
}
m_strSection= str;
}
public :
static CList< CString ,CString& > m_ListSection;
protected :
int m_nPosCurrent;
} ;
# endif / / ! defined ( AFX_TPLMDIFRAME_H__59DC7C67_79D6_4082_A38B_4CD17E8257C1__INCLUDED_ )
# include "stdafx.h"
# include "TplMDIFrame.hpp"
CList< CString ,CString& > CMDIChildEx:: m_ListSection;
|
Utilisation:
au niveau de la classe CMainframe:
# include "TplMDIFrame.hpp"
class CMainFrame : public CTplMDIFrame< CMDIFrameWnd>
{
..........
|
au niveau de la classe CChildFrame
# include "TplMDIFrame.hpp"
class CChildFrame : public CMDIChildEx
{
........
|
au niveau de la classe d'application dans la fonction initInstance :
pMainFrame- > ShowMainFrame (m_nCmdShow);
|
au niveau des View il faudra enlever la ligne ResizeParentToFit():
void CTestMdILayoutView:: OnInitialUpdate ()
{
CFormView:: OnInitialUpdate ();
}
|
Fonctionnalités :
Ces classes template permettent la gestion de la taille et position de la fenêtre principale ainsi que le mode maximiser de l'application.
Au niveau des fenêtres filles, on retrouve les mêmes fonctionnalités plus la gestion des positions pour plusieurs fenêtres de la même classe.
La différenciations des views se fait grâce à leur numéro d'identifiant dans les ressources.
Ces informations sont stockées dans le .ini de l'application qui ressemblera à ça :
[AppPos]
Maximized= 16777216
FrameRect= EIAAAAAAOKAAAAAAEIDAAAAAHNCAAAAA
[Child- 129 :0 ]
Maximized= 0
FrameRect= HMBAAAAAHABAAAAAKPDAAAAACKCAAAAA
[Child- 129 :1 ]
Maximized= 0
FrameRect= PPPPPPPPPPPPPPPPNMBAAAAAFABAAAAA
|
|
| auteur : Nourdine Falola | Dans un contexte SDI/MDI, le titre d'une fenêtre est régi par le
document auquel elle se rattache. Ce comportement est dû au style
FWS_ADDTOTITLE, activé par défaut, du cadre affichant la vue.
Le nom du document est demandé à la création du projet. A l'étape 4
de l'AppWizard, cliquez sur "Advanced..." :
- le champ "Main frame caption" détermine le début du
titre du cadre principal
- le champ "Doc type name" détermine la racine du nom par
défaut du document. Si on ne met rien la racine sera
"sans nom".
le nom du document peut aussi être modifié dans le code par l'appel de la fonction CDocument::SetTitle(LPCTSTR lpszTitle).
Le titre sera formaté ainsi :
- SDI : pour le cadre principal : "nom par défaut du document - nom du cadre"
- MDI :
pour le cadre principal :
- sans document ouvert : "titre cadre principal"
- avec document ouvert : "titre cadre principal - racine du nom du document actif et n°"
- avec document ouvert et vue maximisée : "titre cadre principal - [racine du nom du document actif et n°]" pour une vue : "racine du nom du document actif et n°"
On peut vouloir modifier ce mécanisme pour personnaliser le titre d'une vue.
Cela peut-être intéressant dans un contexte SDI ou MDI à document à vues
multiples. Pour cela, chaque vue doit être associée à une ressource
IDR (menu, icône, ...) différente (ce qui donnera lieu à une chaîne par
ressource dans la String Table).
1. On modifie la chaîne de la String Table associée à chaque
ressource ( Comment changer le filtre de l'ouverture de fichiers dans une application MFC ?) :
SDI
ID Value Caption
IDR_MAINFRAME 128 Titre main frame\nRacine nom doc par défaut\n\nFichier machin (* .m)\n.m\nM.Document\nM Document
IDR_TATA 129 Titre perso de la 1ère vue\n\n\n\n\n\n
IDR_TITI 130 Titre perso de la 2ème vue\n\n\n\n\n\n
IDR_TOTO 131 Titre perso de la 3ème vue\n\n\n\n\n\n
|
MDI
ID Value Caption
IDR_MAINFRAME 128 Titre main frame\n\nRacine nom doc par défaut\nFichier machin (* .m)\n.m\nM.Document\nM Document
IDR_TATA 129 Titre perso de la 1ère vue\n\n\n\n\n\n
IDR_TITI 130 Titre perso de la 2ème vue\n\n\n\n\n\n
IDR_TOTO 131 Titre perso de la 3ème vue\n\n\n\n\n\n
|
2. Surcharger la fonction CWnd::PreCreateWindow :
SDI
On surchargera CMainFrame::PreCreateWindow(CREATESTRUCT& cs).
MDI
BOOL CChildFrame:: PreCreateWindow (CREATESTRUCT& cs)
{
cs.style & = ~ (LONG)FWS_ADDTOTITLE;
return CMDIChildWnd:: PreCreateWindow (cs);
}
|
|
| auteur : Farscape |
CMenu * pSysMenu = GetParentFrame ()- > GetSystemMenu (FALSE);
ASSERT (pSysMenu ! = NULL );
pSysMenu- > EnableMenuItem (SC_CLOSE,MF_BYCOMMAND | MF_GRAYED );
|
On commence par récupérer le menu système de la MDI, ensuite on règle le statut grisé avec la fonction EnableMenuItem pour l'identifiant SC_CLOSE.
|
| auteur : Farscape | Il faut intercepter le message WM_WINDOWPOSCHANGING sur la MDIChild.
Exemple:
void CChildFrame:: OnWindowPosChanging (WINDOWPOS* lpwndpos)
{
if (! m_rect.IsRectEmpty ())
{
lpwndpos- > x= m_rect.left;
lpwndpos- > y= m_rect.top;
CRect rectBarPos;
static_cast < CMainFrame * > (AfxGetMainWnd ())- > m_wndToolBar.GetWindowRect (& rectBarPos);
lpwndpos- > y- = rectBarPos.Height ();
}
CMDIChildWnd:: OnWindowPosChanging (lpwndpos);
}
void CChildFrame:: OnSize (UINT nType, int cx, int cy)
{
CMDIChildWnd:: OnSize (nType, cx, cy);
if (m_rect.IsRectEmpty ())
{
GetWindowRect (& m_rect);
AfxGetMainWnd ()- > ScreenToClient (& m_rect);
}
}
|
Essai réalisé avec Visual 2005.
Il se peut que le message ne soit pas implémenté avec VC 6.
Il faudra le rajouter manuellement
|
| auteur : Farscape | Le cas typique est l'accès de la vue à partir d'une boîte de dialogue.
On pourra procéder comme suit :
BOOL CMyDialog:: OnInitDialog ()
{
CDialog:: OnInitDialog ();
CView * pView= static_cast < CFrameWnd * > (AfxGetMainWnd ())- > GetActiveView ();
CSampleSDIView * pSDIView= static_cast < CSampleSDIView * > (pView);
pSDIView- > m_Edit.SetWindowText (" essai " );
return TRUE;
}
|
|
lien : Comment récupérer la fenêtre parent dans une boîte de dialogue ?
|
Consultez les autres F.A.Q.
|
|