| auteur : Farscape | 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
DECLARE_MESSAGE_MAP ()
virtual void OnRangeUpdateEdit (UINT nID);
virtual void OnRangeUpdateCheck (UINT nID);
virtual void OnRangeUpdateRadio (UINT nID);
|
Dans le .cpp
BEGIN_MESSAGE_MAP (CMyDlg, CDialog)
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 ))
{
}
else
{
}
}
void CMyDlg:: OnRangeUpdateCheck (UINT nID)
{
LRESULT lResult = SendDlgItemMessage (nID, BM_GETSTATE, 0 , 0 );
if ( lResult = = BST_CHECKED )
{
}
}
void CMyDlg:: OnRangeUpdateRadio (UINT nID)
{
if ( nID = = IDC_RADIO2 )
{
}
}
|
|
| auteur : Farscape | 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_TEST dans la classe fenêtre CSdisamplesView.
Comment procéder :
Définir le message privé :
# define WM_TEST WM_USER + 100
|
WM_USER correspond à la constante de départ pour les messages privés.
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 :
#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()
|
Dans l'include placer la définition de la fonction en dehors des balises AFX_MSG
Dans le code:
rajouter la macro ON_MESSAGE comme ci-dessous :
BEGIN_MESSAGE_MAP (CSdisamplesView, CFormView)
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)
END_MESSAGE_MAP ()
long CSdisamplesView:: OnReceiveTest (WPARAM wparam, LPARAM lparam)
{
AfxMessageBox (" coucou " );
return 0L ;
}
|
pour appeler la fonction il suffira d'utiliser PostMessage :pas d'attente se place dans la file d'attente des messages.
ou SendMessage :traitement immédiat et attente d'une réponse.
Exemple: appel à partir de la classe où est défini le message:
void CSdisamplesView:: OnButtonUp (NMHDR* pNMHDR, LRESULT* pResult)
{
PostMessage (WM_TEST);
}
|
Appel à partir d'un Thread de travail initié par la classe Fenêtre :
void CSdisamplesView:: OnButton1 ()
{
AfxBeginThread (TheThread,GetSafeHwnd (),THREAD_PRIORITY_NORMAL) ;
}
|
Le Thread:
UINT TheThread (LPVOID pParam)
{
HWND hWnd= reinterpret_cast < HWND > ( pvParam) ;
:: PostMessage (hWnd,WM_TEST,0 ,0 ) ;
return 0 ;
}
|
|
| auteur : Farscape | 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 :
connect (button,SIGNAL (clicked ()),SLOT (action ()));
|
Ou clicked est le type d'événement intercepté et action la fonction de réponse utilisateur.
Je propose la syntaxe suivante en MFC :
connect (IDC_BUTTON1,SIGNAL (CTestQTMsgView:: clicked),SLOT (CTestQTMsgView:: action));
|
Où IDC_BUTTON1 est l'identifiant du bouton.
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 :
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;
void (Data:: * pmfnP)(int ) = & Data:: SetData;
void (* psfnP)(int ) = & Data:: SSetData;
mydata.SetData (5 );
cout < < " mydata.data = " < < mydata.GetData () < < endl;
(mydata.* pmfnP)(20 );
cout < < " mydata.data = " < < mydata.GetData () < < endl;
(mydata2.* pmfnP)(10 ) ;
cout < < " mydata2.data = " < < mydata2.GetData () < < endl;
(* psfnP)(30 ) ;
cout < < " static data = " < < Data:: SGetData () < < endl ;
}
|
Pour disposer de l'adresse de la fonction il suffit d'indiquer le nom de la classe suivis de :: .
L'appel de la fonction se fait par l'intermédiaire de l'objet.
Revenons à notre exemple qui donne ceci :
# 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;
UINT nCode;
UINT nID;
UINT nLastID;
DYN_PMSGV pfn;
} ;
bool connect (UINT nIdCtrl,DYN_PMSGI pfnSignal,DYN_PMSGV pfnAction)
{
return _connect (nIdCtrl,nIdCtrl,pfnSignal,pfnAction);
}
bool connect (UINT nIdCtrl,UINT nIdCtrlEnd,DYN_PMSGI pfnSignal,DYN_PMSGV pfnAction)
{
return _connect (nIdCtrl,nIdCtrlEnd,pfnSignal,pfnAction);
}
int mclicked (UINT nIdCtrl){ return WM_MBUTTONDOWN;}
int dclicked (UINT nIdCtrl)
{
return (IsButton (nIdCtrl)?BN_DBLCLK:WM_LBUTTONDBLCLK);
}
int rclicked (UINT nIdCtrl){ return WM_RBUTTONDOWN;}
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)
{
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);
}
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;
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 )
|
utilisation dans une CFormView:
class CTestQTMsgView : public CTplDynMsgMap< CFormView>
{
protected :
CTestQTMsgView ();
DECLARE_DYNCREATE (CTestQTMsgView)
bool action (UINT nIdCtrl);
|
le code dans le source:
CTestQTMsgView:: CTestQTMsgView ()
: CTplDynMsgMap< CFormView> (CTestQTMsgView:: IDD)
{
}
void CTestQTMsgView:: OnInitialUpdate ()
{
CFormView:: OnInitialUpdate ();
ResizeParentToFit ();
connect (IDC_BUTTON1,SIGNAL (CTestQTMsgView:: clicked),SLOT (CTestQTMsgView:: action));
}
void CTestQTMsgView:: action (UINT nIdCtrl)
{
AfxMessageBox (" coucou dynamique " );
}
|
dans la fonction OnInitialUpdate on établit le lien entre le click du bouton et la fonction de réponse local a la classe ..
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 ...
|
| auteur : Nourdine Falola | 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 :
BEGIN_MESSAGE_MAP (CMainFrame, CFrameWnd)
ON_WM_CREATE ()
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;
}
|
protected :
afx_msg int OnCreate (LPCREATESTRUCT lpCreateStruct);
afx_msg BOOL OnItem (UINT nID);
DECLARE_MESSAGE_MAP ()
|
Avec une macro d'interception traitant un groupe de commandes :
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 :
# define ID_ITEM1 32771
# define ID_ITEM2 32772
# define ID_ITEM3 32773
|
alors on pourrait écrire ainsi une fonction permettant d'afficher un message suivant l'item :
BEGIN_MESSAGE_MAP (CMainFrame, CFrameWnd)
ON_WM_CREATE ()
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);
}
|
protected :
afx_msg int OnCreate (LPCREATESTRUCT lpCreateStruct);
afx_msg void OnItemRange (UINT nID);
DECLARE_MESSAGE_MAP ()
|
Avec une macro d'interception étendue traitant un groupe de commandes:
Si on reprend le 1er exemple, alors il suffit de modifier la table d'interception :
BEGIN_MESSAGE_MAP (CMainFrame, CFrameWnd)
ON_WM_CREATE ()
ON_COMMAND_EX_RANGE (ID_ITEM1,ID_ITEM3,OnItem)
END_MESSAGE_MAP ()
|
Liste des macro d'interception permettant d'intercepter un groupe de commandes :
WM_COMMAND
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)
|
notification des contrôles
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* )
|
|
| auteur : Farscape | 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
BEGIN_MESSAGE_MAP (CSampleSDIView, CFormView)
ON_COMMAND (ID_MYHOTKEY, OnMyhotkey)
void CSampleSDIView:: OnMyhotkey ()
{
AfxMessageBox (" coucou " );
}
|
|
| auteur : Farscape | 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.
BEGIN_MESSAGE_MAP (CXTabCtrl, CTabCtrl)
ON_NOTIFY_REFLECT (TCN_SELCHANGE, OnSelchange)
ON_NOTIFY_REFLECT (TCN_SELCHANGING, OnSelchanging)
END_MESSAGE_MAP ()
|
Néanmoins on a parfois besoin de disposer de ces messages dans la fenêtre parent du contrôle.
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 :
BEGIN_MESSAGE_MAP (CTabctrlDlg, CDialog)
ON_NOTIFY (TCN_SELCHANGE, IDC_TAB, OnSelchangeTab)
ON_NOTIFY (TCN_SELCHANGING, IDC_TAB, OnSelchangingTab)
END_MESSAGE_MAP ()
|
Le hic c'est que malgré l'adjonction des ces messages l'appel n'est pas effectué dans la fenêtre parent.
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.
|
| auteur : Farscape | 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 :
# 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)
{
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);
}
|
Note:Il faudra donc générer une classe dérivée de la classe CEdit et intercepter le message WM_LBUTTONDOWN
On associera ensuite une variable de type contrôle à l'édit dans la fenêtre de ressources en indiquant la nouvelle classe personnalisée.
|
| auteur : Farscape | 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:
void GetMouseStatus (bool & bLeftButton,bool & bRightButton,bool & bMiddleButton)
{
bLeftButton = (GetAsyncKeyState (VK_LBUTTON) & 0x8000 );
bRightButton = (GetAsyncKeyState (VK_RBUTTON) & 0x8000 );
bMiddleButton = (GetAsyncKeyState (VK_MBUTTON) & 0x8000 );
}
bool bLeftButton,bRightButton,bMiddleButton;
GetMouseStatus (bLeftButton,bRightButton,bMiddleButton);
TRACE (" \n bLeftButton:%d,bRightButton :%d,bMiddleButton :%d " ,bLeftButton,bRightButton,bMiddleButton);
|
|
| auteur : Farscape | 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:
void CMDIMultiView:: OnMouseMove (UINT nFlags, CPoint point)
{
CFormView:: OnMouseMove (nFlags, point);
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 );
}
}
|
|
| auteur : Farscape | 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.
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);
}
|
|
| auteur : Farscape | 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 :
CWnd:: GetNextDlgTabItem
CWnd* GetNextDlgTabItem ( CWnd* pWndCtl, BOOL bPrevious = FALSE ) const ;
|
CWnd * pFirstCtrl= this - > GetNextDlgTabItem (NULL );
|
|
Consultez les autres F.A.Q.
|
|