FAQ VC++ et MFCConsultez toutes les FAQ
Nombre d'auteurs : 20, nombre de questions : 545, dernière mise à jour : 5 avril 2013 Ajouter une question
Cette faq a été réalisée pour répondre aux questions les plus fréquement posées sur le forum Développement Visual C++
Je tiens à souligner que cette faq ne garantit en aucun cas que les informations qu'elle contient sont correctes ; Les auteurs font le maximum, mais l'erreur est humaine. Si vous trouvez une erreur, ou si vous souhaitez devenir redacteur, lisez ceci.
Sur ce, je vous souhaite une bonne lecture. Farscape
- Comment chercher la fenêtre d'un programme sur le bureau ?
- Comment sérialiser des données avec les MFC ?
- Comment sérialiser des données avec les conteneurs templates MFC ?
- Comment libérer la mémoire sur les collections de template ?
- Comment fonctionnent les éditions sur une fenêtre de type View ?
- Comment faire une application type SDI ou MDI sans menu général ?
- Comment proposer automatiquement la sauvegarde d'un document modifié avant de le fermer ?
- Comment réaliser un splashscreen ?
- Comment ajouter une form .Net (winform) à mon application MFC ?
- Comment supprimer les ascenseurs d'une View ?
En parcourant les fenêtres sur le bureau :
Voici un exemple permettant de trouver le handle de fenêtre de Word et de fermer Word :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | char szIdentite[]="Microsoft Word"; CWnd* pWnd = CWnd::GetDesktopWindow( )->GetTopWindow( ); CWnd *pWndWord = 0; CString s,strWord; while( 1 ) { pWnd = pWnd->GetNextWindow(); if ( pWnd == NULL ) break; pWnd->GetWindowText( s ); strWord=s; if(strWord.Find(szIdentite)!=-1) { pWndWord = pWnd; break; } } if(pWndWord) ::PostMessage(pWndWord->GetSafeHwnd(),WM_SYSCOMMAND, SC_CLOSE, 0L ); |
Code c++ : | Sélectionner tout |
1 2 3 4 5 | CWnd::FindWindow static CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName ); |
Code c++ : | Sélectionner tout |
1 2 | CWnd *pWnd=CWnd::FindWindow( NULL,"MonProg"); |
Les classes MFC proposent des objets permettant de maintenir des collections d'objets en mémoire.
Classiquement on retrouvera des objets gérant des tableaux ,des listes ,et des maps .
On trouve dans les MFC deux générations de ces objets :
CObArray , CObList, CMapPtrToWord
Déclinés avec des string des word ,int etc..
La seconde génération utilise des classes templates :
CArray, CList ,CMap
il est préférable d'utiliser ces classes en remplacement des précédentes.
L'exemple qui suit déclare un objet à sérialiser .
Un tableau de cet objet.
Son remplissage, sa sauvegarde et sa lecture avec la classe CArchive.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | // header .................. // la classe qui contient les données class CItem : public CObject { public: DECLARE_SERIAL( CItem ) CItem(){Clear();} //----------------------------- ~CItem(){} //----------------------------- // constructeur de copie CItem(const CItem &rItem) { CopyFrom(rItem); } //----------------------------- // operateur d'affectation const CItem& operator=(const CItem& Src) { CopyFrom(Src); return *this; } //----------------------------- // effacer les données. void Clear() { m_strNom=""; m_strPrenom=""; m_strCdp=""; m_strVille=""; } //----------------------------- // la methode de serialisation. void Serialize(CArchive& ar) { if(ar.IsStoring()) ar << m_strNom << m_strPrenom << m_strCdp <<m_strVille; else ar >> m_strNom >> m_strPrenom >> m_strCdp >> m_strVille; } //----------------------------- // copier les données d'une source. void CopyFrom(const CItem & Src ) { if(this==&Src) return; Clear(); // clear eventuel. m_strNom =Src.m_strNom; m_strPrenom =Src.m_strPrenom; m_strCdp =Src.m_strCdp; m_strVille =Src.m_strVille; } //----------------------------- // dump pour test CString GetStrDump() { return ( m_strNom + "/" +m_strPrenom + "/" + m_strCdp +"/" + m_strVille); } // les données. CString m_strNom; CString m_strPrenom; CString m_strCdp; CString m_strVille; }; template<> void AFXAPI SerializeElements<CItem> (CArchive& ar, CItem* pElements, int nCount); // la gestion d'un tableau de cette classe . typedef CArray<CItem,CItem&> CArrayItem; // Source......................... //--------------------------------------------------------------------------------------- // definition de la methode de serialisation de l'objet CItem pour le template CArray. template <> void AFXAPI SerializeElements <CItem> ( CArchive& ar, CItem* pItem, int nCount ) { for ( int i = 0; i < nCount; i++, pItem++ ) pItem->Serialize( ar ); } IMPLEMENT_SERIAL( CItem, CObject, 0) void Test() { // un element CItem item; item.m_strCdp="06800"; item.m_strNom="farscape"; item.m_strPrenom="???"; item.m_strVille="Nice"; // un tableau de l'element CArrayItem arItem; // remplissage. arItem.Add(item); for(int i=0;i<10;i++) { item.m_strNom.SetAt(0,'A'+i); arItem.Add(item); } // archivage. { CFile File; if(File.Open("MyArchive.arc", CFile::modeCreate | CFile::modeWrite )) { CArchive ar( &File, CArchive::store); arItem.Serialize(ar); } } // Lecture de l'archive. arItem.RemoveAll(); CFile File; if(File.Open("MyArchive.arc", CFile::modeRead )) { CArchive ar( &File, CArchive::load); arItem.Serialize(ar); } // verification du contenu. for(i=0;i<arItem.GetSize();i++) { AfxMessageBox(arItem[i].GetStrDump()); } } |
Voir l'article sur MSDN Collections: How to Make a Type-Safe Collection
En complément de Comment sérialiser des données avec les MFC ?
Pour ceux qui n'ont pas envie d'implémenter eux-mêmes la fonction SerializeElements<CItem> chaque fois qu'ils veulent sérialiser le contenu d'un CArrayou d'un autre conteneur template MFC, il est possible de définir une classe template, héritant du conteneur en question, implémentant déjà ce service.
Exemple du CArray
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | #include <string> #include <sstream> #include <afxtempl.h> template <class T,class U> class CArraySer: public CArray<T,U> { public: CArraySer(int line,bool b=true); virtual ~CArraySer(); virtual void Serialize(CArchive& ar); void AFXAPI SerializeElts (CArchive& ar, T* pItem, int nCount); private: bool isSerializable; // si true, alors CArraySer contient des objets sérialisables std::string msg; // complément au message d'erreur affiché si on // tente de sérialiser des objets non-sérialisables }; template <class T,class U> CArraySer<T,U>::CArraySer(int line,bool b) { isSerializable = b; std::ostringstream oss; oss << "Erreur ligne " << line << " : CArraySer contient des objets non-sérialisables"; msg = oss.str(); } template <class T,class U> CArraySer<T,U>::~CArraySer() { } template <class T,class U> void AFXAPI CArraySer<T,U>::SerializeElts ( CArchive& ar,T* pItem, int nCount ) { for ( int i = 0; i < nCount; i++, pItem++ ) pItem->Serialize( ar ); } template<class T, class U> void CArraySer<T, U>::Serialize(CArchive& ar) { ASSERT_VALID(this); if (isSerializable) { CObject::Serialize(ar); if (ar.IsStoring()) { ar.WriteCount(GetSize()); } else { DWORD nOldSize = ar.ReadCount(); SetSize(nOldSize, -1); } SerializeElts(ar, m_pData, GetSize()); } else { AfxMessageBox(msg.c_str()); } } |
L'utilisation de CArraySerest similaire à celle de CArray, cependant il n'est plus nécessaire d'implémenter la fonction statique SerializeElements dans le code appelant. Le constructeur comporte 2 arguments : le 1er correspond à la ligne à laquelle vous déclarerez le CArraySer, le 2è est un booléen qui indique si les objets contenus par le CArraySer sont sérialisables ou non. Reprenons l'exemple du CArray en le remplaçant par CArraySer :
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | // La macro peut-être mise dans le fichier citem.cpp s'il existe IMPLEMENT_SERIAL( CItem, CObject, 0) void Test() { // un element CItem item; item.m_strCdp="35200"; item.m_strNom="Shakala"; item.m_strPrenom="Bigboom"; item.m_strVille="Rennes"; // un tableau de l'element CArraySer<CItem,CItem&> arItem(__LINE__); // remplissage. arItem.Add(item); for(int i=0;i<10;i++) { item.m_strNom.SetAt(0,'A'+i); arItem.Add(item); } // archivage. { CFile File; if(File.Open("MyArchive.arc", CFile::modeCreate | CFile::modeWrite )) { CArchive ar( &File, CArchive::store); arItem.Serialize(ar); } } // Lecture de l'archive. arItem.RemoveAll(); CFile File; if(File.Open("MyArchive.arc", CFile::modeRead )) { CArchive ar( &File, CArchive::load); arItem.Serialize(ar); } // verification du contenu. for(i=0;i<arItem.GetSize();i++) { AfxMessageBox(arItem[i].GetStrDump()); } } |
Si vous déclarez le contenu du CArraySer comme non-sérialisable (isSerializable = false) et que vous tentez tout de même la sérialisation, un AfxMessageBox vous délivre un message vous indiquant votre erreur et la ligne à laquelle vous avez déclarez le CArraySer incriminé.
Les collections à base de template MFC : CArray ,CMap ,CList disposent de fonctions particulières pour compléter des traitements de construction, de sérialisation ou de destruction d'éléments.
Voici la problématique : Je déclare un CMap sur dont les valeurs sont des pointeurs sur objets Donc l'insertion dans la CMap nécessitera l'allocation de l'objet au préalable.
Exemple :
la déclaration :
Code c++ : | Sélectionner tout |
1 2 | CMap<CString ,const char *,_InfosFolder* ,_InfosFolder *> m_mapFilter; |
Code c++ : | Sélectionner tout |
1 2 | m_mapFilter.SetAt("clef", new _InfosFolder); |
pour résoudre ce problème on implémentera la fonctionDestructElements :
voici son prototype:
Code c++ : | Sélectionner tout |
1 2 3 4 | template<class TYPE > void AFXAPI DestructElements( TYPE* pElements, int nCount ); |
Code c++ : | Sélectionner tout |
1 2 3 4 5 | template <> void AFXAPI DestructElements <_InfosFolder *> ( _InfosFolder** pItem, int nCount ) { for ( int i = 0; i < nCount; i++, pItem++ ) delete *pItem; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #ifdef _DEBUG #define TRACK_ALLOC(_class) void *m_pNew;\ void* operator new(size_t nSize,LPCSTR lpszFileName,int nLine)\ {\ _class *p=static_cast<_class *>(::operator new(nSize, lpszFileName, nLine));\ p->m_pNew=p;return p;\ }\ bool IsAlloc(){return m_pNew==this;} #else #define TRACK_ALLOC(_class) void *m_pNew;\ void* operator new(size_t nSize)\ {\ _class *p=static_cast<_class *>(::operator new(nSize));\ p->m_pNew=p;return p;\ }\ bool IsAlloc(){return m_pNew==this;} #endif |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct _InfosFolder { //.............. TRACK_ALLOC(_InfosFolder) }; template <> void AFXAPI DestructElements <_InfosFolder *> ( _InfosFolder** pItem, int nCount ) { for ( int i = 0; i < nCount; i++, pItem++ ) { if(*pItem->IsAlloc()) delete *pItem; } } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | void CMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::RemoveAll() { ASSERT_VALID(this); if (m_pHashTable != NULL) { // destroy elements (values and keys) for (UINT nHash = 0; nHash < m_nHashTableSize; nHash++) { CAssoc* pAssoc; for (pAssoc = m_pHashTable[nHash]; pAssoc != NULL; pAssoc = pAssoc->pNext) { DestructElements<VALUE>(&pAssoc->value, 1); DestructElements<KEY>(&pAssoc->key, 1); } } } // free hash table delete[] m_pHashTable; m_pHashTable = NULL; m_nCount = 0; m_pFreeList = NULL; m_pBlocks->FreeDataChain(); m_pBlocks = NULL; } |
Principe de fonctionnement général d'une édition à partir d'une CViewCScrollView :
Le traitement normal d'affichage se fait dans la fonction virtuelle OnDraw utilisée aussi pour le dessin écran .
Elle reçoit en argument un objet CDC (contexte de périphérique) .
Si on imprime, la fonction OnDraw est appelée par une autre fonction virtuelle, OnPrint ;le dc sera alors un contexte de périphérique pour imprimante, et CDC::IsPrinting() renvoie TRUE .
Dans le cas de la prévisualisation écran on aura un objet CPreviewDC; OnPrint et OnDraw seront appelées.
Les fonctions disponibles pour le système d'édition sont:
- OnPreparePrinting : définition de la plage d'édition voir CPrintInfo
- OnBeginPrinting : créations d'objets gdi personnels.
- OnPrepareDC : appelée pour chaque page, c'est l'emplacement convenu pour définir le système de coordonnées (autre que MM_TEXT) .
- OnPrint : Appelée pour chaque page ,traitements spécifiques pour compléter éventuellement le dessin, entêtes, pieds de page, date et heure d'édition. Appelle OnDraw.
- OnEndPrinting: suppression des objets GDI.
dans une view on trouve généralement les messages suivants:
Code c++ : | Sélectionner tout |
1 2 3 4 | ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview) |
- ID_FILE_PRINT : impression avec sélection de l'imprimante.
- ID_FILE_PRINT_DIRECT: impression directe sur l'imprimante en cours.
- ID_FILE_PRINT_PREVIEW: prévisualisation écran.
Note : le système MFC associe l'édition/prévisualisation à la fenêtre en cours on ne dispose pas en standard d'un objet indépendant pour gérer l'édition comme ça existait chez Borland avec les OWL .
On ne peut pas générer une application de type SDI ou MDI sans menu directement.
La solution consiste à le supprimer lors de la création de Mainframe en procédant comme suit :
Pour un projet SDI: Dans la classe CMainFrame, il faut rajouter dans la fonction PreCreateWindow, le code suivant :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if (cs.hMenu!=NULL) { ::DestroyMenu(cs.hMenu); // delete menu if loaded cs.hMenu = NULL; // no menu for this window } return CFrameWnd::PreCreateWindow(cs); } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if (cs.hMenu!=NULL) { ::DestroyMenu(cs.hMenu); // delete menu if loaded cs.hMenu = NULL; // no menu for this window } return CMDIFrameWnd::PreCreateWindow(cs); } BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { // TODO: Add your specialized code here and/or call the base class return CreateClient(lpcs,NULL); // a la place de la ligne ci-dessus //return CMDIFrameWnd::OnCreateClient(lpcs, pContext); } BOOL CMainFrame::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext) { // TODO: Add your specialized code here and/or call the base class return CFrameWnd::LoadFrame(nIDResource, dwDefaultStyle, pParentWnd, pContext); //return CMDIFrameWnd::LoadFrame(nIDResource, dwDefaultStyle, pParentWnd, pContext); } |
c'est à dire le menu correspondant à l'identifiant utilisé dans la déclaration du document template dans la fonction InitInstance de la classe d'application.
On a pu déjà remarquer que beaucoup d'applications proposent à l'utilisateur d'enregistrer le document en cours de fermeture si celui-ci a été modifié depuis son ouverture. Les MFC proposent également ce mécanisme.
L'architecture d'application gère ce comportement grâce à la donnée membre BOOL m_bModified de la classe CDocument (dirty flag : TRUE si le document a été modifié, FALSE sinon). Cet indicateur est initialisé à false à la création, lecture, enregistrement du document. C'est au programmeur de le mettre à TRUE lorsque le document a été modifié.
Sa valeur peut-être consultée avec la fonction BOOL CDocument::IsModified() et modifiée avec la fonction void SetModifiedFlag(BOOL bModified = TRUE).
Lorsque l'utilisateur ferme un document, l'architecture d'application appelle la fonction virtuelle virtual BOOL SaveModified( ) qui ouvre une boîte de dialogue si m_bModified = TRUE. On peut redéfinir le comportement de la fonction SaveModified( ).
Exemple d'indication de modification du document :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // fonction de la classe dérivée de CDocument void CMyDoc::ModifData(CString strName,CString strLevel,CString strLoc,CTime tDate) { // m_pData est une donnée membre de CMyDoc m_pData->SetName(strName); m_pData->SetLevel(strLevel); m_pData->SetPlace(strLoc); m_pData->SetDate(tDate); // les données du document ont été modifiées SetModifiedFlag(); // mise à jour des vues sur le document UpdateAllViews(NULL); } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | // modification des données de CMyDoc dans une vue void CMyView::MyFunction() { CMyDoc * pDoc = static_cast<CMyDoc*>(GetDocument()); // appel de la fonction modificant les données du document pDoc->ModifData("Nom","Niveau","Localisation",CTime::GetCurrentTime()); // si on n'avait pas écrit la fonction CMyDoc::ModifData(...), // on aurait géré les modifications du document ici et écrit : // pDoc->SetModifiedFlag(); } |
Un splashscreen est une boîte de dialogue qui apparaît au lancement d'une application pendant que celle-ci se charge. Lorsque l'application est prête, le splashscreen disparaît et la fenêtre principale de l'application apparaît.
Pour réaliser un splashscreen il suffit de décocher l'option "Title bar" dans les propriétés de la boîte de dialogue, et d'intercepter le message WM_WINDOWPOSCHANGING (à la main) :
Dans le .h de la dialog
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | // Generated message map functions //{{AFX_MSG(CPasBougerDlg) ... afx_msg void OnWindowPosChanging( WINDOWPOS* lpwndpos ); //}}AFX_MSG |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | BEGIN_MESSAGE_MAP(CPasBougerDlg, CDialog) //{{AFX_MSG_MAP(CPasBougerDlg) ... ON_WM_WINDOWPOSCHANGING() //}}AFX_MSG_MAP END_MESSAGE_MAP() ... void CPasBougerDlg::OnWindowPosChanging(WINDOWPOS* pWndPos) { if (IsWindowVisible()) pWndPos->flags |= SWP_NOMOVE; } |
Pour une boîte de dialogue, on procèdera de la même façon en laissant éventuellement la barre de titre.
Dans un contexte SDI, on interceptera le message sur le CMainFrame et on interdira le redimensionnement :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | void CMainFrame::OnWindowPosChanging(WINDOWPOS* pWndPos) { if (IsWindowVisible()) { pWndPos->flags |= SWP_NOMOVE; pWndPos->flags |= SWP_NOSIZE; } } |
Il s'agit tout d'abord de transformer son application MFC en application managée, pour ceci, il faut ajouter le support du CLR.
Bouton droit sur le projet -> Common properties -> general -> Common language runtime support ; Mettre à /clr (common language runtime support).
Ensuite ajoutez simplement une nouvelle winform, rajoutez des composants dessus, etc.
Instanciez votre nouvelle form,
Code C++ : | Sélectionner tout |
1 2 | mfcPlusWinforms::mfcWinForm dlg; dlg.ShowDialog(); // pour qu'elle se comporte comme une dialog |
N'oubliez pas bien sûr d'inclure le fichier .h correspondant au code de la winform.
Téléchargez le programme d'exemple : mfcPlusWinforms.rar
Dans le cas d'une CFormView, CView les ascenseurs apparaissent automatiquement dès que la surface cliente de la fenêtre devient trop petite par rapport à la surface réelle.
Comment faire dans ce contexte pour supprimer les ascenseurs ? Hé bien une CFormView ou CView hérite de la classe CSCrollView qui dispose d'une fonction spécifique pour adapter la surface de la fenêtre à la taille fenêtrée : SetScaleToFitSize
Exemple réalisé dans la méthode OnInitialUpdate d'une CFormView :
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 | CRect Rect; GetParentFrame()->GetWindowRect(&Rect); GetParentFrame()->SetWindowPos( NULL,0,0,Rect.Width(),Rect.Height()/2,SWP_NOMOVE | SWP_NOZORDER); // SIZE size; size.cx=Rect.Width(); size.cy=Rect.Height()/2; SetScaleToFitSize(size); |
J'ai diminué la taille de la fenêtre parent (MDIChild) par deux et ajusté la taille réelle avec SetScalToFitSize.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.