基于mfc的运动版卡切换的经验

一:项目框架:

MachineMotion/

├── 头文件/

│   ├── stdafx.h

│   ├── targetver.h

│   ├── Resource.h

│   ├── MachineMotion.h

│   ├── MachineMotionDlg.h

│   ├── MotionControllerBase.h

│   ├── MotionControllerFactory.h

│   ├── GTSMotionController.h

│   └── YKMotionController.h

├── 源文件/

│   ├── stdafx.cpp

│   ├── MachineMotion.cpp

│   ├── MachineMotionDlg.cpp

│   ├── MotionControllerFactory.cpp

│   ├── GTSMotionController.cpp

│   └── YKMotionController.cpp

├── 资源文件/

│   ├── MachineMotion.rc

│   └── res/

└── 外部依赖项/

└── (运动控制卡SDK库文件)

二:基础知识

多态的意义:

灵活性:同一套代码可处理不同类型的对象,无需为每个类型单独编写逻辑。

可扩展性:新增派生类(如 Bird)时,只需重写接口,无需修改上层调用代码(如 LetAnimalSpeak)。

降低耦合:上层代码仅依赖基类接口,不依赖具体实现,便于维护和迭代。

必须通过基类的指针或者引用调用虚函数

被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

virtual ~CMotionControllerBase() {} 的核心作用是保证派生类对象通过基类指针被删除时,析构函数能正确链式调用(先派生类后基类),这是面向对象中使用多态(基类指针管理派生类对象)时必须遵守的规则,尤其对于需要管理资源的类(如硬件控制器、文件操作类等)至关重要。

= 0表示该函数为纯虚函数。它的核心作用是:将函数声明为 “必须由派生类实现的接口”,同时使当前类成为 “抽象类”,无法直接实例化。(包含纯虚函数的类称为 “抽象类”,抽象类不能直接创建对象)

继承的关键细节:

访问控制:父类成员在子类中的访问权限由继承方式(public/protected/private)决定,通常用 public 继承(保持接口开放性)。

构造函数:子类构造函数必须先调用父类的构造函数(初始化继承的成员),通过初始化列表实现(如 Dog(...) : Animal(name), ...)。

重写与多态:子类可重写父类的虚函数(virtual),结合基类指针 / 引用实现多态(见之前的 “多态” 解释)。

封装的核心作用:

数据安全:通过私有成员(private)阻止外部直接修改数据,仅允许通过验证逻辑的公共方法操作,避免数据被随意篡改(如年龄不会出现负数)。

降低耦合:外部只需关注类的接口(方法),无需了解内部实现(如 SetAge 如何验证年龄),即使内部实现修改(如调整年龄范围),外部调用代码也无需变动。

代码复用与维护:将数据和操作捆绑成独立的类,便于重复使用,且修改时只需关注类内部,不影响其他代码。

virtual 函数(虚函数)的核心作用是实现多态性(Polymorphism),即允许派生类重写基类的函数,并且在通过基类指针或引用调用该函数时,自动执行派生类的重写版本

统一接口,规范行为

基类中声明的纯虚函数定义了所有运动控制器必须支持的核心功能(初始化、相对运动、急停等)。无论后续接入哪种具体型号的运动控制器(如步进电机控制器、伺服电机控制器,甚至不同厂商的设备),都必须实现这些接口,确保上层代码调用时 “用法一致”,无需关心底层硬件差异。

            控制卡类型(▼)     初始化   急停                   

轴控制: 

使能   失能                    

相对运动:                      

绝对运动:                      

JOG运动:                        

状态: 未初始化                   

三:开始搭建

3.1:第一步,运动基类搭建

运动控制基类(抽象类)   抽象基类 / 接口,定义了所有运动控制卡必须实现的核心功能

定义 初始化(Initialize),关闭(close),相对运动(RelativeMove),绝对运动(AbsoluteMove),jog运动(jogmove),停止运动(stopmove),急停(emergencystop),获取最后错误(GetLastError)

