前言 写这篇文章的想法是使用自定义QML控件的方式访问EPICS过程变量,现有的Qt EPICS框架之前也介绍过了,还有配套的QEGui工具,框架的基本功能已经比较完善,但实际使用过程中还是遇到了一些问题。我总结有以下2点:
QEGui工具只能加载基于Widget的.ui界面文件,文件只能包含布局、控件和控件属性,不能嵌入代码(C++代码必须编译)。用户的操作(如:输入、点击按钮等)的相应槽函数封装在框架自定义的控件里实现,这样极大方便了用户的开发,用户只需要设计界面,填写变量名就可以实现变量访问,但相应的也失去了很大的灵活性,程序的功能变得很单一。
QEGui工具不一定符合实际开发过程中的需求,仅使用QEGui加载.ui界面,脱离了C++代码,程序很难实现用户想要的效果。而更好的选择是调用Qt EPICS框架动态库提供的控件,结合C++代码开发应用程序,但却不得不进行编译操作。
那么,有没有更好的基于Qt的EPICS框架方案呢?
刚好最近我也学习实践了QML相关的内容,这种前后端分离开发的方式给了我一些灵感。我们完全可以在Qt EPICS框架的基础上,实现自定义QML控件访问EPICS过程变量,用户使用QML编写界面,调用自定义QML控件即可,可以实现和Qt EPICS框架加载.ui界面文件类似的功能。但QML在动画、3D显示等方面明显具有优势,还有一点是基于Widget的.ui界面文件不具备的:QML本身可以嵌入javascript函数,动态控制界面的显示、切换等,甚至可以实现和底层C++接口的交互。而QML本身也不需要进行编译,完全可以只使用QML语言实现程序开发,且具有很高灵活性。
实现思路 Qt EPICS 框架提供了QCaObject类访问EPICS过程变量,但该类几乎是完全为QEWidget服务的,并没有声明属性(property),无法直接在QML中使用。
所以第1步需要对QCaObject做一层封装,将EPICS过程变量的字段声明为QPvObject类的属性,然后就可以在QML中访问EPICS过程变量了。
第2步是将QPvObject封装进自定义的QML控件QmlPvControl,实现数据的显示、输入等操作。
第3步将QmlPvControl导入(import)到QML文件,在外部QML文件中使用自定义控件。
代码示例 由于完整的代码较多,这里只放部分代码。
QPvObject类的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 /* qpvobject.h */ class QPvObject : public QObject { Q_OBJECT public: enum epicsAlarmSeverity { NO_ALARM, /**< No alarm */ MINOR_ALARM, /**< Minor alarm severity */ MAJOR_ALARM, /**< Major alarm severity */ INVALID_ALARM, /**< Invalid alarm severity */ ALARM_NSEV /**< Number of alarm severities */ }; Q_ENUM(epicsAlarmSeverity) enum pvConnectionMode { NONE, WR_ONLY, /**< Write only */ RD_ONCE, /**< Read once */ MONITOR /**< Read and Write */ }; Q_ENUM(pvConnectionMode) private: Q_PROPERTY(QString pvName READ pvName WRITE setPvName NOTIFY pvNameChanged FINAL) Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged FINAL) Q_PROPERTY(QPvObject::pvConnectionMode mode READ mode WRITE setMode NOTIFY modeChanged FINAL) Q_PROPERTY(QString hostName READ hostName NOTIFY hostNameChanged FINAL) Q_PROPERTY(QString fieldType READ fieldType NOTIFY fieldTypeChanged FINAL) Q_PROPERTY(QString descriptor READ descriptor NOTIFY descriptorChanged FINAL) Q_PROPERTY(QString egu READ egu NOTIFY eguChanged FINAL) Q_PROPERTY(QCaDateTime dateTime READ dateTime NOTIFY dateTimeChanged FINAL) Q_PROPERTY(quint16 status READ status NOTIFY statusChanged FINAL) Q_PROPERTY(QPvObject::epicsAlarmSeverity severity READ severity NOTIFY severityChanged FINAL) Q_PROPERTY(QString statusName READ statusName NOTIFY statusNameChanged FINAL) Q_PROPERTY(QString severityName READ severityName NOTIFY severityNameChanged FINAL) Q_PROPERTY(quint32 hostElementCount READ hostElementCount NOTIFY hostElementCountChanged FINAL) Q_PROPERTY(quint32 dataElementCount READ dataElementCount NOTIFY dataElementCountChanged FINAL) Q_PROPERTY(bool readAccess READ readAccess NOTIFY readAccessChanged FINAL) Q_PROPERTY(bool writeAccess READ writeAccess NOTIFY writeAccessChanged FINAL) public: QString pvName() const; QVariant value() const; QPvObject::pvProtocol protocol() const; QPvObject::pvConnectionMode mode() const; QString hostName() const; QString fieldType() const; QString descriptor() const; QString egu() const; const QCaDateTime& dateTime() const; quint16 status() const; QPvObject::epicsAlarmSeverity severity() const; QString statusName() const; QString severityName() const; quint32 hostElementCount() const; quint32 dataElementCount() const; bool readAccess() const; bool writeAccess() const; public Q_SLOTS: virtual void setPvName(const QString &pvname) = 0; virtual void setValue(const QVariant &value) = 0; virtual void setMode(const QPvObject::pvConnectionMode &mode); Q_SIGNALS: void pvNameChanged(const QString&); void valueChanged(const QVariant&); void protocolChanged(const QPvObject::pvProtocol&); void modeChanged(const QPvObject::pvConnectionMode&); void dateTimeChanged(const QCaDateTime&); void fieldTypeChanged(const QString&); void descriptorChanged(const QString&); void eguChanged(const QString&); void hostNameChanged(const QString&); void statusChanged(const quint16); void severityChanged(const QPvObject::epicsAlarmSeverity); void statusNameChanged(const QString); void severityNameChanged(const QString); void hostElementCountChanged(const quint32); void dataElementCountChanged(const quint32); void readAccessChanged(const bool); void writeAccessChanged(const bool); protected: QPointer<qcaobject::QCaObject> m_caobject; // ... }; 注意到QPvObject类有两个虚函数setPvName和setValue,这两个函数需要子类实现。
...