I have a Windows GUI program (Visual Studio 2019) with many buttons that perform the same function but on a different device. Rather than have 20 different functions along the lines of:
BEGIN_MESSAGE_MAP(CChainDlg, CDialogEx)
ON_BN_CLICKED ( IDC_CHECK_1, &CChainDlg::OnBnClickedCheck1 )
ON_BN_CLICKED ( IDC_CHECK_2, &CChainDlg::OnBnClickedCheck2 )
ON_BN_CLICKED ( IDC_CHECK_3, &CChainDlg::OnBnClickedCheck3 )
...
END_MESSAGE_MAP()
...
void CChainDlg::OnBnClickedCheck1() {...}
void CChainDlg::OnBnClickedCheck2() {...}
void CChainDlg::OnBnClickedCheck3() {...}
... each of which performs the same basic function only differing in an array index, I'd like to have one function that can accept an index.
The ON_BN_CLICKED macro does not want to take arguments which would seem like the most obvious way of accomplishing this. E.g.:
BEGIN_MESSAGE_MAP(CChainDlg, CDialogEx)
ON_BN_CLICKED ( IDC_CHECK_1, &CChainDlg::OnBnClickedCheck(0) )
ON_BN_CLICKED ( IDC_CHECK_2, &CChainDlg::OnBnClickedCheck(1) )
ON_BN_CLICKED ( IDC_CHECK_3, &CChainDlg::OnBnClickedCheck(2) )
...
END_MESSAGE_MAP()
What I currently do is write the generic function that takes an argument and have the individual OnBnClickedCheck1() functions call the generic function with the correct argument. But that just seems messy.
Suggest to me a better way!
If your button control IDs are sequential in value, you can use the ON_CONTROL_RANGE() macro to map all of the controls to a single handler, eg:
BEGIN_MESSAGE_MAP(CChainDlg, CDialogEx)
ON_CONTROL_RANGE( BN_CLICKED, IDC_CHECK_1, IDC_CHECK_3, &CChainDlg::OnBnClicked )
...
END_MESSAGE_MAP()
...
void CChainDlg::OnBnClicked(UINT nID) {...}
Now your handler can take a parameter that tells it which control triggered the notification.
This is described in more detail on MSDN:
Handlers for Message-Map Ranges
To expand on Captain Obvlious's comment: You could use a template method:
class CChainDlg {
BEGIN_MESSAGE_MAP(CChainDlg, CDialogEx)
ON_BN_CLICKED ( IDC_CHECK_1, &CChainDlg::OnBnClickedCheck<1> )
ON_BN_CLICKED ( IDC_CHECK_2, &CChainDlg::OnBnClickedCheck<2> )
ON_BN_CLICKED ( IDC_CHECK_3, &CChainDlg::OnBnClickedCheck<3> )
...
END_MESSAGE_MAP()
template<size_t idx>
void OnBnClickedCheck() {
... my_array[idx] ...
}
}
Use ON_COMMAND_EX. Because you just have a BN_CLICKED event here this will work:
BEGIN_MESSAGE_MAP(CChainDlg, CDialogEx)
ON_COMMAND_EX( IDC_CHECK_1, &CChainDlg::OnBnClicked )
ON_COMMAND_EX( IDC_CHECK_2, &CChainDlg::OnBnClicked )
...
END_MESSAGE_MAP()
...
BOOL CChainDlg::OnBnClicked(UINT nID) {...}
Related
ParentWnd contains mfc control called modeOfOperation (drop down list). When modeOfOperation is Normal everything is normal. We added new modeOfOperation=Extreme. When modeOfOperation is Extreme I want to disable 90% of existing ParentWnd controls because they don't work in Extreme mode. I have existing codebase with hundreds of UI controls. I want to find one place in code to disable 90% of them without hurting rest of functionality.
I know that 90% of UI controls that I need to disable are in several child windows. One of them is m_childWindow1. I need to tell if given message is is handled by m_childWindow1,...,m_childWindowN.
So ParentWnd routes messages to childWindow. I want to override childWindow handler in case when given message is handled by childWindow. So I need function like bool CWnd::isMessageIdInMessageMap(int id).
BOOL ParentWnd::OnCmdMsg( UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo )
{
if ( nCode == CN_UPDATE_COMMAND_UI )
{
CWnd *contents = m_childWindow1->getContents();
if( contents )
{
if( contents->OnCmdMsg( nID, nCode, pExtra, pHandlerInfo ) )
{
//I want to enter additional code here
//But I don't want to call contents->OnCmdMsg
return true;
}
}
}
}
...
}
Simply use the existing functions (OnCmdMsg).
Create your own CCmdUI object (overwrite the Enable... functions if required) pass is as pExtra argument to OnCmdMsg and you know after the call if the was a handler or not.
There are no side effects...
My application is a VC6 MFC dialog based application with multiple property pages.
I have to capture a mousemove event over a control, for example Checkbox.
How can I capture the mousemove events over a checkbox in MFC?
A checkbox is a button control (eg. CWnd). Derive your own class from CCheckBox and handle the OnMouseMove event.
Per request...assuming a class derived from CButton...
BEGIN_MESSAGE_MAP(CMyCheckBox, CButton)
ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()
void CMyCheckBox::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CButton::OnMouseMove(nFlags, point);
}
Thanks for your replies.. I found a way to get the mousemove event for my app.
WM_SETCURSOR windows message gets the mouse move. It returns the Cwnd pointer for a control and the dialog.
Find my code below.
BOOL CMyDialog::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
CWnd* pWndtooltip = GetDlgItem(IDC_STATIC_TOOLTIP);
if (pWnd != this)
{
if (IDC_SN_START_ON == pWnd->GetDlgCtrlID())
pWndtooltip->ShowWindow(SW_SHOW);
}
else
pWndtooltip->ShowWindow(SW_HIDE);
SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
return true;
}
I found in #raj's OnSetCursor() code, that the associated Member variable for IDC_STATIC_TOOLTIP is that variable to which you assign the desired tool tip text. For example, if the associated variable is m_strToolTip, assign the desired text to display during the hovering event as follows:
m_strToolTip.Format("%s", "Tool tip text goes here");
I also found that UpdateData() was required upon entry into the event handler and UpdateData(FALSE) was required before the return. The SetCursor() call seems to have no effect when commented.
You can also override CDialog::PreTranslateMessage:
BOOL CSomeDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_MOUSEMOVE && pMsg->hwnd == m_checkBox->m_hWnd)
{
...
}
return CDialog::PreTranslateMessage(pMsg);
}
In my application I have a standard MFC modal dialog. I'd like close that dialog when the user clicks outside the dialog window. For that purpose I put ON_MESSAGE(WM_KILLFOCUS, OnKillFocus) in the dialog's message map hy hand (the class wizard does not offer that option):
BEGIN_MESSAGE_MAP(CTestTreeCtrlDlg, CDialog)
//{{AFX_MSG_MAP(CTestTreeCtrlDlg)
ON_NOTIFY(TVN_SELCHANGED, IDC_TREE1, OnSelchangedTree)
//}}AFX_MSG_MAP
ON_MESSAGE(WM_KILLFOCUS, OnKillFocus)
END_MESSAGE_MAP()
...
void CTestTreeCtrlDlg::OnKillFocus()
{
...
}
Now if I click outside the dialog, the latter of course looses focus, but the OnKillFocus method won't get called for some reason.
Thank you patriiice !
WM_ACTIVATE does the job:
BEGIN_MESSAGE_MAP(CTestTreeCtrlDlg, CDialog)
//{{AFX_MSG_MAP(CTestTreeCtrlDlg)
ON_NOTIFY(TVN_SELCHANGED, IDC_TREE1, OnSelchangedTree)
//}}AFX_MSG_MAP
ON_WM_ACTIVATE()
END_MESSAGE_MAP()
...
void CTestTreeCtrlDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
CDialog::OnActivate(nState, pWndOther, bMinimized);
if (nState == WA_INACTIVE)
OnOK() ;
}
Quick search -> http://www.itlisting.org/5-windows/964b01901673b4b0.aspx
I am pretty sure it is a better approach to do this.
I have a CTreeCtrl and I like to use its TVN_ITEMEXPANDING message, but the handler function never calls.
CsetkliensDlg.h
afx_msg void OnItemExpanding(NMHDR* pNmhdr,LRESULT *lResult);
CsetkliensDlg.cpp
BEGIN_MESSAGE_MAP(CCsetkliensDlg, CDialogEx)
ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, &CCsetkliensDlg::OnItemExpanding)
END_MESSAGE_MAP()
...
void CCsetkliensDlg::OnItemExpanding(NMHDR* pNmhdr,LRESULT *lResult)
{
AfxMessageBox("almafa");
}
The items have childs.
You are trying to catch a notification in the parent dialog so you should use ON_NOTIFY instead of ON_NOTIFY_REFLECT.
Of course then your message map will be something lik:
ON_NOTIFY( TVN_ITEMEXPANDING, CTREECTRL_RESOURCES_ID, ONHandlerFunction)
You can use the reflection mechanism but then the handler should be in a CTreeCtrl derived class.
I have a WTL 8.0 SDI application for Windows Mobile 5. In this contrived example below, I create a view, destroy it, then re-create it. But, when it's re-created assertions in the WM_INITDIALOG handler fail because the control's HWND isn't valid.
I note that I can fix this by handling WM_DESTROY in CMyView and manually destroying every child control. But, I didn't think I should have to. MSDN even says:
This message is sent first to the
window being destroyed and then to the
child windows (if any) as they are
destroyed.
Anybody have an idea as to what's going on?
Edit: If I handle WM_NCDESTROY in CMyView, all of the the child control handles are still valid! (some_control_.IsWindow()==TRUE) That's not how it's supposed to be...
Thanks,
PaulH
class CMyView : public CDialogImpl< CMyView >,
public CWinDataExchange< CMyView >
{
// <snip> Message Map and other standard WTL macros </snip>
LRESULT OnInitDialog( UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/ )
{
DoDataExchange( FALSE );
// assertion fails within the SetWindowText() call
// atlwin.h line 876
// ATLASSERT(::IsWindow(m_hWnd));
some_control_.SetWindowText( _T( "Foo" ) );
return 0;
};
private:
CEdit some_control_;
}; // class CMyView
class CMainFrame : public CFrameWindowImpl< CMainFrame >,
public CUpdateUI< CMainFrame >,
public CMessageFilter,
public CIdleHandler
{
public:
// <snip> Message Map and other standard WTL macros </snip>
BOOL CMainFrame::PreTranslateMessage( MSG* pMsg )
{
if( CFrameWindowImpl< CMainFrame >::PreTranslateMessage( pMsg ) )
return TRUE;
return my_view_.PreTranslateMessage( pMsg );
};
LRESULT OnCreate( UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/ )
{
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT( pLoop != NULL );
pLoop->AddMessageFilter( this );
pLoop->AddIdleHandler( this );
m_hWndClient = my_view_.Create( m_hWnd );
my_view_.DestroyWindow();
m_hWndClient = my_view_.Create( m_hWnd );
};
private:
CMyView my_view_;
}; // class CMainFrame
It is not good practice to Create, Destroy and re-Create the same window, you should consider hiding it and reinitializing your contents when showing it again.
Anyhow your code will not ASSERT at re-creation with:
virtual void CMyView::OnFinalMessage(HWND)
{
some_control_.m_hWnd = 0;
}
I'm not a hundred percent sure, but it seems like the some_control_ CEdit control is not registered with the parent window. I think you will need to call some_control_.Create(...) with the parent handle as a parameter.
see msdn article for a documentation of CEdit::Create().