You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何捕获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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 12:37:42