t的宣傳語:一個框架,一套代碼庫,任意平臺部署。
Qt開發平臺下概念太多,新手們可能給一個個名詞搞暈了:Qt, Qt Core, Qt Quick, Qt QML。。。所以整理了一下,給新手們導一下航。
Qt創造出卓越的用戶體驗將用戶轉變為死忠粉,并持續塑造、提升品牌形象。
2. Qt能為你節省開支
有了Qt, 一個代碼棧和一個開發團隊就能同時支持所有目標平臺。
3. 讓產品更快上市
Qt為項目簡化每一步工作流程。有了Qt,設計師和開發者能無縫協作,充分利用Qt現成的各類庫在競爭脫穎而出,率先交付產品。
4. 性能,已達
性能也是一種功能。 Qt讓可以您輕松交付。更靈敏的人機交互、更快的啟動時間、更流暢的動畫和更好的性能,讓您的UX與眾不同。
5. 快樂的開發者才是高效的開發者,無需996
Qt是開發者想要的最快、最簡單和最有趣的體驗。
6. 適用在任何平臺上進行開發
“一次開發、任意部署”說的就是Qt。跨平臺開發不僅能節省開支,還能推動戰略落地。
7. 靈活。可靠。就是Qt。
通過從第三方圖形設計工具導入UI資產、使用Qt預制組件、控件或QML來設計它們。
8. 一個框架、更少依賴性
基于不同框架的庫和工具集開發的應用程序不僅很復雜,而且更不穩定 —— 不信,您去問問弗蘭肯斯坦博士。
9. 支持多種語言
Qt支持不同的開發語言。C++高效、強大、通用。Qt的聲明式UI語言QML使得以快速創建UI變得容易。你甚至可以用Python來開發!
10. 開源且永不過時
您是否知道一個開發框架的生態系統可以為您項目的未來保駕護航?Qt擁有超過百萬的專業用戶群體。因為根植開源,社區的貢獻讓Qt蓬勃發展。
Qt網站上看起來好像吹得天花亂墜,但確實有它的過人之處。
現在跨平臺開發里Flutter、Electron火熱,是因為上手簡單,前端HTML5的資源最多,相應開發者也人數眾多(迭代也非常快)。但離絲滑使用體驗還是有一差距的。
所以有空學習一下Python, C++,結合Qt,還是可以吃香很久的。
1、元對象系統簡介
Qt的信號槽和屬性系統基于在運行時進行內省的能力,所謂內省是指面向對象語言的一種在運行期間查詢對象信息的能力, 比如如果語言具有運行期間檢查對象型別的能力,那么是型別內省(type intropection)的,型別內省可以用來實施多態。
'C++'的內省比較有限,僅支持型別內省, 'C++'的型別內省是通過運行時類型識別(RTTI)(Run-Time Type Information)中的typeid 以及 dynamic_cast關鍵字來實現的。
Qt拓展了’C++'的內省機制,但并沒有采用’C++'的RTTI,而是提供了更為強大的元對象(meta object)機制,來實現內省機制。基于內省機制,可以列出對象的方法和屬性列表,并且能夠獲取有關對象的所有信息,如參數類型。如果沒有內省機制,QtScript和 QML是難以實現的。
Qt中的元對象系統全稱Meta Object System,是一個基于標準’C++'的擴展,為Qt提供了信號與槽機制、實時類型信息、動態屬性系統。元對象系統基于QObject類、Q_OBJECT宏、元對象編譯器MOC實現。
A、QObject 類
作為每一個需要利用元對象系統的類的基類。
B、Q_OBJECT宏
定義在每一個類的私有數據段,用來啟用元對象功能,比如動態屬性、信號和槽。
在一個QObject類或者其派生類中,如果沒有聲明Q_OBJECT宏,那么類的metaobject對象不會被生成,類實例調用metaObject()返回的就是其父類的metaobject對象,導致的后果是從類的實例獲得的元數據其實都是父類的數據。因此類所定義和聲明的信號和槽都不能使用,所以,任何從QObject繼承出來的類,無論是否定義聲明了信號、槽和屬性,都應該聲明Q_OBJECT 宏。
C、元對象編譯器MOC (Meta Object Complier),
MOC分析C++源文件,如果發現在一個頭文件(header file)中包含Q_OBJECT 宏定義,會動態的生成一個moc_xxxx命名的C++源文件,源文件包含Q_OBJECT的實現代碼,會被編譯、鏈接到類的二進制代碼中,作為類的完整的一部分。
2、元對象系統的功能
元對象系統除了提供信號槽機制在對象間進行通訊的功能,還提供了如下功能:
QObject::metaObject() 方法
獲得與一個類相關聯的 meta-object
QMetaObject::className() 方法
在運行期間返回一個對象的類名,不需要本地’C++'編譯器的RTTI(run-time type information)支持
QObject::inherits() 方法
用來判斷生成一個對象類是不是從一個特定的類繼承出來,必須是在QObject類的直接或者間接派生類當中。
QObject::tr() and QObject::trUtf8()
為軟件的國際化翻譯字符串
QObject::setProperty() and QObject::property()
根據屬性名動態的設置和獲取屬性值
使用qobject_cast()方法在QObject類之間提供動態轉換,qobject_cast()方法的功能類似于標準C++的dynamic_cast(),但qobject_cast()不需要RTTI的支持。
3、Q_PROPERTY()的使用
#define Q_PROPERTY(text)
Q_PROPERTY定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC處理。
Q_PROPERTY(type name
READ getFunction
[WRITE setFunction]
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
Type:屬性的類型
Name:屬性的名稱
READ getFunction:屬性的訪問函數
WRITE setFunction:屬性的設置函數
RESET resetFunction:屬性的復位函數
NOTIFY notifySignal:屬性發生變化的地方發射的notifySignal信號
REVISION int:屬性的版本,屬性暴露到QML中
DESIGNABLE bool:屬性在GUI設計器中是否可見,默認為true
SCRIPTABLE bool:屬性是否可以被腳本引擎訪問,默認為true
STORED bool:
USER bool:
CONSTANT:標識屬性的值是常量,值為常量的屬性沒有WRITE、NOTIFY
FINAL:標識屬性不會被派生類覆寫
注意:NOTIFY notifySignal聲明了屬性發生變化時發射notifySignal信號,但并沒有實現,因此程序員需要在屬性發生變化的地方發射notifySignal信號。
Object.h:
#ifndef OBJECT_H
#define OBJECT_H
#include <QObject>
#include <QString>
#include <QDebug>
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(int score READ score WRITE setScore NOTIFY scoreChanged)
Q_CLASSINFO("Author", "Scorpio")
Q_CLASSINFO("Version", "1.0")
Q_ENUMS(Level)
protected:
QString m_name;
QString m_level;
int m_age;
int m_score;
public:
enum Level
{
Basic,
Middle,
Advanced
};
public:
explicit Object(QString name, QObject *parent = 0):QObject(parent)
{
m_name = name;
setObjectName(m_name);
connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
}
int age()const
{
return m_age;
}
void setAge(const int& age)
{
m_age = age;
emit ageChanged(m_age);
}
int score()const
{
return m_score;
}
void setScore(const int& score)
{
m_score = score;
emit scoreChanged(m_score);
}
signals:
void ageChanged(int age);
void scoreChanged(int score);
public slots:
void onAgeChanged(int age)
{
qDebug() << "age changed:" << age;
}
void onScoreChanged(int score)
{
qDebug() << "score changed:" << score;
}
};
#endif // OBJECT_H
Main.cpp:
#include <QCoreApplication>
#include "Object.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object ob("object");
//設置屬性age
ob.setProperty("age", QVariant(30));
qDebug() << "age: " << ob.age();
qDebug() << "property age: " << ob.property("age").toInt();
//設置屬性score
ob.setProperty("score", QVariant(90));
qDebug() << "score: " << ob.score();
qDebug() << "property score: " << ob.property("score").toInt();
//內省intropection,運行時查詢對象信息
qDebug() << "object name: " << ob.objectName();
qDebug() << "class name: " << ob.metaObject()->className();
qDebug() << "isWidgetType: " << ob.isWidgetType();
qDebug() << "inherit: " << ob.inherits("QObject");
return a.exec();
}
4、Q_INVOKABLE使用
#define Q_INVOKABLE
Q_INVOKABLE定義在/src/corelib/kernel/Qobjectdefs.h文件中,用于被MOC識別。
Q_INVOKABLE宏用于定義一個成員函數可以被元對象系統調用,Q_INVOKABLE宏必須寫在函數的返回類型之前。如下:
【領QT開發教程學習資料,點擊下方鏈接莬費領取↓↓,先碼住不迷路~】
點擊→領取「鏈接」
Q_INVOKABLE void invokableMethod();
invokableMethod()函數使用了Q_INVOKABLE宏聲明,invokableMethod()函數會被注冊到元對象系統中,可以使用 QMetaObject::invokeMethod()調用。
Q_INVOKABLE與QMetaObject::invokeMethod均由元對象系統喚起,在Qt C++/QML混合編程、跨線程編程、Qt Service Framework以及 Qt/ HTML5混合編程以及里廣泛使用。
A、在跨線程編程中的使用
如何調用駐足在其他線程里的QObject方法呢?Qt提供了一種非常友好而且干凈的解決方案:向事件隊列post一個事件,事件的處理將以調用所感興趣的方法為主(需要線程有一個正在運行的事件循環)。而觸發機制的實現是由MOC提供的內省方法實現的。因此,只有信號、槽以及被標記成Q_INVOKABLE的方法才能夠被其它線程所觸發調用。如果不想通過跨線程的信號、槽這一方法來實現調用駐足在其他線程里的QObject方法。另一選擇就是將方法聲明為Q_INVOKABLE,并且在另一線程中用invokeMethod喚起。
B、Qt Service Framework
Qt服務框架是Qt Mobility 1.0.2版本推出的,一個服務(service)是一個獨立的組件提供給客戶端(client)定義好的操作。客戶端可以通過服務的名稱,版本號和服務的對象提供的接口來查詢服務。 查找到服務后,框架啟動服務并返回一個指針。
服務通過插件(plug-ins)來實現。為了避免客戶端依賴某個具體的庫,服務必須繼承自QObject,保證QMetaObject?系統可以用來提供動態發現和喚醒服務的能力。要使QmetaObject機制充分的工作,服務必須滿足,其所有的方法都是通過 signal、slot、property或invokable method和Q_INVOKEBLE來實現。
QServiceManager manager;
QObject *storage ;
storage = manager.loadInterface("com.nokia.qt.examples.FileStorage");
if(storage)
QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt"));
上述代碼通過service的元對象提供的invokeMethod方法,調用文件存儲對象的deleteFile() 方法。客戶端不需要知道對象的類型,因此也沒有鏈接到具體的service庫。 當然在服務端的deleteFile方法,一定要被標記為Q_INVOKEBLE,才能夠被元對象系統識別。
Qt服務框架的一個亮點是它支持跨進程通信,服務可以接受遠程進程。在服務管理器上注冊后,進程通過signal、slot、invokable method和property來通信,就像本地對象一樣。服務可以設定為在客戶端間共享,或針對一個客戶端。 在Qt服務框架推出之前,信號、槽以及invokable method僅支持跨線程。 下圖是跨進程的服務/客戶段通信示意圖。invokable method和Q_INVOKEBLE 是跨進城、跨線程對象之間通信的重要利器。
1、Q_OBJECT宏的定義
任何從QObject派生的類都包含自己的元數據模型,一般通過宏Q_OBJECT定義。
Q_OBJECT定義在/src/corelib/kernel/Qobjectdefs.h文件中。
#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
static const QMetaObject staticMetaObject; \
Q_OBJECT_GETSTATICMETAOBJECT \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
Q_DECL_HIDDEN static const QMetaObjectExtraData staticMetaObjectExtraData; \
Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
QMetaObject類型的靜態成員變量staticMetaObject是元數據的數據結構。metaObject,qt_metacast,qt_metacall、qt_static_metacall四個虛函數由MOC在生成的moc_xxx.cpp文件中實現。metaObject的作用是得到元數據表指針;qt_metacast的作用是根據簽名得到相關結構的指針,返回void*指針;qt_metacall的作用是查表然后調用調用相關的函數;qt_static_metacall的作用是調用元方法(信號和槽)。
#define Q_DECL_HIDDEN __attribute__((visibility("hidden")))
2、QMetaObject類型
QMetaObject類定義在/src/corelib/kernel/Qobjectdefs.h文件。
struct Q_CORE_EXPORT QMetaObject
{
...
enum Call {
InvokeMetaMethod,
ReadProperty,
WriteProperty,
ResetProperty,
QueryPropertyDesignable,
QueryPropertyScriptable,
QueryPropertyStored,
QueryPropertyEditable,
QueryPropertyUser,
CreateInstance
};
int static_metacall(Call, int, void **) const;
static int metacall(QObject *, Call, int, void **);
struct { // private data
const QMetaObject *superdata;
const char *stringdata;
const uint *data;
const void *extradata;
} d;
};
QMetaObject中有一個嵌套結構封裝了所有的數據:
const QMetaObject *superdata;//元數據代表的類的基類的元數據
const char *stringdata;//元數據的簽名標記
const uint *data;//元數據的索引數組的指針
const QMetaObject **extradata;//擴展元數據表的指針,指向QMetaObjectExtraData數據結構。
struct QMetaObjectExtraData
{
#ifdef Q_NO_DATA_RELOCATION
const QMetaObjectAccessor *objects;
#else
const QMetaObject **objects;
#endif
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **); //from revision 6
//typedef int (*StaticMetaCall)(QMetaObject::Call, int, void **); //used from revison 2 until revison 5
StaticMetacallFunction static_metacall;
};
static_metacall是一個指向Object::qt_static_metacall 的函數指針。
3、QT_TR_FUNCTIONS宏定義
宏QT_TR_FUNCTIONS是和翻譯相關的。
#define QT_TR_FUNCTIONS \
static inline QString tr(const char *s, const char *c = 0) \
{ return staticMetaObject.tr(s, c); } \
#endif
4、Qt中其它宏的定義
Qt在/src/corelib/kernel/Qobjectdefs.h文件中定義了大量的宏。
#ifndef Q_MOC_RUN
# if defined(QT_NO_KEYWORDS)
# define QT_NO_EMIT
# else
# define slots
# define signals protected
# endif
# define Q_SLOTS
# define Q_SIGNALS protected
# define Q_PRIVATE_SLOT(d, signature)
# define Q_EMIT
#ifndef QT_NO_EMIT
# define emit
#endif
#define Q_CLASSINFO(name, value)
#define Q_INTERFACES(x)
#define Q_PROPERTY(text)
#define Q_PRIVATE_PROPERTY(d, text)
#define Q_REVISION(v)
#define Q_OVERRIDE(text)
#define Q_ENUMS(x)
#define Q_FLAGS(x)
#define Q_SCRIPTABLE
#define Q_INVOKABLE
#define Q_SIGNAL
#define Q_SLOT
Qt中的大部分宏都無實際的定義,都是提供給MOC識別處理的,MOC工具通過對類中宏的解析處理生成moc_xxx.cpp文件。
在 Qt4 及之前的版本中,signals被展開成protected。Qt5則變成public,用以支持新的語法。
1、MOC功能
A、處理Q_OBJECT宏和signals/slots關鍵字,生成信號和槽的底層代碼
B、處理Q_PROPERTY()和Q_ENUM()生成property系統代碼
C、處理Q_FLAGS()和Q_CLASSINFO()生成額外的類meta信息
D、不需要MOC處理的代碼可以用預定義的宏括起來,如下:
#ifndef Q_MOC_RUN
…
#endif
2、MOC限制
A、模板類不能使用信號/槽機制
B、MOC不擴展宏,所以信號和槽的定義不能使用宏, 包括connect的時候也不能用宏做信號和槽的名字以及參數
C、從多個類派生時,QObject派生類必須放在第一個。 QObject(或其子類)作為多重繼承的父類之一時,需要把它放在第一個。 如果使用多重繼承,moc在處理時假設首先繼承的類是QObject的一個子類,需要確保首先繼承的類是QObject或其子類。
D、函數指針不能作為信號或槽的參數, 因為其格式比較復雜,MOC不能處理。可以用typedef把它定義成簡單的形式再使用。
E、用枚舉類型或typedef的類型做信號和槽的參數時,必須fully qualified。這個詞中文不知道怎么翻譯才合適,簡單的說就是, 如果是在類里定義的, 必須把類的路徑或者命名空間的路徑都加上, 防止出現混淆。如Qt::Alignment之類的,前面的Qt就是Alignment的qualifier, 必須加上,而且有幾級加幾級。
F、信號和槽不能返回引用類型
G、signals和slots關鍵字區域只能放置信號和槽的定義,不能放其它的如變量、構造函數的定義等,友元聲明不能位于信號或者槽聲明區內。
H、嵌套類不能含有信號和槽
MOC無法處理嵌套類中的信號和槽,錯誤的例子:
class A:public QObject
{
Q_OBJECT
public:
class B
{
public slots://錯誤用法
};
};
I、信號槽不能有缺省參數
3、自定義類型的注冊
Qt線程間傳遞自定義類型數據時,自己定義的類型如果直接使用信號槽來傳遞的話會產生下面這種錯誤:
原因:當一個signal被放到隊列中(queued)時,參數(arguments)也會被一起一起放到隊列中,參數在被傳送到slot之前需要被拷貝、存儲在隊列中;為了能夠在隊列中存儲參數(argument),Qt需要去construct、destruct、copy參數對象,而為了讓Qt知道怎樣去作這些事情,參數的類型需要使用qRegisterMetaType來注冊。
步驟:(以自定義XXXXX類型為例)
A、自定義類型時在類的頂部包含:#include <QMetaType>
B、在類型定義完成后,加入聲明:Q_DECLARE_METATYPE(XXXXX);
C、在main()函數中注冊自定義類類型:qRegisterMetaType<XXXXX>(“XXXXX”);
如果希望使用類型的引用,同樣要注冊:qRegisterMetaType<XXXXX>(“XXXXX&”);
4、MOC的使用
查看工程的Makefile文件可以查找到MOC生成moc_xxx.cpp文件的命令:
moc_Object.cpp: ../moc/Object.h
/usr/local/Trolltech/Qt-4.8.6/bin/moc $(DEFINES) $(INCPATH) ../moc/Object.h -o moc_Object.cpp
因此命令行可以簡化為:
moc Object.h -o moc_Object.cpp
滿足換流站監控系統安防要求,許繼電氣股份有限公司的研究人員張浩然、趙冠華、申艷紅、靳瑋瑋、張睿,在2020年第9期《電氣技術》雜志上撰文,提出了基于Qt框架中的QtWebEngine模塊,利用Web技術開發可在Linux上運行的C/S報表組件的技術方案。文中首先介紹了報表文件的格式定義,然后介紹了設計器和查看器的設計思路,并對其中關鍵的交互流程及實現方法進行了說明。
出于國家安全考慮,現在換流站監控系統要求運行在Linux系統上。現有的客戶端/服務器(client/server, C/S)類專用報表組件如水晶報表等都是運行在Windows系統上的,在Linux上無法使用。在Linux上雖然有Open Office等辦公套件,但是由于Linux上沒有類似Windows上的對象連接與嵌入技術(object linking and embedding, OLE)的對象嵌入機制,所以也無法利用這些辦公套件開發報表。
雖然現有的瀏覽器/服務器(browser/server, B/S)類報表組件可以運行在Linux系統上,但是使用B/S類報表,需要部署Web服務器。而變電站監控系統屬于一區系統,按照二次安防要求,不能采用Web服務。因此,無法使用B/S類報表組件。故此,開發可以在Linux系統使用的C/S報表組件,勢在必行。
本文提供了基于QtWebEngine模塊,利用Web技術的報表組件開發方案。此方案既利用了Web頁面的強大展示能力,又通過QtWebEngine與Web頁面的交互能力避開了搭建Web服務器的需要,從而構建了一套強大靈活的C/S報表組件。以下對此技術方案的技術環節進行闡述。
QtWebEngine是Qt框架中的一個瀏覽器模塊,它提供了易于使用且可擴展的應用程序接口(appli- cation programming interface, API)。利用QtWebEngine可以很容易地把Web內容嵌入到Qt應用程序中。QtWebEngine不允許C++/Qt代碼直接操作頁面元素。然而,QtWebEngine提供了RunJavaScript方法。可以通過該方法調用Web頁面的JavaScript腳本,并獲取執行結果,從而獲取和調整頁面內容。
QtWebEngine支持Web技術中最新的超文本標記語言(hyper text markup language, HTML)第五版標準。HTML5提供了一套拖放接口,使Web應用能夠支持拖放功能。通過這些功能,用戶可以使用鼠標選擇可拖動元素,并將元素拖動到可放置容器,通過釋放鼠標按鈕來放置這些元素。
開發者可以自定義能夠成為可拖拽的元素類型、可拖拽元素產生的反饋,以及可放置的容器元素。基于HTML5的拖放接口,可以實現報表的可視化設計。此外,Web技術中的高性能數據圖表和數據表格也是報表開發中必需的功能。
報表模板文件的本質是一個基于JavaScript對象表示法(Java script object notation, JSON)格式的文本文件,其內部保存了數據源和界面部件信息兩部分數據。
2.1 數據源
數據源保存的并非真正在報表中使用的數據,而是報表要使用的數據點的編號和名稱信息。當用戶查看報表時,查看器會根據數據源中的數據點編號和用戶選擇的時間段,動態生成一個以數據點編號為列,以時間點為行的表格。這才是報表要使用的數據。數據源在報表模板中以JSON數組形式存儲,如圖1所示。
圖1 數據源JSON結構
每個數組元素是一個JSON對象,對象內部由兩個鍵值對組成,id代表數據點的編號,name代表數據點名稱。
2.2 界面部件信息
報表界面部件信息包含了報表界面部件的布局關系和每個部件自身的屬性設置。界面部件的布局關系可以用一個樹形的結構表示,如圖2所示。
整個報表作為根節點;在報表頂層的部件為第一級子節點;其中的容器類型節點可以擁有自己的子節點,容器類型節點可以嵌套。報表組件在展示報表時可以遞歸遍歷這個樹形結構來創建組件對象。
這個樹形結構在報表模板中用嵌套的JSON對象來存儲,如圖3所示。
圖2 界面部件布局關系
圖3 界面部件信息JSON結構
界面部件的結構定義如下:1)type,組件類型;2)id,組件編號;3)options,組件屬性;4)list,組件的子節點列表。
加載報表模板文件后,設計器和查看器可以根據部件的type獲取對應的類型,創建部件實例。在設計階段,設計器可以通過部件id定位部件對象,以便更新部件屬性設置和調整部件位置。
報表組件分為設計器與查看器兩個部分。設計器用于創建和編輯報表,由監控系統工程開發人員使用;查看器給運行人員使用,用于在線展示報表內容。設計器與查看器是相互獨立的,但是在底層通過報表模板的格式定義相互影響,共享部分設計。
3.1 設計器
設計器內部的組件關系如圖4所示。
圖4 報表設計器組件關系
由圖4可以看出,Web頁面內的主要組件有Widgets-list、Type-list、Report-from、Properties-editor、DataSource-editor和Store。
Widgets-list是一個提供了包含所有報表部件占位符的列表組件;Type-list是非可視化組件,提供了報表部件的類型定義;創建組件的時候,需要獲取對應的組件類型,比如數據表格、數據圖表,所以需要一個組件的類型映射表,根據組件的type獲取對應的類型,創建實例。
Report-form代表了報表表單。Widgets-list內的部件占位符都被賦予HTML中的draggable屬性,這樣可以將部件占位符從Widgets-list拖到Report- form上。當一個部件占位符被從Widgets-list拖拽走時,Widgets-list會創建該占位符的一個復制品,供下次使用。
Report-form組件監聽了drop事件。當部件占位符被拖到Report-form上時,會觸發監測的drop事件。Report-form組件的事件處理器可以從事件參數中獲取部件占位符,并提取出其攜帶的部件類型信息。然后Widget-builder根據部件類型信息從Type- lis獲取完整的部件定義,并創建相應的部件實例。
Store組件管理數據源定義、報表結構信息、報表組件屬性等內容。DataSource-editor組件用于編輯修改數據源定義。
Properties-editor組件用于編輯修改報表組件的屬性,Store組件與Properties-editor組件、Report- form組件都是雙向交互的。在Properties-editor組件中修改了組件屬性,Store中的內容會相應改變,并且會改變Report-form中報表的顯示。在Report-form中修改了報表的部件或布局時,Store中的信息會相應改變,在Properties-editor組件上顯示的內容也會跟隨變動。
報表設計器的Qt部分加載報表模板或創建一個內容為空的報表模板后,會執行RunJavaScript方法來調用Web側的LoadReportTpl方法,將模板文件傳遞給Web側。Web側將報表模板解析后,先存入Store組件,然后由Report-form組件構建相應的報表部件并顯示。
用戶隨后可以通過可視化方式對報表模板進行編輯,如配置數據源、添加修改圖表/表格、設置圖表/表格的關聯數據點等。用戶編輯完模板后,可以點擊Qt側的保存按鈕,然后Qt側執行RunJavaScript方法調用Web側的SaveReportTpl方法,從Web側獲取報表模板。
3.2 查看器
查看器內部結構的組件關系如圖5所示。
圖5 報表查看器組件關系
由圖5可以看出,查看器Web頁面內的主要組件有Type-list、Report-from和Store。查看器內的組件與設計器內同名組件的功能基本相同。不同之處在于,Store組件和Report-from的交互關系為單向。Web側將報表模板解析后,先存入Store組件,然后由Report-form組件構建相應的報表部件并顯示。Report-from不再對Store組件有影響。
查看器的組件關系相比設計器較為簡單。然而,其工作流程卻相對復雜。查看器的工作流程如圖6所示。
圖6 報表查看器工作流程
由圖6可以看出,報表查看器的工作流程如下:Qt部分負責加載報表模板;然后Qt側通過RunJavaScript方法調用Web側的LoadReportTpl方法,將報表模板文件傳遞給Web側,由Web側顯示空的報表框架;同時Qt側對報表模板進行解析,提取出其中的數據源設置;然后Qt側根據解析出的數據源配置以及用戶設置的時間段從服務器獲取相應時間端的數據;Qt側通過RunJavaScript方法調用Web側的LoadDataSet方法,將報表要使用的數據集傳遞給Web側;最后Web側用數據集填充報表框架,將報表渲染出來。
本文描述的報表組件開發方案,提供了可視化的報表設計工具,報表格式能夠靈活定義,而且能夠跨平臺使用,充分滿足變電站監控系統的安防需求。并且,報表組件基于QtWebEngine和Web技術開發,報表模板采用JSON格式定義,不依賴于第三方報表組件,具有良好的兼容性和通用性,易于應用到其他C/S架構的客戶端系統中。
本技術方案也可供使用其他非Qt框架的監控系統做技術參考,只要使用的開發框架有可用的瀏覽器組件,即可采用類似的技術路線開發報表組件。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。