BCN_HOTITEMCHANGE notification code is not getting called with CMFCButton class.
I'm using Visual Studio 2010
Steps performed:
1) Add a CButton to my dialog from the resource editor.
2) Use my own derived class CMFCButtonEx derived from CMFCButton class (which in turn is derived from CButton class).
3) Declare CMFCButtonEx 'm_btn_ex' instance variable to my dialog.
4) Add DDX_Control(pDX, IDC_BUTTON_EX, m_btn_ex);
5) When I click this button all my message handlers in the derived class are called except BCN_HOTITEMCHANGE notification associated with OnButtonItemChange function. The breakpoint in that function never hits. All the other message handlers (OnMouseMove, OnMouseLeave, and OnButtonClicked) are called correctly, and the breakpoints in them get hit.
As a note if I derive by CMFCButtonEx class from CButton class, then I can hit the breakpoint in OnButtonItemChange function i.e. BCN_HOTITEMCHANGE notification code is handled correctly
For some reason my BCN_HOTITEMCHANGE notification code is not being handled correctly by CMFCButton class. I can't understand what am I doing wrong here. I have also tried to use TBN_HOTITEMCHANGE notification code instead of BCN_HOTITEMCHANGE but the breakpoint in the OnButtonItemChange function never hits.
Thanks!
CMFCButtonEx class code:
IMPLEMENT_DYNAMIC(CMFCButtonEx, CMFCButton)
CMFCButtonEx::CMFCButtonEx()
{
m_bMouseTracking = FALSE
}
CMFCButtonEx::~CMFCButtonEx()
{
}
BEGIN_MESSAGE_MAP(CMFCButtonEx, CMFCButton)
//{{AFX_MSG_MAP(CMFCButtonEx)
ON_WM_MOUSEMOVE()
ON_WM_MOUSELEAVE()
ON_NOTIFY_REFLECT_EX(BCN_HOTITEMCHANGE, OnButtonItemChange)
//ON_NOTIFY_REFLECT_EX(TBN_HOTITEMCHANGE, OnButtonItemChange)
ON_CONTROL_REFLECT_EX(BN_CLICKED, OnButtonClicked)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BOOL CMFCButtonEx::OnButtonClicked()
{
AfxMessageBox("MFC Button Clicked");
return FALSE;
}
BOOL CMFCButtonEx::OnButtonItemChange(NMHDR* pNMHDR, LRESULT* pResult)
{
NMBCHOTITEM* pnmbchotitem = (NMBCHOTITEM*)pNMHDR;
AfxMessageBox("MFC Button Item Change");
*pResult = 0;
return FALSE;
}
void CMFCButtonEx::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bMouseTracking)
{
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = this->m_hWnd;
if (::_TrackMouseEvent(&tme))
{
m_bMouseTracking = TRUE;
AfxMessageBox("MFC Button Mouse Move");
}
}
CMFCButton::OnMouseMove(nFlags, point);
}
void CMFCButtonEx::OnMouseLeave()
{
m_bMouseTracking = FALSE;
AfxMessageBox("MFC Button Mouse Move");
CMFCButton::OnMouseLeave();
}
BCN_HOTITEMCHANGE is not triggered for CMFCButton, or any classes derived from CMFCButton
As stated in documentation, Visual Style must be enabled otherwise BCN_HOTITEMCHANGE is not triggered. CMFCButton relies on owner draw method, so visual style is disabled for this button.
You could remove BS_OWNERDRAW from your CMFCButtonEx class. But this is not recommended because CMFCButtonEx relies on BS_OWNERDRAW.
A better solution is derive your class from CButton (and make sure Visual Styles is enabled), use custom draw instead of owner draw. You may not need OnMouseMove and OnMouseLeave.
class CMyButton : public CButton {}
...
BOOL CMyButton::OnButtonItemChange(NMHDR* pNMHDR, LRESULT* pResult)
{
auto ptr = (NMBCHOTITEM*)pNMHDR;
bool enter = ptr->dwFlags & HICF_ENTERING;
if (enter)
TRACE(L"CMyButton... enter\n");
else
TRACE(L"CMyButton... exit\n");
*pResult = 0;
return FALSE;
}
Related
I've faced with strange behaviour of a home ribbon button.
I've created standard MFC application in Visual Studio 2010 with Office template that has a ribbon control. But if I double click on the Home ribbon button at the upper position the application is closed.
Could you please tell me if it is standard MFC application handlers behaviour and how I can change it?
I've looked at Prevent double click on MFC-Dialog button but couldn't apply it to my case (more clearly - I don't know how to add double click handler to a ribbon home button).
CMFCRibbonApplicationButton is not derived from CWnd so cannot handle WM_LBUTTONDBLCLK message.
One solution is to derive from CMFCRibbonBar.
class CCustomRibbonBar : public CMFCRibbonBar
{
// ...
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
};
BEGIN_MESSAGE_MAP(CCustomRibbonBar, CMFCRibbonBar)
ON_WM_LBUTTONDBLCLK()
END_MESSAGE_MAP()
void CCustomRibbonBar::OnLButtonDblClk(UINT nFlags, CPoint point)
{
CMFCRibbonBaseElement* pHit = HitTest(point);
if (pHit->IsKindOf(RUNTIME_CLASS(CMFCRibbonApplicationButton)))
{
// the user double-clicked in the application button
// do what you want here but do not call CMFCRibbonBar::OnLButtonDblClk
return;
}
CMFCRibbonBar::OnLButtonDblClk(nFlags, point);
}
Another solution: override PreTranslateMessage in CMainFrame class;
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
if ((WM_LBUTTONDBLCLK == pMsg->message) && (pMsg->hwnd == m_wndRibbonBar))
{
CPoint point(pMsg->pt);
m_wndRibbonBar.ScreenToClient(&point);
CMFCRibbonBaseElement* pHit = m_wndRibbonBar.HitTest(point);
if (pHit && pHit->IsKindOf(RUNTIME_CLASS(CMFCRibbonApplicationButton)))
{
// do what you want but do not call CMDIFrameWndEx::PreTranslateMessage
return TRUE; // no further dispatch
}
}
return CMDIFrameWndEx::PreTranslateMessage(pMsg);
}
Derive your own derived class of CMFCRibbonApplicationButton.
Create a message handler for CMFCRibbonApplicationButton::OnLButtonDblClk
Provide your own implementation of what you want to do on the double click. If nothing should happen, just leave the body empty.
In your CMainFrame you find a definition of CMFCRibbonApplicationButton m_MainButton. Replace the class name with your implementation.
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);
}
I don't know how to disable the CListCtrl select option. I want to override the CListCtrl class method or handle any window command ? Thanks.
If you want to stop the user selecting an item in a CListCtrl, you need to derive your own class from CListCtrl and add a message handler for the LVN_ITEMCHANGING notification.
So, an example class CMyListCtrl would have a header file:
MyListCtrl.h
#pragma once
class CMyListCtrl : public CListCtrl
{
DECLARE_DYNAMIC(CMyListCtrl)
protected:
DECLARE_MESSAGE_MAP()
public:
// LVN_ITEMCHANGING notification handler
afx_msg void OnLvnItemchanging(NMHDR *pNMHDR, LRESULT *pResult);
};
And then MyListCtrl.cpp:
#include "MyListCtrl.h"
IMPLEMENT_DYNAMIC(CMyListCtrl, CListCtrl)
BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(LVN_ITEMCHANGING, &CMyListCtrl::OnLvnItemchanging)
END_MESSAGE_MAP()
void CMyListCtrl::OnLvnItemchanging(NMHDR *pNMHDR, LRESULT *pResult)
{
// LVN_ITEMCHANGING notification handler
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
// is the user selecting an item?
if ((pNMLV->uChanged & LVIF_STATE) && (pNMLV->uNewState & LVNI_SELECTED))
{
// yes - never allow a selected item
*pResult = 1;
}
else
{
// no - allow any other change
*pResult = 0;
}
}
So you can, for example, add a normal CListCtrl to a dialog, then create a member variable for it (by default it will be CListCtrl) then edit your dialog's header file to #include "MyListCtrl.h and change the list control member variable from CListCtrl to CMyListCtrl.
I'm writing a WTL Aero wizard and I'd like to gray out the window's Close button (its first step will require no user interaction and can not be canceled, so disabling the button is perfectly appropriate).
Putting the following code:
CMenuHandle pMenu = GetSystemMenu(FALSE);
pMenu.EnableMenuItem(SC_CLOSE, FALSE);
in OnInitDialog does not work, as the procedure is called before the window itself is displayed on the screen (the ATLASSERT(::IsMenu(m_hMenu)); assertion in EnableMenuItem is tripped at runtime).
Is there an elegant way to disable the Close button? (I'm a WTL beginner, and I'd like the solution to be as clean as possible).
This is a minimal version of the wizard's page code:
#include "stdafx.h"
class MainPage : public CAeroWizardPageImpl<MainPage> {
public:
BEGIN_MSG_MAP(MainPage)
MESSAGE_HANDLER_EX(WM_INITDIALOG, OnInitDialog)
CHAIN_MSG_MAP(__super)
END_MSG_MAP()
enum {
IDD = IDR_MAINFRAME
};
MainPage() : CAeroWizardPageImpl<MainPage>(IDR_MAINFRAME) {
/* Set the wizard's title */
m_headerTitle.LoadString(IDS_INSTALLHEADER);
SetHeaderTitle(m_headerTitle);
}
private:
CString m_headerTitle;
LRESULT OnInitDialog(UINT message, WPARAM wParam, LPARAM lParam) {
UNREFERENCED_PARAMETER(message);
UNREFERENCED_PARAMETER(wParam);
UNREFERENCED_PARAMETER(lParam);
/* Disable the wizard buttons and center the window */
ShowWizardButtons(0, 0);
EnableWizardButtons(PSWIZB_BACK, 0);
CenterWindow();
return TRUE;
}
};
The close [X] button is a part of Common Controls wizard property sheet class. You are not supposed to alter its presentation and behavior. What you can do is to handle PSN_QUERYCANCEL notification and prevent wizard from closing. With WTL it is easy, however you need to know that that there are two versions of notifications handlers available.
If _WTL_NEW_PAGE_NOTIFY_HANDLERS is defined, typically in stdafx.h, then you do it like this:
class MainPage :
public CAeroWizardPageImpl<MainPage>
{
// ...
INT OnQueryCancel()
{
return 1; // Zero to Allow Wizard Close
}
};
Otherwise, older syntax is in use:
class MainPage :
public CAeroWizardPageImpl<MainPage>
{
// ...
BOOL OnQueryCancel()
{
return FALSE; // Allow Wizard Close?
}
};
Along with preventing cancellation/close you are free to indicate this by showing a message box suggesting use to wait until the pending operation is completed, or otherwise display a notification (e.g. flash a static control etc.)
I am using a MFC link control in my dialog based application,and I add an event handler of BN_CLICKED for it, hoping that it could do something for me, however, when i click on it, it just does nothing at all(when i don't set the url of it, when i set the url, it will jump to that url), my event handler is not triggered. So, how to disable its default "jump" behaviour and trigger my handler?
Subclassing CMFCLinkCtrl and adding an ON_WM_LBUTTONDOWN event handler seems to work.
You can then choose whether or not to call CMFCLinkCtrl::OnLButtonDown.
class CMyLinkCtrl : public CMFCLinkCtrl {
DECLARE_DYNAMIC(CMyLinkCtrl)
public:
CMyLinkCtrl();
virtual ~CMyLinkCtrl();
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
protected:
DECLARE_MESSAGE_MAP()
};
IMPLEMENT_DYNAMIC(CMyLinkCtrl, CMFCLinkCtrl)
CMyLinkCtrl::CMyLinkCtrl() {
}
CMyLinkCtrl::~CMyLinkCtrl() {
}
BEGIN_MESSAGE_MAP(CMyLinkCtrl, CMFCLinkCtrl)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
void CMyLinkCtrl::OnLButtonDown(UINT nFlags, CPoint point) {
static bool bDisabled = false;
if(bDisabled) {
MessageBox(_T("Link is disabled"));
} else {
CMFCLinkCtrl::OnLButtonDown(nFlags, point);
}
bDisabled = !bDisabled;
}