MFC中如何为继承CButton的多按钮对象实现专属事件处理?
问题背景
在MFC开发中,普通CButton控件可通过OnBnClickedButtonXXX()为单个按钮编写专属逻辑。但由于需要OnLButtonDown和OnLButtonUp的功能,我继承CButton实现了CButtonHold类,现在希望给多个不同用途的该类按钮实现类似OnLButtonDownXXX()的专属按下、松开处理逻辑(例如控制不同步进电机)。
目前通过全局线程标志、给CButtonHold实例传入标识参数的方式实现,代码可运行但不够优雅,想寻求更合理的方案。
现有实现结构
全局线程标志初始化
bool thrdOne = true, thrdTwo = true; CWinThread* motorThread;
CButtonHold类定义
class CButtonHold : public CButton { public: CButtonHold(int val, bool thrd); bool thrd; int val; ... };
对话框构造函数初始化实例
CButtonHold btnX(1, thrdOne), btnY(2, thrdTwo);
对话框类中的线程相关函数
static UINT btnStartThread(LPVOID arg) { CButtonHold* button = (CButtonHold*)arg; while (button->btnThread) { if(button->val == 1) //执行对应操作.... else //执行另一操作.... } AfxEndThread(0, TRUE); return 0; } void callThread(CButtonHold* button) { if (button->btnThread) motorThread = AfxBeginThread(btnStartThread, button, THREAD_PRIORITY_NORMAL, 0, 0, NULL); } void stopThread(CButtonHold* button) { button->btnThread = false; }
优化方案
方案1:使用回调函数(推荐)
给CButtonHold类添加回调函数成员,让每个按钮实例绑定专属的按下、松开处理逻辑,线程直接调用回调,无需通过val判断分支:
- 修改
CButtonHold类定义:
#include <functional> class CButtonHold : public CButton { public: using HoldAction = std::function<void()>; using StopAction = std::function<void()>; CButtonHold() : m_isRunning(false), m_thread(nullptr) {} void SetHoldActions(HoldAction holdFunc, StopAction stopFunc) { m_holdFunc = std::move(holdFunc); m_stopFunc = std::move(stopFunc); } protected: afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnLButtonUp(UINT nFlags, CPoint point); DECLARE_MESSAGE_MAP() private: HoldAction m_holdFunc; StopAction m_stopFunc; bool m_isRunning; CWinThread* m_thread; static UINT ThreadProc(LPVOID arg); };
- 实现消息映射与线程逻辑:
BEGIN_MESSAGE_MAP(CButtonHold, CButton) ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() END_MESSAGE_MAP() void CButtonHold::OnLButtonDown(UINT nFlags, CPoint point) { CButton::OnLButtonDown(nFlags, point); if (!m_isRunning) { m_isRunning = true; m_thread = AfxBeginThread(ThreadProc, this, THREAD_PRIORITY_NORMAL, 0, 0, NULL); } } void CButtonHold::OnLButtonUp(UINT nFlags, CPoint point) { CButton::OnLButtonUp(nFlags, point); if (m_isRunning) { m_isRunning = false; if (m_stopFunc) m_stopFunc(); // 可选:等待线程安全结束 WaitForSingleObject(m_thread->m_hThread, INFINITE); m_thread = nullptr; } } UINT CButtonHold::ThreadProc(LPVOID arg) { CButtonHold* btn = static_cast<CButtonHold*>(arg); while (btn->m_isRunning) { if (btn->m_holdFunc) btn->m_holdFunc(); // 添加延时避免CPU占用过高 Sleep(10); } return 0; }
- 对话框中初始化按钮并绑定专属逻辑:
// 对话框类成员 CButtonHold m_btnX; CButtonHold m_btnY; // 在OnInitDialog中初始化 BOOL CYourDialog::OnInitDialog() { CDialogEx::OnInitDialog(); m_btnX.Create(_T("X轴"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(10,10,100,30), this, IDC_BTN_X); m_btnX.SetHoldActions( [](){ /* X轴步进电机运行逻辑 */ }, [](){ /* X轴停止逻辑 */ } ); m_btnY.Create(_T("Y轴"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(110,10,200,30), this, IDC_BTN_Y); m_btnY.SetHoldActions( [](){ /* Y轴步进电机运行逻辑 */ }, [](){ /* Y轴停止逻辑 */ } ); return TRUE; }
方案2:派生专属子类
如果各按钮逻辑差异极大,可针对每个用途派生CButtonHold的子类,各自实现专属线程逻辑:
class CXAxisButton : public CButtonHold { private: static UINT ThreadProc(LPVOID arg) { CXAxisButton* btn = static_cast<CXAxisButton*>(arg); while (btn->m_isRunning) { // X轴专属运行逻辑 Sleep(10); } return 0; } protected: void OnLButtonDown(UINT nFlags, CPoint point) override { CButtonHold::OnLButtonDown(nFlags, point); // 可选:添加X轴专属按下前置逻辑 } }; // 同理实现CYAxisButton,对话框中直接使用子类实例
方案3:自定义消息+父窗口处理
让CButtonHold在按下/松开时向父窗口发送自定义消息,对话框中为每个按钮ID编写专属处理函数:
- 定义自定义消息:
#define WM_BUTTON_HOLD_DOWN (WM_USER + 100) #define WM_BUTTON_HOLD_UP (WM_USER + 101)
- 修改
CButtonHold的消息处理:
void CButtonHold::OnLButtonDown(UINT nFlags, CPoint point) { CButton::OnLButtonDown(nFlags, point); GetParent()->SendMessage(WM_BUTTON_HOLD_DOWN, GetDlgCtrlID(), (LPARAM)this); } void CButtonHold::OnLButtonUp(UINT nFlags, CPoint point) { CButton::OnLButtonUp(nFlags, point); GetParent()->SendMessage(WM_BUTTON_HOLD_UP, GetDlgCtrlID(), (LPARAM)this); }
- 对话框中添加消息映射与处理:
BEGIN_MESSAGE_MAP(CYourDialog, CDialogEx) ON_MESSAGE(WM_BUTTON_HOLD_DOWN, &CYourDialog::OnButtonHoldDown) ON_MESSAGE(WM_BUTTON_HOLD_UP, &CYourDialog::OnButtonHoldUp) END_MESSAGE_MAP() LRESULT CYourDialog::OnButtonHoldDown(WPARAM wParam, LPARAM lParam) { UINT btnID = static_cast<UINT>(wParam); if (btnID == IDC_BTN_X) { // 启动X轴线程逻辑 } else if (btnID == IDC_BTN_Y) { // 启动Y轴线程逻辑 } return 0; } LRESULT CYourDialog::OnButtonHoldUp(WPARAM wParam, LPARAM lParam) { UINT btnID = static_cast<UINT>(wParam); if (btnID == IDC_BTN_X) { // 停止X轴线程逻辑 } else if (btnID == IDC_BTN_Y) { // 停止Y轴线程逻辑 } return 0; }
内容的提问来源于stack exchange,提问作者user22946389
相关产品推荐
相关产品推荐

