Qt4學習筆記 (7) 本篇說一下Qt對於腳本的支持, 即QtScript模塊.


本篇說一下Qt對於腳本的支持, 即QtScript模塊.

Qt支持的腳本基於ECMAScript腳本語言, 這個東西又是javascript, jscript的基礎. 所以, 一般只要學過javascript就基本會寫Qt腳本了. 自此開始, Qt腳本現在就叫javascript.
    不過作為土人, javascript中有一個prototype的概念, 現在才知道. javascript本沒有類的概念, 跟不用說是繼承之類的了. 但是憑借prototype的特性, 我們可以實現類似C++中類, 以及類繼承等一些特性.
    prototype是個什么概念? 因為這個單詞實在表意不清, 導致我花了很多時間來理解這個. 每個javascript對象都有一個指向另一個對象的引用, 這就是它的prototype. 一個對象的prototype定義了這個對象可以進行的操作集. 用C++來類比的話, 這些操作集是一定是成員函數. 看下面的javascript代碼:

function Shape(x, y) { this.x = x; this.y = y; } Shape.prototype.area = function() { return 0; } function Circle(x, y, radius) { Shape.call(this, x, y); this.radius = radius; } Circle.prototype = new Shape; Circle.prototype.area = function() { return Math.PI * this.radius * this.radius; }

我們把Circle對象的prototype設置成Shape對象, 實際上就是把Shape對象的prototype賦給了Circle對象, 讓Circle對象的初始操作集跟Circle對象是一樣的. 之后我們又重載了area()函數, 當然我們還可以加入新的函數. 它對應的C++代碼如下:

class Shape { public: Shape(double x, double y) { this->x = x; this->y = y; } virtual double area() const { return 0; } double x; double y; }; class Circle : public Shape { public: Circle(double x, double y, double radius) : Shape(x, y) { this->radius = radius; } double area() const { return M_PI * radius * radius; } double radius; };

所以, 我們看到了, 對於一個javascript對象來說, 它還包括了一個內部的prototype對象. 對於Qt要用C++來實現類似prototype的功能的話, 除了要寫一個javascript中的對應類, 還要寫這個類對應的prototype類. 這個東西很高級, 也很麻煩, 所以建議看官方文檔: http://doc.trolltech.com/4.3/qtscript.html#making-use-of-prototype-based-inheritance

下面我們來說一下一般怎樣從Qt的C++代碼中調用Qt的script代碼. 假設我們要寫一個dialog, 上面有一個QPushButton, 一個QLineEdit. 點擊QPushButton的時候, 會彈出一個QMessageBox來顯示消息.

a) 直接寫Qt的C++代碼的話, 只要用signal/slot就行了:

void HelloDialog::helloClicked() { QString text = textEdit->text(); text = "Hello " + text; QMessageBox::information(this, "info", text); }

b) 現在我們要加入javascript 的支持. 要解決的大概有這么一些問題: javascript中怎么拿到QLineEdit里的字符串? javascript中怎么調用QMessage這個Qt的類? 我們還是先來看代碼:

QScriptValue constructQMessageBox(QScriptContext *, QScriptEngine *engine) { return engine->newQObject(new QMessageBox()); } void HelloDialog::helloClicked() { QString helloScript = scriptsDir.filePath("xxx.js"); QFile file(helloScript); if (!file.open(QIODevice::ReadOnly)) { return; } QTextStream in(&file); in.setCodec("UTF-8"); QString script = in.readAll(); file.close(); // section 1 QScriptEngine engine; QScriptValue helloDialog = engine.newQObject(this); engine.globalObject().setProperty("helloDialog", helloDialog); // section 2 QScriptValue constructor = engine.newFunction(constructQMessageBox); QScriptValue qsMetaObject = engine.newQMetaObject(&(QMessageBox::staticMetaObject), constructor); engine.globalObject().setProperty("QMessageBox", qsMetaObject); QScriptValue result = engine.evaluate(script); }

我們先把整個javascript文件讀進來, 加入一堆設置, 最后調用QScriptEngine::evaluate()函數來執行這段javascript. QScriptEngine這個類就相當於javascript的解釋器.
    javascript里沒有類這個概念, 所有的變量都是var類型. 如果要讓Qt的C++類在javascript里運行, 那么先要將它包裝(wrap)成一個javascript的類型. 代碼的section 1部分把this(即當前的dialog)先做了包裝, 然后把包裝后的對象加入到javascript的運行環境的全局變量中.
    接着來解決QMessageBox的問題. 由於javascript中沒有類, 繼而也就是沒構造函數這個概念, 但是當我們在javascript中new一個Qt C++對象的時候, 還是需要調用它的構造函數. 代碼的section 2部分先把一個C++回調函數(之所以稱為回調函數, 是因為要作為QScriptEngine::newFunction()的參數, signature是固定的)包裝成一個QScriptValue, 然后把它和QMessageBox的meta-object信息一起包裝成一個QScriptValue, 最后依樣畫葫蘆地加入到javascript的運行環境的全局變量中. 這樣我們就能在javascript中new出一個QMessageBox了.
    有一個很重要問題. 就是Qt的meta-object系統和javascript的調用系統是有對應關系的. 在javascript中, 一個var如果是QObject包裝而來, 那么這個QObject的所有property(Q_PROPERTY聲明), signal/slot都是可以在javascript中調用的. 還有就是這個QObject的所有child (指的是包含而不是繼承關系), 也是可以直接訪問的.
    看一下javascript代碼. 其中greeting和text都是屬性:

function showMessage(parent, title, text) { var messageBox = new QMessageBox; messageBox.windowTitle = title; messageBox.text = text; messageBox.icon = QMessageBox.Information; return messageBox.exec(); } return showMessage(helloDialog, "info", helloDialog.greeting + " " + helloDialog.text);

c) 我們實現了用javascript來控制邏輯. GUI的話, Qt也提供了一種可以直接讀取*.ui的方法: QUiLoader::load()函數. 於是我們連GUI也可以不用直接編譯到binary里去了. 我們要做的就是用Qt的C++代碼搭一個大概的框架, 加載需要的*.ui, *.js文件, 在適當的時候調用適當的javascript函數就行了. 而且*.ui文件對於每個控件都會有一個objectName的屬性, 用uic生成代碼的話, 這個值就是變量名, 如果用QUiLoader::load()的話, 這個就被賦給了QObjectobjectName這個property. 當我們要在一個QWidget的javascript對象里引用它的子控件的時候, 便能直接用這個objectName來引用. 於是*.ui 和*.js文件可以說簡直配合的天衣無縫那.
    還是來看代碼, Qt的C++代碼沒什么好說的, 就看javascript代碼:

function showMessage(parent, title, text) { var messageBox = new QMessageBox; messageBox.windowTitle = title; messageBox.text = text; messageBox.icon = QMessageBox.Information; return messageBox.exec(); } function doClick() { var text = dialog.textEdit.text; showMessage(dialog, "info", "Hello " + text); } dialog.show(); dialog.helloButton.clicked.connect(doClick); return 0;

直接訪問子控件是不是清爽多了? 呵呵. 代碼見這里. 其它請參考官方文檔:
*) http://doc.trolltech.com/4.3/ecmascript.html
*) http://doc.trolltech.com/4.3/qtscript.html

 

http://ju.outofmemory.cn/entry/150623


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM