如何使Q_INVOKABLE方法从QML中获取可变数量的参数

时间:2018-09-19 13:54:27

标签: qt qml

众所周知,在JavaScript中,所有函数都可以采用任意数量的参数。 在Qt中,您可以使QObject(其方法可以从QML访问),并用Q_INVOKABLE进行标记。例如:

class myObj: public QObject
{
    Q_OBJECT

//...

public slots:
    Q_INVOKABLE QJSValue myFunction(QJSValue value);

//...

};

然后您可以从JS调用它:

(function(){
    //like this:
    var result = myObj.myFunction("test");
    //but also like this:
    var result2 = myObj.myFunction(1,2,3,4,5); //,6,7,8, ..., 9998, 9999
})();

那么,如何在C ++端处理可变数量的参数?在JS方面,我们有“ arguments”对象。有Q_INVOKABLE方法的模拟物吗?

3 个答案:

答案 0 :(得分:1)

用正常的方式似乎是不可能的。现在,我已经解决了。 QJSValue可以包含任何JS类型。包括数组。所以方法看起来像这样:

QJSValue myObject::test(QJSValue arg1, QJSValue arg2, QJSValue rest)
{
    qDebug() << "arg1 = " << arg1.toString();
    qDebug() << "arg2 = " << arg2.toString();
    auto args = toJSValueList(rest);
    qDebug() << "args = [";
    for(auto arg : args) {
        qDebug() << "  " << arg.toString() << ",";
    }
    qDebug() << "]";
    return (arg1.isUndefined() ? 0 : 1) + (arg2.isUndefined() ? 0 : 1) + args.length();
}

参数arg1和arg2是必需的。其余参数可以作为第三个参数(数组)传递给方法。 toJSValueList方法只是将包含数组的QJSValue转换为QJSValueList的助手。

QJSValueList myObject::toJSValueList(QJSValue arg)
{
    QJSValueList list;
    auto length = arg.property("length");
    if(length.isNumber()){
        for(int i = 0, intLength = length.toInt(); i < intLength; ++i){
            list << arg.property(static_cast<quint32>(i));
        }
    } else if(!arg.isUndefined()){
        list << arg;
    }
    return list;
}

这足以使事情正常进行。但是,如果实际上有人需要将任意数量的参数直接传递给函数(而不是通过数组),则可以稍加修改即可。

class myObject: public QObject, public QmlSingletonProvider<QmlTimer>
{
    Q_OBJECT
public:
    //...
    // declare property of type QJSValue which will actually be a function 
    Q_PROPERTY(QJSValue variadic_test READ variadic_test)

public slots:
    Q_INVOKABLE QJSValue test(QJSValue arg1, QJSValue arg2, QJSValue rest);

private:
    QJSValueList toJSValueList(QJSValue arg);
    QJSValue variadic_test(); // getter for property
    QJSValue variadic_test_fn; // stored JS function
};


// variadic wrapper for  method test()
QJSValue myObject::variadic_test()
{
    if(variadic_test_fn.isCallable())
        return variadic_test_fn;

    auto engine = qjsEngine(this);
    if(!engine) return QJSValue();

    variadic_test_fn = engine->evaluate("function(){ return this.test( arguments[0], arguments[1], Array.prototype.slice.call( arguments, 2 ) ); }");

    return variadic_test_fn;
}

QJSValue也可以是一个函数。因此,您可以添加具有QJSValue类型的只读Q_PROPERTY,并在首次访问属性时为其分配JS函数。该函数将是一个包装器,该包装器仅将其所有参数收集到数组并将其传递给实际方法。在JS中,您可以看到并调用这两个函数:实际的一个和可变参数的包装器。在该示例中,我按原样传递了两个参数(如果参数数目小于2,则将传递“ undefined”。),因为方法“ test”等待至少2个参数。其余参数通过“ Array.prototype.slice.call(arguments,2)”收集到数组。如果没有必需的参数,则可以使用“ function(){return this.test(Array.prototype.slice.call(arguments));}”。 当然,您的C ++方法应该准备好可能的“未定义”值。并且每个参数的类型需要手动检查。即使它是用C ++编写的,它看起来也更像是“ JS代码”。

答案 1 :(得分:1)

对于每个QObject来说似乎都是不可能的,但是您可以使用自定义JS脚本包装您的单例C ++后端SLOT,该脚本将多个参数结合在一起形成单个数组。这可以通过here方法回调来完成,该回调提供对QJSEngine实例的访问。

QJSValue ApiProvider::initSingletonType(QQmlEngine *qmlEngine, QJSEngine *jsEngine) {
    qDebug() << "ApiProvider initSingletonType";
    Q_UNUSED(qmlEngine);
    QJSValue instance=jsEngine->newQObject(new ApiProvider(jsEngine));
    QJSValue restProvider=jsEngine->evaluate(
        "(function (instance) {" \
        "   instance.createElement=function(type,...child){" \
        "       return instance.createElementInternal(type,child);" \
        "   }; " \
        "   return instance;" \
        "})"
    );
    if (restProvider.isError()) {
        qCritical() << restProvider.toString();
        return QJSValue();
    } else {
        QJSValue result=restProvider.call({instance});
        if (result.isError()) {
            qCritical() << result.toString();
            return QJSValue();
        } else {
            return instance;
        }
    }
}

...


qmlRegisterSingletonType(
    "com.tripolskypetr.quitejs",
    1, 0,
    "Api",
    ApiProvider::initSingletonType
);

qmlRegisterSingletonType实现了条件jsx工厂,其中每个元素的rest参数中可以具有任意数量的后代。源代码已准备就绪。

sample

答案 2 :(得分:0)

您可以将QList<QVariant>作为C ++ Q_INVOKABLE函数的参数,并在qml端传递任何数组,例如["string", 1, 2, 3]QVariant可转换为大多数基本数据类型。