MFC 实现按钮按下持续执行

发布于:2024-11-29 ⋅ 阅读:(13) ⋅ 点赞:(0)

在MFC开发中,有时需要实现按钮按下时持续执行某项操作,并在松开按钮时停止该操作。比如在运动控制场景中,JOG+JOG- 按钮通常需要这样的功能。本文将介绍两种实现这种需求的方法:通过 PreTranslateMessage 函数在消息调度前进行筛选,或者通过重载 CButton 类来添加自定义的鼠标事件。

方案一:使用 PreTranslateMessage 进行消息筛选

实现思路

PreTranslateMessage 是 MFC 提供的一个用于在窗口消息调度之前对消息进行预处理的函数。我们可以利用这个函数来检测特定按钮的鼠标按下和抬起事件,从而控制操作的开始和停止。

实现步骤

  1. 重载 PreTranslateMessage 函数

    在对话框类中重载 PreTranslateMessage 函数,捕获鼠标按下和松开的消息。

    BOOL CAxisSettingsDlg::PreTranslateMessage(MSG* pMsg)
    {
        if (pMsg->message == WM_LBUTTONDOWN || pMsg->message == WM_LBUTTONUP)
        {
            CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
            if (pWnd)
            {
                int nCtrlID = pWnd->GetDlgCtrlID();
                if (nCtrlID == IDC_BUTTON_AXIS_TEST_JOG_ADD || nCtrlID == IDC_BUTTON_AXIS_TEST_JOG_SUB)
                {
                    if (pMsg->message == WM_LBUTTONDOWN)
                    {
                        handleAxisOperation(nCtrlID == IDC_BUTTON_AXIS_TEST_JOG_ADD ? AxisOperationType::JOG_ADD : AxisOperationType::JOG_SUB, true);
                        StartJogOperation(nCtrlID == IDC_BUTTON_AXIS_TEST_JOG_ADD ? AxisOperationType::JOG_ADD : AxisOperationType::JOG_SUB);
                    }
                    else if (pMsg->message == WM_LBUTTONUP)
                    {
                        handleAxisOperation(nCtrlID == IDC_BUTTON_AXIS_TEST_JOG_ADD ? AxisOperationType::JOG_ADD : AxisOperationType::JOG_SUB, false);
                        StopJogOperation();
                    }
                    return TRUE; // 消息已处理
                }
            }
        }
        return CDialogEx::PreTranslateMessage(pMsg);
    }
    
  2. 解释实现细节

    • PreTranslateMessage 中,我们对 WM_LBUTTONDOWNWM_LBUTTONUP 消息进行筛选。
    • 使用 FromHandle 获取对应的窗口对象,通过控件 ID 判断是否是我们关注的按钮。
    • JOG+JOG- 按钮,分别在按下和松开时调用 handleAxisOperation 函数开始或停止操作。
  3. 使用定时器实现持续执行

    • WM_LBUTTONDOWN 消息处理中,启动一个定时器以持续执行操作。
    • WM_LBUTTONUP 消息处理中,停止定时器。
    void CAxisSettingsDlg::StartJogOperation(AxisOperationType opType)
    {
        m_nJogOperationType = opType;
        SetTimer(TIMER_JOG_OPERATION, 100, nullptr); // 每隔100毫秒执行一次
    }
    
    void CAxisSettingsDlg::StopJogOperation()
    {
        KillTimer(TIMER_JOG_OPERATION);
    }
    
    void CAxisSettingsDlg::OnTimer(UINT_PTR nIDEvent)
    {
        if (nIDEvent == TIMER_JOG_OPERATION)
        {
            handleAxisOperation(m_nJogOperationType, true);
        }
        CDialogEx::OnTimer(nIDEvent);
    }
    

优缺点

  • 优点:实现简单,直接对对话框中的消息进行统一处理,减少对控件的直接操作。
  • 缺点:需要在 PreTranslateMessage 中判断多个控件的 ID,当控件较多时,代码复杂度增加。

PreTranslateMessage 的其他应用和注意事项

