4

I have a Q_INVOKABLE method in C++ class and I want this method to return data back to QML, within the same method, without the use of signals.

My mehtod is declared as:

Q_INVOKABLE void select_company(int index,QString *out);

And defined as:

void Companies::select_company(int index,QString *out) {

    out->clear();
    out->append("out string");
}

When I call it from a JavaScript function in QML:

var out_str;
data_model.select_company(index,out_str);
console.log(out_str);

I get this output on the console:

qrc:/CompaniesList.qml:56: Error: Unknown method parameter type: QString*

Is it possible to pass a (JavaScript) variable from QML to C++ method, and have C++ to modify this variable ? If it is not possible to do that by passing a pointer or reference, what is the (simplest) other approach to do this?

The only way I have found so far is by passing a QJSValue and set properties to it, like this:

Declaration:

Q_INVOKABLE void select_company(int index,QJSValue out);

Definition:

void Companies::select_company(int index,QJSValue out) {

    out.setProperty("company_name","Acme, Inc.");
    out.setProperty("identity_id",29673);
}

QML:

var retval={};
data_model.select_company(index,retval);
console.log(retval.company_name);
console.log(retval.identity_id);

Would be interesting to find out all the possible ways to call a C++ method and return some data immediately.

dtech
  • 47,916
  • 17
  • 112
  • 190
Nulik
  • 6,748
  • 10
  • 60
  • 129
  • 1
    http://stackoverflow.com/questions/31618468/passing-a-javascript-callback-to-a-c-invoked-method-in-qml – Alexander V Dec 30 '16 at 06:12
  • @AlexanderVX , there is a missing validation in that code , you have to check if jsCallback.isCallable() and only after that you can call it. But, I do not want to declare functions, I want Qt to let me modify JavaScript values on the C++ side. – Nulik Dec 30 '16 at 15:30

2 Answers2

2

If you want to "pass by reference" and edit a JavaScript object on the C++ side, then QJSValue is the way to go. There is no direct support for C++ types out of the box, there are some conversions for specific types.

Passing as a pointer will only work for QObject derived objects.

Keep in mind that QJSValue will work only if it is an Object, maybe also an array. It will not work for other types, as the object will be copied, and changes will be done on the copy and will not be reflected on the original object.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • That's approach will work, what I don't like in it personally that it gets to the point there all methods in C++ are very 'abstract' with all same type parameters so it's hard to read, easy to make mistake.. but it definitely will work and sometimes helps =) – evilruff Dec 30 '16 at 06:58
0

Not that I mind, but what stops you from declaring something like:

Q_INVOKABLE QString getSomeValueFromCPP(int index) {
 ...
 return value; 
}

and then in QML:

var val = getSomeValueFromCPP(index);

For more complicated structures if you need to return something by-value I suggest to have a look on Q_GADGET approach, it's awesome.

P.S. as there are so many comments I decided to make a small update in here rather then in comments.

First of all, I want to give a credits to all ddriver comments as they are all completely right.

