如何捕获CDateTimeCtrl控件上下调节按钮的Windows消息?
问题描述
我在对话框应用中使用了CDateTimeCtrl控件及回调字段,所有功能均正常运行,但尝试捕获该控件上类似Spin控件的上下调节按钮被点击的Windows消息却未能成功。通过Spy检测得知,该上下调节按钮的类为msctls_updown32,且Spy的消息日志也未提供有效线索。
我已尝试如下方式处理UDN_DELTAPOS消息,但仍无法从CMyTimeCtrl中捕获该消息:
class CMyTimeCtrl : public CDateTimeCtrl { DECLARE_DYNAMIC(CMyTimeCtrl) public: CMyTimeCtrl(); virtual ~CMyTimeCtrl(); protected: int m_msec; DECLARE_MESSAGE_MAP() public: virtual void DoDataExchange(CDataExchange* pDX); afx_msg void OnDtnFormat(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnDtnFormatquery(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnDtnWmkeydown(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnDeltaposSpin(NMHDR* pNMHDR, LRESULT* pResult); }; BEGIN_MESSAGE_MAP(CMyTimeCtrl, CDateTimeCtrl) ON_NOTIFY(WM_NOTIFY, UDN_DELTAPOS, &CMyTimeCtrl::OnDeltaposSpin) ON_NOTIFY_REFLECT(DTN_FORMAT, &CMyTimeCtrl::OnDtnFormat) ON_NOTIFY_REFLECT(DTN_FORMATQUERY, &CMyTimeCtrl::OnDtnFormatquery) ON_NOTIFY_REFLECT(DTN_WMKEYDOWN, &CMyTimeCtrl::OnDtnWmkeydown) END_MESSAGE_MAP()
解决方案
首先得指出你之前的问题:ON_NOTIFY的参数顺序搞反了,而且更关键的是,CDateTimeCtrl内部的msctls_updown32控件是它的子控件,这个子控件的ID是系统自动分配的,你没法直接通过ID捕获它的UDN_DELTAPOS通知。最靠谱的解决办法是子类化这个内部的UpDown控件,下面是具体步骤:
1. 创建自定义UpDown控件子类
先写一个继承自CUpDownCtrl的子类,用来处理UpDown控件的通知和鼠标事件:
class CMyUpDownCtrl : public CUpDownCtrl { DECLARE_DYNAMIC(CMyUpDownCtrl) public: CMyUpDownCtrl() = default; virtual ~CMyUpDownCtrl() = default; protected: DECLARE_MESSAGE_MAP() public: // 处理UDN_DELTAPOS通知,精准获取调节方向 afx_msg void OnDeltapos(NMHDR* pNMHDR, LRESULT* pResult); // 可选:直接处理鼠标点击事件 afx_msg void OnLButtonDown(UINT nFlags, CPoint point); }; BEGIN_MESSAGE_MAP(CMyUpDownCtrl, CUpDownCtrl) ON_NOTIFY_REFLECT(UDN_DELTAPOS, &CMyUpDownCtrl::OnDeltapos) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() void CMyUpDownCtrl::OnDeltapos(NMHDR* pNMHDR, LRESULT* pResult) { NM_UPDOWN* pNMUpDown = reinterpret_cast<NM_UPDOWN*>(pNMHDR); // pNMUpDown->iDelta 为+1表示点击了上按钮,-1表示点击了下按钮 // 在这里添加你的业务逻辑 *pResult = 0; // 告诉系统我们已经处理了这个通知 } void CMyUpDownCtrl::OnLButtonDown(UINT nFlags, CPoint point) { // 处理鼠标左键点击事件(如果需要的话) CUpDownCtrl::OnLButtonDown(nFlags, point); // 别忘了调用基类方法 }
2. 在CMyTimeCtrl中子类化内部UpDown控件
在CMyTimeCtrl的初始化阶段(比如OnCreate或者控件关联后的初始化方法里),找到内部UpDown控件的句柄,然后关联到我们的自定义子类:
// 在CMyTimeCtrl的头文件中添加成员变量: // CMyUpDownCtrl m_myUpDown; BOOL CMyTimeCtrl::OnInitDialog() { CDateTimeCtrl::OnInitDialog(); // 查找内部的msctls_updown32控件 HWND hUpDownWnd = FindWindowEx(m_hWnd, NULL, _T("msctls_updown32"), NULL); if (hUpDownWnd != nullptr) { // 子类化这个UpDown控件,之后它的消息就会转到我们的子类处理 m_myUpDown.SubclassWindow(hUpDownWnd); } return TRUE; }
备选方案:用PreTranslateMessage捕获鼠标点击
如果不想子类化控件,也可以在CMyTimeCtrl中重写PreTranslateMessage,直接检测鼠标是否点击了UpDown区域:
BOOL CMyTimeCtrl::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_LBUTTONDOWN) { HWND hUpDownWnd = FindWindowEx(m_hWnd, NULL, _T("msctls_updown32"), NULL); if (hUpDownWnd != nullptr) { CRect upDownRect; ::GetWindowRect(hUpDownWnd, &upDownRect); ScreenToClient(upDownRect); // 判断点击点是否在UpDown控件范围内 if (upDownRect.PtInRect(pMsg->pt)) { // 可以进一步判断是上按钮还是下按钮: // int halfHeight = upDownRect.Height() / 2; // bool isUpButton = (pMsg->pt.y < upDownRect.top + halfHeight); // 这里添加你的处理逻辑 } } } return CDateTimeCtrl::PreTranslateMessage(pMsg); }
注意:子类化的方法是最优解,因为它能直接捕获UDN_DELTAPOS通知,精准知道用户的调节方向;而PreTranslateMessage的方法只能检测鼠标点击,需要自己判断按钮方向,适合简单场景。
内容的提问来源于stack exchange,提问作者ark1974
相关产品推荐
相关产品推荐