PreTranslateMessage 函数在 MFC 中使用非常广泛,除了用于处理鼠标按下和松开的事件外,还可以用于:

  1. 全局快捷键处理:可以在 PreTranslateMessage 中捕获键盘消息,实现对全局快捷键的处理。
  2. 输入验证:在将消息传递给控件之前,可以对用户的输入进行验证,例如限制输入字符的范围。
  3. 防止某些消息传递:通过在 PreTranslateMessage 中直接返回 TRUE,可以阻止某些消息传递给控件。

需要注意的是,如果在 PreTranslateMessage 中处理的逻辑过多,可能会影响界面的响应速度,特别是在需要频繁判断控件 ID 的情况下。建议仅在必要时使用该函数进行消息处理。

方案二:重载 CButton 类以处理鼠标事件

实现思路

另一种方法是创建一个继承自 CButton 的新类,在其中重载 WM_LBUTTONDOWNWM_LBUTTONUP 消息。这样,我们可以直接在按钮类中实现按下和抬起的逻辑,代码更加清晰,易于维护。

实现步骤

  1. 创建新的按钮类 CJOGControlButton

    创建一个继承自 CButton 的类,重载 OnLButtonDownOnLButtonUp 事件。

    class CJOGControlButton : public CButton
    {
    public:
        DECLARE_MESSAGE_MAP()
    
        afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
        afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
        afx_msg void OnTimer(UINT_PTR nIDEvent);
    
        // 添加一个回调函数,用于执行特定操作
        void SetCallback(std::function<void(bool)> callback) { m_callback = callback; }
        void StartTimer() { SetTimer(1, 100, nullptr); } // 启动定时器持续执行
        void StopTimer() { KillTimer(1); } // 停止定时器
    
    private:
        std::function<void(bool)> m_callback;
    };
    
    BEGIN_MESSAGE_MAP(CJOGControlButton, CButton)
        ON_WM_LBUTTONDOWN()
        ON_WM_LBUTTONUP()
        ON_WM_TIMER()
    END_MESSAGE_MAP()
    
    void CJOGControlButton::OnLButtonDown(UINT nFlags, CPoint point)
    {
        if (m_callback)
            m_callback(true); // 开始操作
        StartTimer(); // 启动定时器持续执行
        CButton::OnLButtonDown(nFlags, point);
    }
    
    void CJOGControlButton::OnLButtonUp(UINT nFlags, CPoint point)
    {
        if (m_callback)
            m_callback(false); // 停止操作
        StopTimer(); // 停止定时器
        CButton::OnLButtonUp(nFlags, point);
    }
    
    void CJOGControlButton::OnTimer(UINT_PTR nIDEvent)
    {
        if (m_callback)
            m_callback(true); // 持续操作
        CButton::OnTimer(nIDEvent);
    }
    
  2. 在对话框中使用自定义按钮

    • 将按钮控件替换为自定义的 CJOGControlButton 类型,并在对话框中进行绑定。
    DDX_Control(pDX, IDC_BUTTON_AXIS_TEST_JOG_ADD, m_btnJogAdd);
    DDX_Control(pDX, IDC_BUTTON_AXIS_TEST_JOG_SUB, m_btnJogSub);
    
    // 设置回调函数
    m_btnJogAdd.SetCallback([this](bool bPressed) {
        handleAxisOperation(AxisOperationType::JOG_ADD, bPressed);
    });
    m_btnJogSub.SetCallback([this](bool bPressed) {
        handleAxisOperation(AxisOperationType::JOG_SUB, bPressed);
    });
    

优缺点

  • 优点:代码更加模块化,将按钮的行为封装在按钮类中,逻辑清晰,易于复用。可以通过设置不同的回调函数,使按钮的行为更加灵活和通用。
  • 缺点:需要创建自定义控件类,稍微增加了类的数量,但维护起来更方便。

总结

这两种方法各有优劣,开发者可以根据具体项目需求选择合适的方案。

  • 如果项目中需要对多个按钮统一处理,并且对代码复杂度的要求不高,可以选择使用 PreTranslateMessage 进行消息筛选。
  • 如果项目中有多个需要处理类似行为的按钮,建议通过重载 CButton 类,将行为逻辑封装在控件内部,增强代码的模块化和复用性。