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 détecter les changements sur un groupe d'édit/checkbox/radio ?
- Comment gérer le click sur des boutons crées dynamiquement ?
- Comment simuler un click sur un CButton ?
- Comment définir un message privé ?
- Comment faire un message Map dynamique ?
- Comment intercepter plusieurs messages avec le même gestionnaire de commandes ?
- Comment mettre en place un raccourci clavier ?
- Comment faire pour que les messages réfléchis d'un contrôle soient disponibles sur la fenêtre parent ?
- Comment utiliser la méthode de Windows pour le redimensionnement d'une fenêtre ?
- Comment récupérer le statut des boutons de la souris ?
- Comment détecter que la souris sort de la fenêtre de travail ?
- Comment donner le focus au prochain contrôle par la touche flèche bas ?
- Comment trouver le premier contrôle dans l'ordre de tabulation ?
En utilisant la macro ON_CONTROL_RANGE (voir documentation MSDN pour description) et en indiquant une plage d'indices consécutifs
Exemple d'implémentation :
Dans le .h
Code c++ : | Sélectionner tout |
1 2 3 4 5 | DECLARE_MESSAGE_MAP() virtual void OnRangeUpdateEdit(UINT nID); virtual void OnRangeUpdateCheck(UINT nID); virtual void OnRangeUpdateRadio(UINT nID); |
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 | BEGIN_MESSAGE_MAP(CMyDlg, CDialog) //{{AFX_MSG_MAP(CMyDlg) //}}AFX_MSG_MAP ON_CONTROL_RANGE(EN_CHANGE, IDC_EDIT1, IDC_EDIT3, OnRangeUpdateEdit) ON_CONTROL_RANGE(BN_KILLFOCUS , IDC_CHECK1, IDC_CHECK3, OnRangeUpdateCheck) ON_CONTROL_RANGE(BN_CLICKED , IDC_RADIO1, IDC_RADIO4, OnRangeUpdateRadio) // ................. void CMyDlg::OnRangeUpdateEdit(UINT nID) { if(SendDlgItemMessage(nID, EM_GETMODIFY, 0, 0)) { // c'est modifier } else { // non } } void CMyDlg::OnRangeUpdateCheck(UINT nID) { // Récupération de l'état pour la check box nID. LRESULT lResult = SendDlgItemMessage(nID, BM_GETSTATE, 0, 0); if ( lResult == BST_CHECKED ) { // la check est cochée . } } void CMyDlg::OnRangeUpdateRadio(UINT nID) { // par exemple je decide uniquement une reaction sur IDC_RADIO2. if ( nID == IDC_RADIO2 ) { // action } } |
Comme on ne peut pas mettre à l'avance avec ClassWizard la notification du click sur le(s) bouton(s) il faudra le faire manuellement en utilisant la macro : ON_CONTROL_RANGE qui permet de récupérer les notifications de messages en provenance des contrôles pour une plage d'identificateurs.
Imaginons que la plage dynamique des Id des boutons soit de 1000 à 2000 on aura :
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 | // .h // Generated message map functions protected: //{{AFX_MSG(CTestMDIView) //}}AFX_MSG void OnRangeClickButton(UINT nID); DECLARE_MESSAGE_MAP() }; // .cpp BEGIN_MESSAGE_MAP(CTestMDIView, CFormView) //{{AFX_MSG_MAP(CTestMDIView) //}}AFX_MSG_MAP ON_CONTROL_RANGE(BN_CLICKED , 1000,2000, OnRangeClickButton) END_MESSAGE_MAP() void CTestdlgBarMDIView::OnRangeClickButton(UINT nID) { TRACE1("Click on button :%d\n\r",nID); } |
Il ne faut pas oublier que l'on peut aussi appeler directement la fonction associée à la notification click du bouton ….
Mais si ce n'est pas possible il faudra envoyer un message directement au bouton :
Code c++ : | Sélectionner tout |
1 2 3 | GetDlgItem(IDC_BUTTON1)->PostMessage(WM_KEYDOWN,' '); GetDlgItem(IDC_BUTTON1)->PostMessage(WM_KEYUP,' '); |
Dans certaines situations, il est parfois nécessaire de pouvoir envoyer un message privé à une fenêtre .
L'exemple typique c'est la communication ou le rafraîchissement d'une fenêtre à partir d'un thread de travail.
Exemple :
Insertion d'un message privé WM_TESTdans la classe fenêtre CSdisamplesView.
Comment procéder :
Définir le message privé :
Code c++ : | Sélectionner tout |
1 2 |
#define WM_TEST WM_USER+100 |
Implémenter la fonction de réponse au message :
Celle-ci doit être insérée manuellement comme dans l'exemple ci-dessous :
Dans l'include de la classe fenêtre concernée :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #define WM_TEST WM_USER+100 class CSdisamplesView : public CFormView { protected: // create from serialization only CSdisamplesView(); DECLARE_DYNCREATE(CSdisamplesView) public: //…………… // Generated message map functions protected: //{{AFX_MSG(CSdisamplesView) //}}AFX_MSG // fonction de réponse pour le message privé long OnReceiveTest(WPARAM wparam,LPARAM lparam); DECLARE_MESSAGE_MAP() |
rajouter la macro ON_MESSAGE comme ci-dessous :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | BEGIN_MESSAGE_MAP(CSdisamplesView, CFormView) //{{AFX_MSG_MAP(CSdisamplesView) //}}AFX_MSG_MAP // Standard printing commands ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview) ON_MESSAGE(WM_TEST, OnReceiveTest) // message privé END_MESSAGE_MAP() long CSdisamplesView::OnReceiveTest(WPARAM wparam, LPARAM lparam) { AfxMessageBox("coucou"); return 0L; } |
ou SendMessage :traitement immédiat et attente d'une réponse.
Exemple: appel à partir de la classe où est défini le message:
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | //------------------------------------------------------------------- void CSdisamplesView::OnButtonUp(NMHDR* pNMHDR, LRESULT* pResult) { PostMessage(WM_TEST); } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | void CSdisamplesView::OnButton1() { // TODO: Add your control notification handler code here // le handle de fenetre est passé en arguement par GetSafeWnd(). AfxBeginThread(TheThread,GetSafeHwnd(),THREAD_PRIORITY_NORMAL) ; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | UINT TheThread(LPVOID pParam) { HWND hWnd= reinterpret_cast< HWND >( pvParam) ; ::PostMessage(hWnd,WM_TEST,0,0) ; return 0 ; } |
A l'instar de la librairie QT qui permet de définir dynamiquement une fonction de réponse à un message en provenance d'un contrôle, je propose la définition d'un mécanisme semblable avec les MFC.
Sous QT on peut trouver ce genre de syntaxe :
Code c++ : | Sélectionner tout |
1 2 | connect(button,SIGNAL(clicked()),SLOT(action())); |
Je propose la syntaxe suivante en MFC :
Code c++ : | Sélectionner tout |
1 2 | connect(IDC_BUTTON1,SIGNAL(CTestQTMsgView::clicked),SLOT(CTestQTMsgView::action)); |
CTestQTMsgView::clicked et CTestQTMsgView::action correspondent à l'adresse de fonctions non statique à la classe.
En fait cette syntaxe repose sur la possibilité d'avoir un pointeur sur une fonction non statique à une classe.
Cette possibilité souvent méconnue est utilisée par les macros MFC des Messages Map.
Un exemple ci-dessous issu de MSDN :
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 | include <iostream.h> class Data { private: int y; static int x; public: void SetData(int value) {y = value; return;}; int GetData() {return y;}; static void SSetData(int value) {x = value; return;}; static int SGetData() {return x;}; }; int Data::x = 0; void main(void) { Data mydata, mydata2; // Initialize pointer. void (Data::*pmfnP)(int) = &Data::SetData; // mydata.SetData; // Initialize static pointer. void (*psfnP)(int) = &Data::SSetData; mydata.SetData(5); // Set initial value for private data. cout << "mydata.data = " << mydata.GetData() << endl; (mydata.*pmfnP)(20); // Call member function through pointer. cout << "mydata.data = " << mydata.GetData() << endl; (mydata2.*pmfnP)(10) ; // Call member function through pointer. cout << "mydata2.data = " << mydata2.GetData() << endl; (*psfnP)(30) ; // Call static member function through pointer. cout << "static data = " << Data::SGetData() << endl ; } |
L'appel de la fonction se fait par l'intermédiaire de l'objet.
Revenons à notre exemple qui donne ceci :
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 133 | #if !defined(DynMsgMap) #define DynMsgMap #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 typedef bool (AFX_MSG_CALL CCmdTarget::*DYN_PMSGV)(UINT n); typedef int (AFX_MSG_CALL CCmdTarget::*DYN_PMSGI)(UINT n); #define SIGNAL(fct) (DYN_PMSGI)& fct #define SLOT(fct) (DYN_PMSGV)& fct template <class DYN_MSG_MAP> class CTplDynMsgMap : public DYN_MSG_MAP { public: CTplDynMsgMap( UINT nIDTemplate ):DYN_MSG_MAP(nIDTemplate){} struct DynMSgMAP { UINT nMessage; // windows message UINT nCode; // control code or WM_NOTIFY code UINT nID; // control ID (or 0 for windows messages) UINT nLastID; // used for entries specifying a range of control id's DYN_PMSGV pfn; // routine to call (or special value) }; //------------------------- //etablit le lien entre la fonction de réponse et le contrôle. bool connect(UINT nIdCtrl,DYN_PMSGI pfnSignal,DYN_PMSGV pfnAction) { return _connect(nIdCtrl,nIdCtrl,pfnSignal,pfnAction); } //------------------------- //etablit le lien entre la fonction de réponse et la plage de contrôles. bool connect(UINT nIdCtrl,UINT nIdCtrlEnd,DYN_PMSGI pfnSignal,DYN_PMSGV pfnAction) { return _connect(nIdCtrl,nIdCtrlEnd,pfnSignal,pfnAction); } //------------------------- // clic milieu control ou fenetre int mclicked(UINT nIdCtrl){return WM_MBUTTONDOWN;} //------------------------- // double clic control ou fenetre int dclicked(UINT nIdCtrl) { return (IsButton(nIdCtrl)?BN_DBLCLK:WM_LBUTTONDBLCLK); } //------------------------- // clic droit control ou fenetre int rclicked(UINT nIdCtrl){return WM_RBUTTONDOWN;} //------------------------- // clic control int clicked(UINT nIdCtrl) { return (IsButton(nIdCtrl)?BN_CLICKED:WM_LBUTTONDOWN); } //------------------------- virtual BOOL PreTranslateMessage(MSG* pMsg) { UINT nID =0; nID =::GetDlgCtrlID(pMsg->hwnd); for(int i=0;i<m_aDynMsg.GetSize();i++) { if(m_aDynMsg[i].nCode==pMsg->message && nID>=m_aDynMsg[i].nID && nID<=m_aDynMsg[i].nLastID) { if(pMsg->hwnd==m_hWnd) nID=0; if(!((*this).*m_aDynMsg[i].pfn)(nID)) return FALSE; break; } } return DYN_MSG_MAP::PreTranslateMessage(pMsg); } //------------------------- virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) { // crack message parameters UINT nID = LOWORD(wParam); UINT nCode = HIWORD(wParam); if(nCode==WM_COMMAND) for(int i=0;i<m_aDynMsg.GetSize();i++) { if(m_aDynMsg[i].nCode==WM_COMMAND && (nID>=m_aDynMsg[i].nID && nID <= m_aDynMsg[i].nLastID)) { if(!((*this).*m_aDynMsg[i].pfn)(nID)) return FALSE; break; } } return DYN_MSG_MAP::OnCommand(wParam,lParam); } //------------------------- // renvoie si nIdCtrl represente un bouton. bool IsButton(UINT nIdCtrl) { char szClassName[50]; if(!nIdCtrl) return false; CWnd *pWnd=GetDlgItem(nIdCtrl); if(!pWnd) return false; ::GetClassName(pWnd->GetSafeHwnd(),szClassName,sizeof(szClassName)); return(!strcmp("Button",szClassName)); } //------------------------- private: CArray < struct DynMSgMAP ,struct DynMSgMAP > m_aDynMsg; // memorisation des liens. bool _connect(UINT nIdCtrl,UINT nIdCtrlEnd,DYN_PMSGI pfnSignal,DYN_PMSGV pfnAction) { struct DynMSgMAP msg; msg.nMessage=((*this).*pfnSignal)(nIdCtrl); msg.nCode=(!IsButton(nIdCtrl)?msg.nMessage:WM_COMMAND); if(!nIdCtrl) nIdCtrl=GetDlgCtrlID(); msg.nID=nIdCtrl; msg.nLastID=nIdCtrlEnd; msg.pfn=pfnAction; if(msg.nMessage!=-1) { m_aDynMsg.Add(msg); return true; } return false; } }; #endif // !defined(DynMsgMap) |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | class CTestQTMsgView : public CTplDynMsgMap<CFormView> { protected: // create from serialization only CTestQTMsgView(); DECLARE_DYNCREATE(CTestQTMsgView) bool action(UINT nIdCtrl); // fonction de réponse //............ |
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 | CTestQTMsgView::CTestQTMsgView() : CTplDynMsgMap<CFormView>(CTestQTMsgView::IDD) { //{{AFX_DATA_INIT(CTestQTMsgView) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // TODO: add construction code here } void CTestQTMsgView::OnInitialUpdate() { CFormView::OnInitialUpdate(); ResizeParentToFit(); connect(IDC_BUTTON1,SIGNAL(CTestQTMsgView::clicked),SLOT(CTestQTMsgView::action)); } ///////////////////////////////////////////////////////////////////////////// void CTestQTMsgView::action(UINT nIdCtrl) { AfxMessageBox("coucou dynamique"); } |
Noter l'usage des defines SLOT et SIGNAL pour éviter le cast et de devoir écrire & .
la fonction clicked est à compléter pour les autres contrôles .
le système peut être étendu ...
Avec une macro d'interception étendue :
La macro d'interception étendue ON_COMMAND_EX (par exemple) prend 2 paramètres, l'ID de la commande et le nom du gestionnaire. Si la fonction renvoie FALSE, l'architecture d'application recherche un autre gestionnaire de commandes.
Le ClassWizard ne gère pas les gestionnaires étendus. On doit tout faire à la main.
Exemple de gestionnaire interceptant 2 événements :
Dans la barre de menu, ajoutons le menu Test, et avec les options de menu "Item 1", "Item 2", "Item 3". Puis écrivons le code suivant dans la classe CMainFrame :
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 | BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code ! ON_WM_CREATE() //}}AFX_MSG_MAP ON_COMMAND_EX(ID_ITEM1,OnItem) ON_COMMAND_EX(ID_ITEM2,OnItem) ON_COMMAND_EX(ID_ITEM3,OnItem) END_MESSAGE_MAP() BOOL CMainFrame::OnItem(UINT nID) { CString msg; switch(nID) { case ID_ITEM1: msg = "Item1"; break; case ID_ITEM2: msg = "Item 2"; break; default: MessageBeep(0xFFFFFFFF); return FALSE; } AfxMessageBox(msg); return TRUE; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | protected: //{{AFX_MSG(CMainFrame) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); //}}AFX_MSG afx_msg BOOL OnItem(UINT nID); DECLARE_MESSAGE_MAP() |
Les macros suffixées _RANGE permettent de traiter des groupes de commandes dont les ID sont consécutifs.
Par exemple, si les items de tout à l'heure ont pour ID :
Code c++ : | Sélectionner tout |
1 2 3 | #define ID_ITEM1 32771 #define ID_ITEM2 32772 #define ID_ITEM3 32773 |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code ! ON_WM_CREATE() //}}AFX_MSG_MAP ON_COMMAND_RANGE(ID_ITEM1,ID_ITEM3,OnItemRange) END_MESSAGE_MAP() void CMainFrame::OnItemRange(UINT nID) { CString msg; msg.Format("CMainFrame::OnItemRange, nID = %d",nID); AfxMessageBox(msg); } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | protected: //{{AFX_MSG(CMainFrame) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); //}}AFX_MSG afx_msg void OnItemRange(UINT nID); DECLARE_MESSAGE_MAP() |
Si on reprend le 1er exemple, alors il suffit de modifier la table d'interception :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 | BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code ! ON_WM_CREATE() //}}AFX_MSG_MAP ON_COMMAND_EX_RANGE(ID_ITEM1,ID_ITEM3,OnItem) END_MESSAGE_MAP() |
Code c++ : | Sélectionner tout |
1 2 3 4 | rubrique dans la table d'interception Prototype ON_COMMAND_EX(<id>,<FMembre>) afx_msg BOOL FMembre(UINT) ON_COMMAND_EX_RANGE(<id>,<idLast>,<FMembre>) afx_msg BOOL FMembre(UINT) ON_COMMAND_RANGE(<id>,<idLast>,<FMembre>) afx_msg void FMembre(UINT) |
Code c++ : | Sélectionner tout |
1 2 3 4 5 | rubrique dans la table d'interception Prototype ON_CONTROL_RANGE(<wCodNotif>,<id>,<idLast>,<FMembre>) afx_msg void FMembre(UINT) ON_NOTIFY_EX(<wCodNotif>,<id>,<FMembre>) afx_msg BOOL FMembre(UINT,NMHDR*,LRESULT*) ON_NOTIFY_EX_RANGE(<wCodNotif>,<id>,<idLast>,<FMembre>) afx_msg BOOL FMembre(UINT,NMHDR*,LRESULT*) ON_NOTIFY_RANGE(<wCodNotif>,<id>,<idLast>,<FMembre>) afx_msg void FMembre(UINT,NMHDR*,LRESULT*) |
Le raccourci clavier donne la possibilité de relier une séquence de touches à une fonction.
Pour le mettre en place procéder comme suit :
Ouvrir l'éditeur de ressources, section : accélérateurs :
Double cliquer sur l'identifiant de la frame concernée.
Note dans le cas d'un projet SDI :IDR_MAINFRAME
Cliquer en bas de la liste pour rajouter une nouvelle séquence qui disposera d'un identifiant au même titre qu'un menu.
Au niveau de la vue de traitement il restera à intercepter l'identifiant clavier qui générera une fonction de réponse avec la macro ON_COMMAND
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | BEGIN_MESSAGE_MAP(CSampleSDIView, CFormView) //{{AFX_MSG_MAP(CSampleSDIView) ON_COMMAND(ID_MYHOTKEY, OnMyhotkey) //…………… void CSampleSDIView::OnMyhotkey() { // TODO: Add your command handler code here AfxMessageBox("coucou"); } |
Lorsque l'on intercepte des messages réfléchis sur un contrôle classwizard génère les macros ON_NOTIFY_REFLECT dans la boucle des messages.
Exemple sur un CTabCtrl avec les messages TCN_SELCHANGE.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | BEGIN_MESSAGE_MAP(CXTabCtrl, CTabCtrl) //{{AFX_MSG_MAP(CXTabCtrl) ON_NOTIFY_REFLECT(TCN_SELCHANGE, OnSelchange) ON_NOTIFY_REFLECT(TCN_SELCHANGING, OnSelchanging) //}}AFX_MSG_MAP END_MESSAGE_MAP() |
Visual généra donc des messages de type ON_NOTIFY sur la fenêtre parent.
Exemple sur la fenêtre parent d'un CTabCtrl :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | BEGIN_MESSAGE_MAP(CTabctrlDlg, CDialog) //{{AFX_MSG_MAP(CTabctrlDlg) ON_NOTIFY(TCN_SELCHANGE, IDC_TAB, OnSelchangeTab) ON_NOTIFY(TCN_SELCHANGING, IDC_TAB, OnSelchangingTab) //}}AFX_MSG_MAP END_MESSAGE_MAP() |
Pour en disposer il faudra modifier au niveau du contrôle la macro ON_NOTIFY_REFLECT et les remplacer par ON_NOTIFY_REFLECT_EX qui permet l'envoi du message au contrôle et au parent.
Quiconque a déplacé une fenêtre Windows avec la souris s'est demandé comment utiliser ce même mécanisme.
L'exemple qui suit montre comment modifier la taille d'un CEdit par la souris et l'appui sur la touche majuscule, ainsi que son déplacement dans la fenêtre parent avec la touche contrôle combinée avec la souris.
La technique utilisée permet de simuler le mécanisme Windows lorsqu'on se trouve sur le bandeau de la fenêtre.
Le message à envoyer est WM_SYSCOMMAND avec le paramètre SC_MOVE ou SC_SIZE auquel on rajoutera la valeur HTCAPTION pour simuler le message comme sur un bandeau de fenêtre.
Exemple appliqué à un CEdit:
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #define IsShiftDown()( (GetKeyState(VK_SHIFT) & (1 << (sizeof(SHORT)*8-1))) != 0 ) #define IsCtrlDown()( (GetKeyState(VK_CONTROL) & (1 << (sizeof(SHORT)*8-1))) != 0 ) void CMyEdit::OnLButtonDown(UINT nFlags, CPoint point) { // TODO : ajoutez ici le code de votre gestionnaire de messages et/ou les paramètres par défaut des appels if(IsShiftDown()) { ReleaseCapture(); SendMessage(WM_SYSCOMMAND, SC_SIZE |HTCAPTION , 0); } else if(IsCtrlDown()) { ReleaseCapture(); SendMessage(WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0); } else CWnd::OnLButtonDown(nFlags, point); } |
On associera ensuite une variable de type contrôle à l'édit dans la fenêtre de ressources en indiquant la nouvelle classe personnalisée.
En utilisant la fonction GetAsyncKeyState.
si dans la valeur de retour le bit le plus fort est positionné ça voudra dire que la touche est enfoncée .
D'où le test à 0x8000 (32768) dans le code ci-dessous:
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | void GetMouseStatus(bool &bLeftButton,bool &bRightButton,bool &bMiddleButton) { bLeftButton = (GetAsyncKeyState(VK_LBUTTON) & 0x8000); bRightButton = (GetAsyncKeyState(VK_RBUTTON) & 0x8000); bMiddleButton = (GetAsyncKeyState(VK_MBUTTON) & 0x8000); } // utilisation: bool bLeftButton,bRightButton,bMiddleButton; GetMouseStatus(bLeftButton,bRightButton,bMiddleButton); TRACE("\n bLeftButton:%d,bRightButton :%d,bMiddleButton :%d",bLeftButton,bRightButton,bMiddleButton); |
On procédera comme suit :
Au premier message WM_MOUSEMOVE sur la fenêtre on appellera la méthode SetCapture().
Dés lors tous les messages claviers et souris seront transmis à la fenêtre, même ceux en dehors.
Il restera à savoir si le curseur de la souris est à l'extérieur ou à l'intérieur de la fenêtre.
Si il est à l'extérieur il faudra relâcher la capture par la méthode ReleaseCapture().
Exemple d'implémentation:
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 | void CMDIMultiView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CFormView::OnMouseMove(nFlags, point); // bool m_bIsCapture : variable membre initialisée a false dans le constructeur if(!m_bIsCapture) SetCapture(); m_bIsCapture=true; CRect rect; ClientToScreen(&point); GetWindowRect(&rect); if(!rect.PtInRect(point)) { ReleaseCapture(); m_bIsCapture=false; AfxMessageBox("Attention vous sortez de la fenêtre",MB_ICONEXCLAMATION ); } } |
Les nostalgiques des programmes consoles le savent bien, il est très pratique de naviguer dans des champs de saisies par les touches flèches haut et bas.
Sous Windows ce comportement n'est pas standard sauf quand le contrôle fait partie d'un group.
Dans ce contexte comment passer au contrôle suivant par la touche flèche bas ?
On procédera comme suit :
On interviendra directement au niveau la fonction virtuelle PreTranslateMessage de la fenêtre parent et on utilisera GetNextDlgTabItem voir Comment trouver le premier contrôle dans l'ordre de tabulation ? qui donne le prochain contrôle dans l'ordre de tabulation.
Code C++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | BOOL CMyDlg::PreTranslateMessage(MSG* pMsg) { if ( pMsg->message == WM_KEYDOWN ) { if( pMsg->wParam == VK_DOWN ) { CWnd *pWnd=GetNextDlgTabItem(GetFocus()); if(pWnd) pWnd->SetFocus(); return TRUE; } } return CDialog::PreTranslateMessage(pMsg); } |
Il est parfois utile de savoir quel est le premier contrôle dans l'ordre de tabulation pour une fenêtre, on utilisera la méthode :
Code c++ : | Sélectionner tout |
1 2 3 | CWnd::GetNextDlgTabItem CWnd* GetNextDlgTabItem( CWnd* pWndCtl, BOOL bPrevious = FALSE ) const; |
Code c++ : | Sélectionner tout |
1 2 | CWnd *pFirstCtrl=this->GetNextDlgTabItem(NULL); |
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.