QML控件访问EPICS过程变量的思路
前言 写这篇文章的想法是使用自定义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过程变量的字段声明为QmlPvObject类的属性,然后就可以在QML中访问EPICS过程变量了。 第2步是将QmlPvObject封装进自定义的QML控件QmlPvControl,实现数据的显示、输入等操作。 第3步将QmlPvControl导入(import)到QML文件,在外部QML文件中使用自定义控件。 代码示例 由于完整的代码较多,这里只放部分代码。 QmlPvObject类的定义 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 /* qmlpvobject.h */ class QmlPvObject : 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) 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(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(QmlPvObject::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(bool readAccess READ readAccess NOTIFY readAccessChanged FINAL) Q_PROPERTY(bool writeAccess READ writeAccess NOTIFY writeAccessChanged FINAL) public: explicit QmlPvObject(QObject *parent = Q_NULLPTR); QString pvName() const; QVariant value() const; QString hostName() const; QString fieldType() const; QString descriptor() const; QString egu() const; QCaDateTime dateTime() const; quint16 status() const; QmlPvObject::epicsAlarmSeverity severity() const; QString statusName() const; QString severityName() 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 onConnectionChanged(QCaConnectionInfo& connectionInfo, const uint& variableIndex); virtual void onDataChanged(const QVariant& value, QCaAlarmInfo& alarmInfo, QCaDateTime& timeStamp, const uint& variableIndex); Q_SIGNALS: void pvNameChanged(const QString&); void valueChanged(const QVariant&); 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 epicsAlarmSeverity); void statusNameChanged(const QString); void severityNameChanged(const QString); void readAccessChanged(const bool); void writeAccessChanged(const bool); protected: QPointer<qcaobject::QCaObject> m_caobject; // ... }; 注意到QmlPvObject类有两个虚函数setPvName和setValue,这两个函数需要子类实现。 ...