It's absolutely true that first thing to do is to review a design, as any QML/C++ interactions are not really consider such approach as mentioned in the initial question. Second is that it's very important to make a distinguish between ownership for QObject based classes and passed-by-value instances. In case you are going to use a QObject based classed (doesn't matter QML or C++ ownership) you should clearly decide who's going to be a creator of these objects. Also pay attention that any QObjects created in C++ code without an explicit parent and passed into QML engine will be destroyed at some point by garbage collector. Thats very flexible and powerful approach allows to create very efficient data structures like QObject-based models with full properties support etc.

To avoid complications Qt provides another way of dealing with structured data in case you need just to pass values into QML from C++ (as I understand a question that's what you are trying to achieve). That's Q_GADGETS. Thats a similar to Q_OBJECT way of attaching metadata to structures allowing them to have a Q_PROPERTIES, full MetaType information (so qobject_cast will work for example, as well as any QVariant transformations), but excluding SLOTS/SIGNALS.

Major difference is that you are allowed to make a pass-by-value invocations, where you should't bother (to some point) about ownership as well as live-cycle. In simple words in case of Q_GADGET return type QML will get a COPY of initial object and will destroy it then it's not needed.

As mentioned in ddriver answer it's possible to simulate reference parameter then calling C++ method from QML, but I have my doubts it's right way to go as from my point of view it's lack in design.

In your question you are asking about certain technique, but point is that - proper answer is to review a design as what you are trying to do is not how it's supposed to work. Again as ddriver mentioned you should look on C++ part as on "core" providing data in certain way - it can be QObject based models/standalone objects or value-based instances based on Q_GADGET, while QML part is a "representation" logic, it shouldn't be used to create/hold something which C++ suppose to modify, especially by reference.

Ragaring Nulik comments - typical use case is to return Q_GADGET based classes into QML from C++. You shouldn't try to pass those values by reference for modifications inside C++ code.

evilruff
  • 3,947
  • 1
  • 15
  • 27
  • The idea here is to modify the original parameter object from C++, it is just not property worded. – dtech Dec 30 '16 at 06:33
  • Q_INVOKABLE QString getNewVal(index, oldVal); it's either by-value (which I think most clean way) or QObject and properties change.. you overwrite old value, so I don't see a point not to use return type.. – evilruff Dec 30 '16 at 06:35
  • What if you want to modify several parameters? You cannot return multiple values. – dtech Dec 30 '16 at 06:36
  • 2
    you can return something like typedef struct { Q_GADGET int v1; QString v2;...}; it's really lightweight implementation, and looks very clean in the code. – evilruff Dec 30 '16 at 06:37
  • Not to QML you can't. `Q_GADGET` will get you meta information to access an already existing object's members, but you still cannot create those and use them directly in QML. They have to be created from C++ and put into a property of a C++ object with that type. – dtech Dec 30 '16 at 06:40
  • 1
    You can, just need to use qRegisterMetaType(); For sure you can return them from Q_INVOKABLES.. – evilruff Dec 30 '16 at 06:40
  • Also, `Q_GADGET` doesn't work "by magic", you still need to implement a `Q_PROPERTY` for everything you want to see in QML. `qRegisterMetaType()` will allow you to pass that object as a parameter, but not to create it in QML. What does that is `qmlRegisterType()` which works **only** for `QObject` derived classes. – dtech Dec 30 '16 at 06:43
  • Depends what you need to do.. it's more design discussion.. idea of Q_GADGET is a way to pass something more complicated then a basic type by value.. through Q_PROPERY or by just return type.. at then end of the day Q_PROPERTY is just a getter and setter with some meta methods around.. I agree that you can't instantiate an object of Q_GADGET type in the QML, but you can perfectly return it from Q_INVOKABLE or through Q_PROPERTY.. – evilruff Dec 30 '16 at 06:48
  • 1
    I think I understood what you mean =) sorry, early start today, of course you need to define Q_PROPERTIES _inside_ Q_GADGET implementation, but that's obvious I believe.. – evilruff Dec 30 '16 at 06:55
  • @evilruff but if you return a who is going to destroy the object after QML consumes it? I thought objects created on the C++ side have to be destroyed by C++ , otherwise you will get a memory leak. – Nulik Dec 30 '16 at 15:23
  • @evilruff also, what is the point of using exactly Q_GADGET, I also can use Q_OBJECT macro. What is so special in Q_GADGET besides that it is lightweight? – Nulik Dec 30 '16 at 15:24
  • @evilruff can you make an example of how are you using Q_GADGET in your code to pass values from C++ to QML ? – Nulik Dec 30 '16 at 15:31
  • Returning is not the same as passing a modifiable reference. `Q_GADGET` objects themselves cannot be passed by reference, only by value. If you use a return value you won't be modifying the original object by replacing it with a new one. When you return, it is the function call operator that creates the object instance, when you pass by reference you use that already created object instance directly. – dtech Dec 30 '16 at 16:03
  • Ownership applies only to `QObject`s, as they are passed by reference in the engine, the rest is passed by value, which is also why registering a metatype requires it to have a copy constructor, default for returning a `QObjecf` from C++ to QML is QML ownership, which has been known to be buggy in some cases and destroy objects while they are still in use. QML cannot manage the lifetime of non-`QObject`s - they must directly be put in an appropriately typed property, they cannot exist on their own as objects in QML. – dtech Dec 30 '16 at 16:04
  • Lastly, having to do such things from C++ is already indicative of bad API design. C++ and QML Qt are not directly interchangeable, although there exist some automatic conversions for most common data types, those are two completely different worlds which are separated by a layer, for which there is only one correct way to traverse - through a well defined API interface. Think of C++ as the "core" and of QML as the peripherals, keep the core compact and well defined, and do small or "surface" operations in QML, and life will be great. Don't get ahead of yourself and fall victim to poor design. – dtech Dec 30 '16 at 16:13
  • Answer updated, as it would be to long to put in comments – evilruff Dec 30 '16 at 21:00