获取错误字符(GetErrorString

class  CMotionControllerBase

{

public:

Virtual ~CMotionControllerBase();{}

//初始化

Virtual BOOL Initialize() = 0;

Virtual BOOL close() = 0;

//运动

Virtual BOOL RelativeMove(double distance ) = 0;

Virtual BOOL AbsoluteMove(double position) = 0;

Virtual BOOL jogmove(BOOL positiveDirection) = 0;//

Virtual BOOL stopmove() = 0;

Virtual BOOL emergencystop() = 0;

//错误

Virtual DWORD GetLastError() = 0;

Virtual CString GetErrorString(DWORD errorCode) = 0;将错误码(DWORD 类型)转换为人类可读的错误描述文

}

3.2:第二步,建立与运动卡工厂类(本文主要用固高和研控)

enum(枚举)并不是函数,而是一种用户自定义的数据类型,用于定义一组具有离散值的常量(枚举常量),目的是提高代码的可读性和可维护性。

作用

替代魔法数字:用有意义的名称(如 STATUS_MOVING)代替直接使用数字(如 1),使代码更易读。

限制取值范围:枚举变量只能存储枚举中定义的常量,避免无效值(如状态只能是停止 / 移动 / 错误等,不能是其他数字)。

static 修饰的函数称为静态成员函数,属于类本身而非类的实例(对象),其使用方式和作用与普通成员函数有显著区别。成员函数的核心价值是实现 “与类相关但独立于对象” 的功能,它不依赖具体对象的状态,可直接通过类名调用,

静态成员函数在类内声明时需加 static 关键字,类外定义时无需重复 static(但需加类名限定)。

核心特性

属于类,而非对象:静态函数由所有类的实例共享,不依赖于具体对象的存在,无需创建对象即可调用。

无 this 指针:静态函数内部不能访问类的非静态成员(非静态成员依赖于 this 指针指向的对象),但可以访问静态成员(静态成员属于类本身)。

调用方式:通过 类名::函数名 直接调用,或通过对象调用(不推荐,易混淆)。

 注意事项

静态函数不能被 virtual 修饰(静态函数属于类,多态依赖于对象的 this 指针,两者冲突)。

静态函数不能访问非静态成员(非静态成员需要 this 指针指向具体对象,而静态函数无 this 指针)。

静态函数的作用域受类的访问控制(public/private/protected)限制:private 静态函数只能在类内调用。

Create该静态方法的作用是根据输入的 type(控制卡类型),创建对应的派生类对象,并返回基类指针。

通过基类指针(CMotionControllerBase*)返回具体对象,上层代码只需通过基类接口操作(如调用 Initialize、RelativeMove),无需关心对象的具体类型(固高 / 研控),实现 “接口与实现分离”。

Destroy 方法:统一销毁对象,避免资源泄漏

作用:提供统一的对象销毁接口,确保删除控制卡对象时正确释放资源(结合基类的虚析构函数,会先调用派生类析构函数,再调用基类析构函数)。

优势:避免上层代码直接调用 delete,统一管理对象生命周期,降低因手动释放导致的错误风险(如重复释放、漏释放)。

if (pController){delete pController;}

避免野指针错误:如果 pController 是 NULL,直接 delete 虽然安全,但显式检查更清晰,能体现代码的严谨性。

确保资源释放:delete 会触发对象的析构函数,对于运动控制器对象,析构函数可能包含关闭设备连接、释放缓冲区等操作,必须执行以避免资源泄漏。

规范内存管理:动态分配的对象(new 创建)必须通过 delete 释放,否则会导致内存泄漏(程序运行时间越长,占用内存越多,最终可能崩溃)。

建立运动控制卡类:

降低耦合:上层代码无需包含具体控制卡的头文件(CGTSController.h、CYKController.h),只需依赖基类 CMotionControllerBase 和控制卡工厂类,减少代码间的依赖关系。

便于扩展:新增控制卡类型时,只需:① 在 MotionControllerType 中添加新枚举;② 实现新的派生类;③ 在 Create 中添加 case 分支。整个过程无需修改上层使用逻辑,符合 “开闭原则”(对扩展开放,对修改关闭)。

CMotionControllerFactory 与 CMotionControllerBase 是基于 “接口 - 实现 - 创建” 的协作关系:

运动控制卡类(CMotionControllerFactory.h)负责根据 “控制卡类型”(如固高、研控)创建 CMotionControllerBase 的具体派生类对象

Enum CMotionControllertype

{

      MC_GTS = 0,    // 固高

MC_YK = 1      // 研控

}

Class CMotionControllerFactory

{   

public:

//静态 实现类型切换的方法(根据类型创建对应的控制卡对象)

 Static CMotionControllerBase*Create(CMotionControllertype  type)

 // 静态函数:销毁控制卡对象(统一释放资源)

Static  viod  Destroy(  CMotionControllertype*pController  )

{

if (pController)

{

delete pController;

}

}

};

3.3:第三步,建立工厂类的实现文件

头文件包含:

#include "GTSMotionController.h" 和 #include "YKMotionController.h" 引入了具体控制卡类的定义(CGTSMotionController 对应固高控制卡,CYKMotionController 对应研控控制卡),这两个类均派生自基类 CMotionControllerBase,实现了基类中的纯虚函数(如 Initialize、RelativeMove 等)。

switch 分支判断:根据输入的 type(枚举值 MC_GTS 或 MC_YK),通过 new 关键字创建对应的具体派生类对象(CGTSMotionController 或 CYKMotionController)。

返回基类指针:虽然创建的是派生类对象,但返回值类型为基类指针(CMotionControllerBase*),这是多态的关键—— 上层代码只需通过基类指针调用接口,无需关心具体是哪种控制卡,实现了 “接口与实现的分离”。

默认处理:若输入的 type 是未定义的枚举值(如负数或大于 MC_YK 的值),返回 NULL,避免创建无效对象

3. 工厂模式的核心作用体现

隐藏对象创建细节:上层代码无需知道 CGTSMotionController 或 CYKMotionController 的具体实现,只需调用 CreateController(MC_GTS) 即可获取固高控制卡对象,降低了代码耦合度。

便于扩展新类型:若需支持新的控制卡(如雷赛),只需:

新增枚举值 MC_LS;

实现派生类 CLSMotionController;

在 switch 中添加 case MC_LS: return new CLSMotionController();。整个过程无需修改上层调用逻辑。

创建运动控制卡类的源文件(CMotionControllerFactory.cpp)

// MotionControllerFactory.cpp

#include “stdafx.h”

#include "MotionControllerFactory.h"

#include "GTSMotionController.h"

#include "YKMotionController.h"

CMotionControllerBase* CMotionControllerFactory::CreateController(MotionControllerType type)

{

    switch (type)

    {

    case MC_GTS:

        return new CGTSMotionController();

    case MC_YK:

        return new CYKMotionController();

    default:

        return NULL;

    }

}

3.4:第四步,创建派生类,运动控制

创建派生类(CGTSMotionController)

类的继承关系与定位

CGTSMotionController : public CMotionControllerBase 表明它是 CMotionControllerBase 的派生类,需实现基类中所有的纯虚函数(如 Initialize、RelativeMove 等),从而成为可实例化的 “具体控制卡类”。(初始化结束,我怎么知道有没有初始化结束,需要一个句柄检测)

m_bInitialized:记录控制卡是否完成初始化,避免重复初始化或操作未初始化的设备。

m_velocity:存储运动时的速度参数,用于控制固高卡的运动速率

构造函数 CGTSMotionController():会初始化 m_bInitialized = FALSE、m_velocity = 0.0 等默认状态,为控制卡操作做准备。

虚析构函数 virtual ~CGTSMotionController():需在实现中关闭固高卡的硬件连接、释放动态资源(如缓冲区),结合基类的虚析构函数确保资源完全释放。

CGTSMotionController 的核心作用是将固高运动控制卡的硬件细节封装起来,上层代码只需通过基类接口(CMotionControllerBase*)调用,无需关心 “固高卡如何初始化、如何发送移动指令” 等底层细节。通过继承基类并实现所有纯虚函数,将固高控制卡的硬件操作转化为统一的接口调用,保证了功能的完整性

Class CGTSMotionController :public CMotionControllerBase 

{

Private:

BOOL m_blnitialized;

Double m_velocity;

Public:

CGTSMotionController();

Virtual ~CGTSMotionController();

virtual BOOL Initialize();

virtual BOOL Close();

virtual BOOL RelativeMove(double distance);

virtual BOOL AbsoluteMove(double position);

virtual BOOL JogMove(BOOL positiveDirection);

virtual BOOL StopMove();

virtual BOOL EmergencyStop();

virtual DWORD GetLastError();

virtual CString GetErrorString(DWORD errorCode);

}

3.5:第五步,创建派生类的具体实现

有了固高派生类的具体声明,现在需要他的具体实现

构造函数通过初始化列表高效初始化成员变量(m_bInitialized 初始为 FALSE,表示设备未就绪)。

析构函数调用 Close(),确保对象销毁时自动执行关闭操作(如急停、标记未初始化),避免资源泄漏。

运动控制功能:核心操作的模拟实现

所有运动控制函数都先检查 m_bInitialized(确保设备已初始化),再模拟具体操作(实际场景中会调用固高 SDK 的对应接口)。

//初始化派生类自己的定义,为私有变量

CGTSMotionController::CGTSMotionController()

: m_bInitialized(FALSE)//初始化为FALSE,设备未启动,

, m_velocity(5.0)  // 默认速度5mm/s

{

}

CGTSMotionController::~CGTSMotionController()

{

Close();

}

BOOL  CGTSMotionController::  Initialize()

{

m_bInitialized = true;

Return true

}

BOOL  CGTSMotionController::  Close()

{

If(m_bInitialized)

{

m_bInitialized = flase;

}

Return true;

}

BOOL  CGTSMotionController::  RelativeMove(double distance)

{

if (!m_bInitialized) return FALSE;

CString msg;

msg.Format(_T("固高卡相对运动: %.2f mm"), distance);

return TRUE;  // 模拟操作成功

}

BOOL  CGTSMotionController  AbsoluteMove(double position);

{

if (!m_bInitialized) return FALSE;

CString msg;

msg.Format(_T("固高卡绝对运动: %.2f mm"), position);

return TRUE;  // 模拟操作成功

}

BOOL CGTSMotionController::JogMove(BOOL positiveDirection)

{

if (!m_bInitialized) return FALSE;

// JOG运动

CString msg;

msg.Format(_T("固高卡%sJOG运动"), positiveDirection ? _T("正转") : _T("反转"));

return TRUE;

}

BOOL CGTSMotionController::StopMove()

{

if (!m_bInitialized) return FALSE;

// 停止

return TRUE;

}

BOOL CGTSMotionController::EmergencyStop()

{

return TRUE;// 急停

}

DWORD CGTSMotionController::GetLastError()

{

return 0; // 无错误

}

CString CGTSMotionController::GetErrorString(DWORD errorCode)

{

return _T("固高卡操作成功");

}

3.6:由于,另一个控制卡,研控同固高的方法一样!就不介绍了

研控派生类的实现

class CYKMotionController : public CMotionControllerBase

{

private:

BOOL m_bInitialized;

double m_velocity;

public:

CYKMotionController();

virtual ~CYKMotionController();

virtual BOOL Initialize();

virtual BOOL Close();

virtual BOOL RelativeMove(double distance);

virtual BOOL AbsoluteMove(double position);

virtual BOOL JogMove(BOOL positiveDirection);

virtual BOOL StopMove();

virtual BOOL EmergencyStop();

virtual DWORD GetLastError();

virtual CString GetErrorString(DWORD errorCode);

};

研控的具体定义实现

CYKMotionController::CYKMotionController()

: m_bInitialized(FALSE)

, m_velocity(5.0)  //

{

}

CYKMotionController::~CYKMotionController()

{

Close();

}

BOOL CYKMotionController::Initialize()

{

// 研控卡初始化

m_bInitialized = TRUE;

return TRUE;

}

BOOL CYKMotionController::Close()

{

if (m_bInitialized)

{

EmergencyStop();

m_bInitialized = FALSE;

}

return TRUE;

}

BOOL CYKMotionController::RelativeMove(double distance)

{

if (!m_bInitialized) return FALSE;

// 相对运动

CString msg;

msg.Format(_T("研控卡相对运动: %.2f mm"), distance);

return TRUE;

}

BOOL CYKMotionController::AbsoluteMove(double position)

{

if (!m_bInitialized) return FALSE;

// 绝对运动

CString msg;

msg.Format(_T("研控卡绝对运动到: %.2f mm"), position);

//AfxMessageBox(msg);

return TRUE;

}

BOOL CYKMotionController::JogMove(BOOL positiveDirection)

{

if (!m_bInitialized) return FALSE;

// JOG运动

CString msg;

msg.Format(_T("研控卡%sJOG运动"), positiveDirection ? _T("正转") : _T("反转"));

return TRUE;

}

BOOL CYKMotionController::StopMove()

{

if (!m_bInitialized) return FALSE;

// 停止

return TRUE;

}

BOOL CYKMotionController::EmergencyStop()

{

// 急停

return TRUE;

}

DWORD CYKMotionController::GetLastError()

{

return 0; // 模拟无错误

}

CString CYKMotionController::GetErrorString(DWORD errorCode)

{

return _T("研控卡操作成功");

}

3.7:目前已经完成了运动控制卡基类和派生类的创建,现在需要实现对话框类的实现

CMachineMotionDlg 是 MFC 应用程序的主对话框类负责运动控制器的UI 交互与逻辑调度,是用户操作运动控制卡(如固高、研控)的可视化界面入口。

类的基本定位

class CMachineMotionDlg : public CDialogEx 表明这是一个基于 MFC 对话框的窗口类,继承自 CDialogEx(MFC 扩展对话框类),用于实现图形界面交互。其核心功能是:

提供 UI 控件(如按钮、输入框)供用户操作运动控制器;

通过工厂类 CMotionControllerFactory 管理运动控制器对象;

调用控制器接口(CMotionControllerBase)执行具体运动逻辑(初始化、移动、停止等)。

完成对数据的获取以及调用

m_pMotionController:通过工厂类创建的具体控制卡对(如 CGTSMotionController),存储基类指针以支持多态调用。

m_currentCardType:记录用户选择的控制卡类型(从下拉框 m_cmbCardType 中获取),用于工厂类创建对应对象。

完成数据交互以及初始化状态

DoDataExchange:MFC 框架用于绑定控件 ID 与成员变量(如将 IDC_CMB_CARDTYPE 绑定到 m_cmbCardType),通常在类向导中自动生成。

OnInitDialog:对话框创建后执行的初始化逻辑(如初始化下拉框选项、禁用未初始化状态下的运动按

初始化的一个安全措施以及使用

InitializeControls:初始化 UI 控件(如向下拉框 m_cmbCardType 中添加 “固高”“研控” 选项)。

InitializeMotionController:通过工厂类 CMotionControllerFactory 创建对应类型的控制器对象(m_pMotionController),并调用 Initialize 接口。

EnableMotionControls:根据控制器是否初始化,启用或禁用运动按钮(如未初始化时,m_btnRelMove 为灰色不可点击)。

在 MFC 中,“用于操作 UI 元素” 指的是通过代码控制对话框(或窗口)上的可视化组件(如按钮、输入框、文本标签等),实现与用户的交互(如显示信息、获取输入、修改状态等)。这些 UI 元素在代码中通过 “控件变量” 或 “控件 ID” 进行操作,是连接用户操作与程序逻辑的桥梁。

操作 UI 元素” 的本质是实现用户与程序的交互闭环:

用户输入:通过 UI 元素(如下拉框选控制卡、输入框填距离)向程序传递指令。

程序反馈:通过 UI 元素(如状态文本显示结果、按钮状态变化)向用户展示程序执行情况。

创建对话框类(CmachinemotionDlg )

Class CmachinemotionDlg : public CDialogEx

{

 Public:

CmachinemotionDlg(CWnd* pParent = nullptr)// 标准构造函数

virtual ~CmachinemotionDlg()

// 对话框数据

#ifdef AFX_DESIGN_TIME

enum { IDD = IDD_MACHINEMOTION_DIALOG };

#endif

 protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持(数据交换)

protected:(自动生成的,需要知道用法)

HICON m_hIcon;

// 生成的消息映射函数

virtual BOOL OnInitDialog();

afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

DECLARE_MESSAGE_MAP()

private:

// 运动控制器

CMotionControllerBase* m_pMotionController;(储存基类指针)

MotionControllerType m_currentCardType;(类型的选择)

// 控件变量:与对话框资源中的控件一一对应,用于操作UI元素

CComboBox m_cmbCardType; // 下拉框:选择控制卡类型(固高/研控)

CButton m_btnInitialize; // 按钮:初始化控制器

CButton m_btnEmergencyStop; // 按钮:急停

CStatic m_staticStatus; // 静态文本:显示控制器状态(如“未初始化”“运行中”)

// 运动控制相关控件

CEdit m_editRelDistance; // 输入框:相对移动距离

CButton m_btnRelMove; // 按钮:执行相对移动

CEdit m_editAbsPosition; // 输入框:绝对移动目标位置

CButton m_btnAbsMove; // 按钮:执行绝对移动

CButton m_btnJogPos; // 按钮:正向点动

CButton m_btnJogNeg; // 按钮:反向点动

CButton m_btnStop; // 按钮:停止运动

//自定义从初始化函数

void InitializeControls();//初始化控件

BOOL InitializeMotionController();//初始化控制器

void EnableMotionControls(BOOL enable);//启用和禁用按钮

//按钮的响应

public:

afx_msg void OnBnClickedBtnInitialize();//初始化

afx_msg void OnBnClickedBtnEmergencyStop();//急停

afx_msg void OnBnClickedBtnRelMove();//相对运动

afx_msg void OnBnClickedBtnAbsMove();//绝对运动

afx_msg void OnBnClickedBtnJogPos();//jog运动正向

afx_msg void OnBnClickedBtnJogNeg();//iog反向

afx_msg void OnBnClickedBtnStop();//停止

}

3.8:完成框架类的编写后,现在需要完成框架类的具体实现

这个框架源文件的实现是整个运动控制系统的UI 交互核心,负责将用户的界面操作(如点击按钮、输入参数)转化为对运动控制器的实际调用,同时反馈系统状态。

IMPLEMENT_DYNAMIC 是 MFC 框架中用于启用类的运行时类型识别和动态创建功能的关键宏,它与 DECLARE_DYNAMIC 配合,使得 MFC 类在运行时可以被准确识别类型,支持安全的类型转换,是 MFC 框架实现多态和动态对象管理的基础机制。对于你的对话框类 CMachineMotionDlg 来说,这是遵循 MFC 类设计规范的必要步骤,确保其能正确融入 MFC 框架的类型系统中。

第一步1. 构造与析构函数:初始化与资源释放

构造函数初始化控制器指针为 NULL,默认控制卡类型为固高(MC_GTS)。

析构函数检查并释放 m_pMotionController(通过工厂的 DestroyController 方法),避免内存泄漏。

第二步2. 数据交换与消息映射:UI 绑定与事件关联

(1)DoDataExchange:绑定控件与变量

消息映射是 MFC 的核心机制,用于将用户操作(如点击按钮)与对应的处理函数关联。例如,点击 ID 为 IDC_BTN_INITIALIZE 的按钮时,自动调用 OnBnClickedBtnInitialize 函数。

第三步3. 对话框初始化:UI 布局与状态设置

OnInitDialog 是对话框创建后第一个执行的初始化函数,设置窗口标题并调用 InitializeControls 初始化控件。

InitializeControls 完成控件的初始状态配置:下拉框添加选项、输入框设默认值、禁用未初始化状态下的运动按钮(避免误操作)。

ON_BN_CLICKED 是 MFC 中用于 ** 绑定 “按钮点击事件” 与 “处理函数”** 的宏,属于消息映射机制的核心部分,用于响应用户点击按钮的操作。

注意事项

afx_msg 关键字:声明处理函数时必须添加 afx_msg,告诉 MFC 这是一个消息处理函数(编译时会被特殊处理)。

函数参数:处理函数必须是无参的 void 函数,不能有返回值或参数。

ID 唯一性:按钮 ID 必须唯一,否则会导致消息映射混乱(一个 ID 对应多个按钮或处理函数)。

类范围:处理函数必须是当前对话框类(或其父类)的成员函数,不能是全局函数。

总结

ON_BN_CLICKED 是 MFC 中连接 “用户点击按钮” 与 “程序逻辑” 的桥梁,通过消息映射机制将按钮 ID 与处理函数绑定,使得用户操作能被程序捕获并响应。在你的运动控制程序中,所有按钮(初始化、移动、急停等)的交互逻辑都依赖该宏实现,是 UI 交互的核心机制。

第四步核心逻辑:控制器初始化与运动控制

(1)InitializeMotionController:创建并初始化控制器

用于在 “未初始化” 和 “已初始化” 状态间切换控件可用性:未初始化时禁用(防止误操作),初始化后启用(允许用户操作)。

第五步消息响应函数:处理用户交互(核心操作流程)

每个按钮的点击事件对应一个响应函数,负责将 UI 操作转化为控制器调用:

(1)初始化按钮 OnBnClickedBtnInitialize

点击 “初始化” 按钮时,调用 InitializeMotionController 完成控制器创建与初始化,成功后弹出提示。

(2)相对移动按钮 OnBnClickedBtnRelMove

(3)其他按钮(绝对移动、点动、停止、急停)

逻辑与相对移动类似,均遵循 “检查控制器状态 → 获取参数(如需) → 调用对应接口 → 更新状态” 的流程:

绝对移动:读取 m_editAbsPosition 的值,调用 AbsoluteMove。

正向 / 反向点动:调用 JogMove(TRUE) 或 JogMove(FALSE)。

停止:调用 StopMove,更新状态为 “停止”。

急停:调用 EmergencyStop,弹出提示并更新状态为 “急停”。

分层清晰:UI 层(CMachineMotionDlg)仅负责交互,不包含具体运动逻辑(逻辑由 CGTSMotionController 等类实现),符合 “单一职责原则”。

多态适配:通过基类指针 m_pMotionController 调用接口,无论用户选择固高还是研控卡,代码无需修改,实现了 “同一操作适配不同硬件”。

状态安全:通过 EnableMotionControls 严格控制控件状态,避免用户在未初始化时执行运动操作,提升安全性。

资源管理:控制器的创建 / 销毁统一通过工厂类,析构函数确保退出时释放资源,避免内存泄漏。

创建框架源文件(CMachineMotionDlg)

//第一步构造与析构函数:初始化与资源释放

IMPLEMENT_DYNAMIC(CMachineMotionDlg, CDialogEx)

CMachineMotionDlg::CMachineMotionDlg(CWnd* pParent /*=nullptr*/)

    : CDialogEx(IDD_MACHINEMOTION_DIALOG, pParent)

    , m_pMotionController(NULL)

    , m_currentCardType(MC_GTS)

{

}

//析构函数,确保资源释放

CmachinemotionDlg::~CmachinemotionDlg()

{

if (m_pMotionController)

{

CMotionControllerFactory::DestroyController(m_pMotionController);

}

}

//第二步建立ui,数据交换与消息映射

数据交换

void CMachineMotionDlg::DoDataExchange(CDataExchange* pDX)

{

//下拉框,初始化,急停,状态栏

    CDialogEx::DoDataExchange(pDX);

DDX_Control(pDX, IDC_CMB_CARDTYPE, m_cmbCardType);

DDX_Control(pDX, IDC_BTN_INITIALIZE, m_btnInitialize);

DDX_Control(pDX, IDC_BTN_EMERGENCY_STOP, m_btnEmergencyStop);

DDX_Control(pDX, IDC_STATIC_STATUS, m_staticStatus);

//输入框,按钮,

DDX_Control(pDX, IDC_EDIT_REL_DISTANCE, m_editRelDistance);

DDX_Control(pDX, IDC_EDIT_REL_MOVE, m_btnRelMove);

DDX_Control(pDX, IDC_EDIT_ABS_DISTANCE,, m_editAbsPosition);

DDX_Control(pDX, IDC_EDIT_ABS_MOVE, m_btnAbsMove);

DDX_Control(pDX, IDC_EDIT_JOG_POS, m_btnJogPos);

DDX_Control(pDX, IDC_EDIT_JOG_NEG, m_btnJogNeg);

DDX_Control(pDX, IDC_EDIT_STOP, m_btnStop);

消息映射

BEGIN_MESSAGE_MAP(CMachineMotionDlg, CDialogEx)

ON_WM_SYSCOMMAND()

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_BTN_INITIALIZE, &CmachinemotionDlg::OnBnClickedBtnInitialize)

ON_BN_CLICKED(IDC_BTN_EMERGENCY_STOP,&CmachinemotionDlg::OnBnClickedBtnInitialize)

ON_BN_CLICKED(IDC_EDIT_REL_MOVE, &CmachinemotionDlg::OnBnClickedBtnInitialize)

ON_BN_CLICKED(IDC_EDIT_ABS_MOVE, &CmachinemotionDlg::OnBnClickedBtnInitialize)

ON_BN_CLICKED(IDC_EDIT_JOG_POS, &CmachinemotionDlg::OnBnClickedBtnInitialize)

ON_BN_CLICKED(IDC_EDIT_JOG_NEG, &CmachinemotionDlg::OnBnClickedBtnInitialize)

ON_BN_CLICKED(IDC_EDIT_STOP,, &CmachinemotionDlg::OnBnClickedBtnInitialize)

END_MESSAGE_MAP()

第三步3. 对话框初始化:UI 布局与状态设置

框架布局

BOOL CMachineMotionDlg:: OnInitDialog();

{

CDialogEx::OnInitDialog();  // 调用父类初始化

 SetWindowText(_T("运动控制系统"));  // 设置窗口标题

    InitializeControls();  // 初始化控件状态

    return TRUE;

}

初始化状态设置

void CMachineMotionDlg::InitializeControls()

{

// 向下拉框添加控制卡选项(固高/研控),默认选中第一项(固高)

    m_cmbCardType.AddString(_T("固高运动控制卡"));

    m_cmbCardType.AddString(_T("研控运动控制卡"));

    m_cmbCardType.SetCurSel(0);0是固高,1是研控

 // 为输入框设置默认值(相对移动10mm,绝对位置0mm)

    m_editRelDistance.SetWindowText(_T("10.0"));

    m_editAbsPosition.SetWindowText(_T("0.0"));

    

    // 初始状态:禁用所有运动控制按钮(未初始化控制器),仅保留“初始化”可用

    EnableMotionControls(FALSE);

    m_btnEmergencyStop.EnableWindow(FALSE);

}

第四步:核心逻辑:控制器初始化与运动控制

首先,初始化状态,所有都打开后,要判断当前是否已经有选择控制器如果有就关闭掉并且销毁当前状态(防止我们误操作,选择不对的控制器),然后通过下拉框用户选择自己需要的控制卡,通过指针指向创建的控制卡类,选择我们需要的控制卡,选择完毕后调用当前控制器的初始化接口,已连接到对应控制器,如果初始化失败则会销毁当前数据,避免资源外泄,如果初始化成功,则会打开所有运动按钮,更新显示状态

BOOL CMachineMotionDlg::InitializeMotionController()

{

// 若已有控制器,先关闭并销毁

    if (m_pMotionController)

    {

        m_pMotionController->Close();

        CMotionControllerFactory::DestroyController(m_pMotionController);

        m_pMotionController = NULL;

    }

 // 获取用户选择的控制卡类型(下拉框选中项)

    int sel = m_cmbCardType.GetCurSel();

    m_currentCardType = (sel == 0) ? MC_GTS : MC_YK;  // 0:固高,1:研控

// 通过工厂类创建对应类型的控制器对象

    m_pMotionController = CMotionControllerFactory::CreateController(m_currentCardType);

    if (!m_pMotionController)  // 创建失败(如类型无效)

    {

        AfxMessageBox(_T("创建运动控制器失败!"));

        return FALSE;

    }

    

    // 调用控制器的初始化接口

    if (!m_pMotionController->Initialize())

    {

        AfxMessageBox(_T("初始化运动控制器失败!"));

        // 初始化失败则销毁对象,避免内存泄漏

        CMotionControllerFactory::DestroyController(m_pMotionController);

        m_pMotionController = NULL;

        return FALSE;

    }

    

    // 初始化成功:启用运动按钮,更新状态显示

    EnableMotionControls(TRUE);

    m_btnEmergencyStop.EnableWindow(TRUE);

    m_staticStatus.SetWindowText(_T("系统状态: 已连接"));

    

    return TRUE;

}

//所有按键恢复使能

void CMachineMotionDlg::EnableMotionControls(BOOL enable)

{

m_editRelDistance.EnableWindo(enable); // 输入框:相对移动距离

m_btnRelMove.EnableWindo(enable); // 按钮:执行相对移动

m_editAbsPosition.EnableWindo(enable); // 输入框:绝对移动目标位置

m_btnAbsMove.EnableWindo(enable); // 按钮:执行绝对移动

m_btnJogPos.EnableWindo(enable); // 按钮:正向点动 m_btnJogNeg.EnableWindo(enable); // 按钮:反向点动

m_btnStop.EnableWindo(enable); // 按钮:停止运动

}

第五步,消息响应函数:处理用户交互(核心操作流程)

点击 “初始化” 按钮时,调用 InitializeMotionController 完成控制器创建与初始化,成功后弹出提示

//初始化按钮

void CMachineMotionDlg::OnBnClickedBtnInitialize()

{

// 调用 InitializeMotionController() 执行初始化核心逻辑

    if (InitializeMotionController())

    {

// 若初始化成功,弹出提示框告知用户

        AfxMessageBox(_T("运动控制卡初始化成功!"));  // 提示用户初始化结果

//若初始化失败,不弹出成功提示(失败提示在 InitializeMotionController 中已处理)

    }流程:检查控制器是否有效 → 读取输入框距离 → 调用 RelativeMove → 更新状态文本。

初始化按钮(IDC_BTN_INITIALIZE)

触发函数:OnBnClickedBtnInitialize

核心逻辑:

调用 InitializeMotionController() 函数,完成控制器的创建与初始化:

销毁已存在的控制器对象(若有),避免资源冲突;

根据下拉框(m_cmbCardType)选择的类型(固高 / 研控),通过工厂类创建对应控制器(CGTSMotionController 或 CYKMotionController);

调用控制器的 Initialize() 接口,完成硬件初始化(如连接设备)。

初始化成功:弹出 “初始化成功” 提示,启用所有运动控制按钮(相对移动、点动等),更新状态为 “系统状态:已连接”。

初始化失败:在 InitializeMotionController 中弹出错误提示(如 “创建控制器失败”),保持运动按钮禁用。

当初始化成功时候,所有按钮恢复使能,用于在 “未初始化” 和 “已初始化” 状态间切换控件可用性:未初始化时禁用(防止误操作),初始化后启用(允许用户操作)

下来完成各个案件的初始化

//急停按键

void CMachineMotionDlg::OnBnClickedBtnEmergencyStop()

{

    if (m_pMotionController && m_pMotionController->EmergencyStop())

    {

        AfxMessageBox(_T("急停已触发!"));

        m_staticStatus.SetWindowText(_T("系统状态: 急停"));

    }

}

急停按钮(IDC_BTN_EMERGENCY_STOP)

触发函数:OnBnClickedBtnEmergencyStop

核心逻辑:

检查控制器指针 m_pMotionController 是否有效(非空);

调用控制器的 EmergencyStop() 接口,触发紧急停止(立即强制停止所有运动,优先级最高);

成功后弹出 “急停已触发” 提示,更新状态为 “系统状态:急停”。

//相对运动按键

void CMachineMotionDlg::OnBnClickedBtnRelMove()

{

    if (!m_pMotionController) return;

    CString strDistance;

    m_editRelDistance.GetWindowText(strDistance);

    double distance = _ttof(strDistance);

    if (m_pMotionController->RelativeMove(distance))

    {

        m_staticStatus.SetWindowText(_T("系统状态: 相对运动中"));

    }

相对移动按钮(IDC_BTN_REL_MOVE)

触发函数:OnBnClickedBtnRelMove

核心逻辑:

检查控制器是否初始化(m_pMotionController 非空);

从输入框 m_editRelDistance 中获取用户输入的距离(如 10.0),转换为 double 类型;

调用控制器的 RelativeMove(distance) 接口,执行相对移动(从当前位置偏移指定距离);

成功后更新状态为 “系统状态:相对运动中”。

//绝对运动按键

void CMachineMotionDlg::OnBnClickedBtnAbsMove()

{

    if (!m_pMotionController) return;

    

    CString strPosition;

    m_editAbsPosition.GetWindowText(strPosition);

    double position = _ttof(strPosition);

    

    if (m_pMotionController->AbsoluteMove(position))

    {

        m_staticStatus.SetWindowText(_T("系统状态: 绝对运动中"));

    }

}

绝对移动按钮(IDC_BTN_ABS_MOVE)

触发函数:OnBnClickedBtnAbsMove

核心逻辑:

检查控制器是否初始化;

从输入框 m_editAbsPosition 中获取用户输入的目标位置(如 50.0),转换为 double 类型;

调用控制器的 AbsoluteMove(position) 接口,执行绝对移动(移动到坐标系中的固定位置);

成功后更新状态为 “系统状态:绝对运动中”

//jog正向运动

void CMachineMotionDlg::OnBnClickedBtnJogPos()

{

    if (!m_pMotionController) return;

    

    if (m_pMotionController->JogMove(TRUE))

    {

        m_staticStatus.SetWindowText(_T("系统状态: 正转JOG"));

    }

}正向点动按钮(IDC_BTN_JOG_POS)

触发函数:OnBnClickedBtnJogPos

核心逻辑:

检查控制器是否初始化;

调用控制器的 JogMove(TRUE) 接口,执行正向点动(以预设速度向正方向持续移动,通常用于手动调整);

成功后更新状态为 “系统状态:正转 JOG”。

//jog反向运动按键

void CMachineMotionDlg::OnBnClickedBtnJogNeg()

{

    if (!m_pMotionController) return;

    

    if (m_pMotionController->JogMove(FALSE))

    {

        m_staticStatus.SetWindowText(_T("系统状态: 反转JOG"));

    }

}与正向相反

//停止按键

void CMachineMotionDlg::OnBnClickedBtnStop()

{

    if (m_pMotionController)

    {

        m_pMotionController->StopMove();

        m_staticStatus.SetWindowText(_T("系统状态: 停止"));

    }

}停止按钮(IDC_BTN_STOP)

触发函数:OnBnClickedBtnStop

核心逻辑:

检查控制器是否初始化;

调用控制器的 StopMove() 接口,执行正常停止(减速停止当前运动);

成功后更新状态为 “系统状态:停止”。

总结:实现了运动控制系统的 UI 交互逻辑,通过 MFC 的消息映射机制将用户操作与控制器接口调用绑定,同时通过工厂模式和多态性兼容不同品牌的控制卡,是 “面向接口编程” 和 “分层设计”

3.9完成这些后,就是应用程序类的编写与使用

MachineMotion.h 是 MFC 应用程序的主应用类头文件,定义了应用程序的入口类 CMachineMotionApp,负责整个程序的初始化、运行和退出管理。

CWinApp 是 MFC 框架提供的应用程序基类,封装了 Windows 应用程序的核心逻辑(如消息循环、初始化、退出等)。

CMachineMotionApp 继承自 CWinApp,是当前应用程序的入口类,整个程序的生命周期由该类管理。

构造函数 CMachineMotionApp()用于初始化应用程序对象(theApp),通常在构造函数中设置应用程序的基本属性(如默认图标、文档模板等,你的代码中未显示额外逻辑)。

InitInstance 是 MFC 应用程序的初始化入口函数,程序启动时会自动调用,负责:

创建主窗口(对话框 CMachineMotionDlg);

显示主窗口;

初始化应用程序的全局状态。

返回值 BOOL:TRUE 表示初始化成功,程序继续运行;FALSE 表示初始化失败,程序退出。

(3)消息映射声明DECLARE_MESSAGE_MAP()

声明该类支持 MFC 消息映射机制,用于处理应用程序级别的消息(如系统消息、命令消息等)。实际的消息映射实现通常在 .cpp 文件中(通过 BEGIN_MESSAGE_MAP 宏)。

全局应用对象

extern CMachineMotionApp theApp;

theApp 是 CMachineMotionApp 类的全局实例,是 MFC 应用程序的核心对象。

Windows 系统启动程序时,会先创建 theApp 对象,然后调用其 InitInstance 函数启动应用,整个程序的生命周期与 theApp 绑定。

核心作用是:

确保预编译头和资源文件正确包含,保证程序编译和资源访问正常;

通过继承 CWinApp 获得 MFC 应用程序的基础功能(如消息循环、窗口管理);

声明 InitInstance 作为程序初始化的入口,控制程序启动流程。

theApp 全局对象是程序运行的起点,MFC 框架通过它管理整个应用的生命周期,是连接操作系统与应用程序的桥梁。

主应用类 CMachineMotionApp

class CMachineMotionApp : public CWinApp

{

public:

//主应用类

    CMachineMotionApp();

//初始化

public:

    virtual BOOL InitInstance();

//全局映射

    DECLARE_MESSAGE_MAP()

};

extern CMachineMotionApp theApp;

到此就剩最后一步,创建主文件的源文件

MachineMotion.cpp是 MFC 应用程序的主实现文件,包含了应用程序入口类 CMachineMotionApp 的具体实现,负责程序的启动、主窗口创建和消息循环管理。

 消息映射:应用程序级消息处理

BEGIN_MESSAGE_MAP 到 END_MESSAGE_MAP 定义了应用程序类的消息映射表,用于处理应用程序级别的命令消息。

这里仅映射了 ID_HELP(帮助菜单命令),当用户点击 “帮助” 时,调用基类 CWinApp 的 OnHelp 函数(默认实现,通常打开帮助文档)。

应用程序类构造函数与全局对象

CMachineMotionApp::CMachineMotionApp():构造函数为空,未添加额外初始化逻辑(若需设置应用程序属性,可在此处添加)。

theApp:CMachineMotionApp 的全局实例,是 MFC 程序的 “入口点”——Windows 系统启动程序时,首先创建该对象,触发应用程序的初始化流程。

这是整个程序启动的核心函数,步骤解析:

(1)基类初始化

CWinApp::InitInstance() 是 MFC 框架的初始化函数,负责注册窗口类、加载资源等基础工作,必须调用。

(2)创建并显示主对话框

CMachineMotionDlg dlg;:创建主对话框对象(CMachineMotionDlg 是你的运动控制界面)。

m_pMainWnd = &dlg;:MFC 要求将主窗口指针赋值给 m_pMainWnd,框架通过该指针管理主窗口的生命周期。

dlg.DoModal();:显示模态对话框 —— 对话框弹出后,用户必须关闭它才能进行其他操作(整个程序的交互都在这个对话框中进行)。

(3)程序退出控制

返回 FALSE 表示:当主对话框(dlg)关闭后,应用程序直接退出,不再进入消息循环(因为模态对话框本身已处理了所有用户交互)。

//开局的消息映射

BEGIN_MESSAGE_MAP(CMachineMotionApp, CWinApp)

    ON_COMMAND(ID_HELP, &CWinApp::OnHelp)END_MESSAGE_MAP()

CMachineMotionApp::CMachineMotionApp(){}

CMachineMotionApp theApp;

BOOL CMachineMotionApp::InitInstance()

{

    CWinApp::InitInstance();// 调用基类初始化(MFC 框架必要步骤)

    // 1. 创建主对话框对象

CMachineMotionDlg dlg;

    // 2. 将对话框设为主窗口(MFC 框架要求设置 m_pMainWnd)

m_pMainWnd = &dlg;

    // 3. 显示模态对话框(阻塞式,直到对话框关闭才返回)

    dlg.DoModal();

    // 4. 返回 FALSE:表示程序主窗口关闭后,应用程序退出(不再进入消息循环)

return FALSE;

}

四,最后就是总结说明啦

使用说明

初始化:选择控制卡类型,点击"初始化"按钮

相对运动:输入距离(mm),点击"运动"按钮

绝对运动:输入目标位置(mm),点击"运动"按钮

JOG运动:点击"正转"或"反转"按钮进行连续运动

停止:点击"停止"按钮停止当前运动

急停:紧急情况下点击"急停"按钮

到此就谢谢大家观看,后续也会加强学习,以及更新其他使用的方法,愿大家共同进步

更多推荐