1. 程式人生 > >Qt 5.5 中Qt Script翻譯 (四)

Qt 5.5 中Qt Script翻譯 (四)

接著上一章

讓C++類的屬性在指令碼中有效

在上一個例子當中,如果我們想在Qt指令碼中set或get屬性,我們需要像下面這樣寫程式碼:

var obj = new MyObject;
obj.setEnabled( true );
print( "obj is enabled: " + obj.isEnabled() );

為了使這成為可能,您必須定義C++ QObject子類中的屬性。例如,下面的MyObject類宣告聲明瞭一個名為enabled的布林屬性,它使用函式setEnabled(bool)作為它的setter函式,使用isEnabled()作為它的getter函式:

class MyObject : public QObject
{
    Q_OBJECT
    // define the enabled property
    Q_PROPERTY( bool enabled WRITE setEnabled READ isEnabled )

public:
    MyObject( ... );

    void aNonScriptableFunction();

public slots: // these functions (slots) will be available in Qt Script
    void calculate( ... );
    void setEnabled( bool enabled );
    bool isEnabled() const;

private:
   ....

};

與原始程式碼的唯一區別是使用了巨集Q_PROPERTY,它接受屬性的型別和名稱,以及setter和getter函式的名稱作為引數。

如果不希望類的屬性在Qt Script中可訪問,則在宣告屬性時將SCRIPTABLE屬性設定為false;預設情況下,SCRIPTABLE屬性為true。例如:

Q_PROPERTY(int nonScriptableProperty READ foo WRITE bar SCRIPTABLE false)

在指令碼中響應C++物件訊號

在QT物件模型中,訊號被用作QObjts之間的通知機制。這意味著一個物件可以將訊號連線到另一個物件的slot,並且每當發出訊號時,就呼叫slot。使用QObject::connet()建立連線。

QT指令碼程式也可以使用訊號和槽機制。在C++中宣告訊號的程式碼是相同的,不管訊號是否將連線到C++中的slot或QT指令碼中。

class MyObject : public QObject
{
    Q_OBJECT
    // define the enabled property
    Q_PROPERTY( bool enabled WRITE setEnabled READ isEnabled )

public:
    MyObject( ... );

    void aNonScriptableFunction();

public slots: // these functions (slots) will be available in Qt Script
    void calculate( ... );
    void setEnabled( bool enabled );
    bool isEnabled() const;

signals: // the signals
    void enabledChanged( bool newState );

private:
   ....

};

我們對前一節中的程式碼所做的唯一更改是宣告一個帶有相關訊號的訊號部分。現在,指令碼編寫器可以定義一個函式並連線到這樣的物件:

function enabledChangedHandler( b )
{
    print( "state changed to: " + b );
}

function init()
{
    var obj = new MyObject();
    // connect a script function to the signal
    obj["enabledChanged(bool)"].connect(enabledChangedHandler);
    obj.enabled = true;
    print( "obj is enabled: " + obj.enabled );
}

定義應用程式物件

之前的章節講解的是怎樣在腳本當中使用C++物件,應用程式物件是同一類物件,它們使應用的功能在指令碼程式中可以使用,由於C++程式已經編寫在Qt中,很多物件已經是QObject物件,最簡單的方法是簡單地將所有這些QObject作為應用程式物件新增到指令碼引擎。對於小型應用程式,這可能就足夠了,但是對於大型應用程式,這可能不是正確的方法,問題在於,這種方法暴露太多的內部API,並且讓指令碼程式訪問不應該訪問的內部屬性。

通常,使應用程式功能對指令碼程式可用的最佳方法是編寫一些QObject,這些QObject使用訊號、插槽和屬性定義應用程式公共API。這使您完全可以控制應用程式提供的功能。這些物件的實現只是呼叫應用程式中執行實際工作的函式。因此,不要讓指令碼引擎可以使用所有的QObject,只需新增包裝器QObjects。

返回QObject指標

如果有一個槽返回QObject指標,則應該注意,預設情況下,Qt Script只處理型別QObject*和QWidget*的轉換。這意味著,如果用像“MyObject*getMyObject()”這樣的簽名宣告插槽,Qt Script不會自動知道MyObject*應該以與QObject*和QWidget*相同的方式處理。解決這個問題的最簡單的方法是隻在指令碼介面的方法簽名中使用QObject*和QWidget*。

或者,可以使用qScriptRegisterMetaType()函式註冊自定義型別的轉換函式。這樣,您就可以保留C++宣告中的精確型別,同時仍然允許指向自定義物件的指標在C++和指令碼之間無縫地流動。例子:

class MyObject : public QObject
  {
      Q_OBJECT
      ...
  };

  Q_DECLARE_METATYPE(MyObject*)

  QScriptValue myObjectToScriptValue(QScriptEngine *engine, MyObject* const &in)
  { return engine->newQObject(in); }

  void myObjectFromScriptValue(const QScriptValue &object, MyObject* &out)
  { out = qobject_cast<MyObject*>(object.toQObject()); }

  ...

  qScriptRegisterMetaType(&engine, myObjectToScriptValue, myObjectFromScriptValue);

函式物件和本地函式

在Qt指令碼中,函式是第一型別,它們是物件可以有自己的屬性,就像其他物件一樣,它們可以儲存在變數當中,也可以作為引數傳遞到其他函式中,當你想定義和使用自己的指令碼函式時,瞭解Qt指令碼中的函式行為是非常有用的。本節討論這個問題,本解釋怎樣實現本機函式,也就是說,用c++編寫的Qt指令碼函式,而不是用指令碼自己編寫的指令碼函式,即使您主要依賴於Qt Script提供的動態QObject繫結,瞭解這些強大的概念和技術對於理解在執行指令碼函式時實際發生的情況也很重要。

在C++中呼叫一個Qt指令碼函式

在C++中獲取一個指令碼函式並通過QScriptValue::call()呼叫該函式,一個典型的場景是評估一個定義函式的指令碼,在某個時候,您希望從C++呼叫該函式,或者傳遞一些引數,然後處理結果。下面的指令碼定義了一個具有ToKelVin()函式的Qt指令碼物件:

({ unitName: "Celsius",
     toKelvin: function(x) { return x + 273; }
   })

toKelvin()函式以Kelvin中的溫度作為引數,並返回轉換為攝氏度的溫度。下面的程式碼片段顯示瞭如何從C++中呼叫ToKelvin()函式:

QScriptValue object = engine.evaluate("({ unitName: 'Celsius', toKelvin: function(x) { return x + 273; } })");
  QScriptValue toKelvin = object.property("toKelvin");
  QScriptValue result = toKelvin.call(object, QScriptValueList() << 100);
  qDebug() << result.toNumber(); // 373

如果指令碼定義了一個全域性函式,則可以訪問該函式作為QScriptEngine::globalObject()的屬性。例如,下面的指令碼定義了一個全域性函式add():

 function add(a, b) {
      return a + b;
  }

C++程式碼可以像下面這樣呼叫add()函式:

QScriptValue add = engine.globalObject().property("add");
  qDebug() << add.call(QScriptValue(), QScriptValueList() << 1 << 2).toNumber(); // 3

如前所述,函式只是Qt Script中的值;函式本身並不“繫結”特定的物件。這就是為什麼您必須指定一個應用該函式的物件(QScriptValue::call()的第一個引數)。

如果函式應該充當一個方法(即,它只能應用於某一類物件,函式中有this),則由函式本身來檢查它是否被與該物件相容地呼叫。

將無效的QScriptValue()作為這個引數傳遞給QScriptValue::call()表明應該使用全域性物件作為這個物件;換句話說,應該將函式作為全域性函式呼叫。