Sending JavaScript Object QML signal parameter - c++

I am trying to connect a QML signal to a Qt slot with following parameter types:
in QML side:
signal Sig(var info)
in Qt side:
QObject::connect(topLevel, SIGNAL(Sig(QVariantMap)), &mObj, SLOT(mSlot(QVariantMap)));
Which gives me the following:
QObject::connect: No such signal QQuickWindowQmlImpl_QML_24::Sig(QVariantMap) in ...
So, I assume that types var and QVariantMap does not match. According to this document, QVariantMap types are converted to JavaScript objects. But I am not sure if it also does the other way around.
I have implemented an opposite type of connection(Qt signal with QVariantMap, QML handler with "Connections" element) which worked just fine. I was able to get the signal's argument as a JS object.
By the way, I have also tried the same connection with string argument types in my code, so I don't think that there is another unrelated mistake in my code.
How do I pass JS objects to Qt(C++) side using signal/slot mechanism? I haven't been able to find a solution. Not even an example that matches my case(signal and slot argument types), actually. Which makes me think that I am doing some sort of design mistake.

The parameters are QVariants on C++ side, so you need to do
QObject::connect(topLevel, SIGNAL(Sig(QVariant)), &mObj, SLOT(mSlot(QVariant)));
Note that you also need to change mSlot parameter type, because QVariant can't be implicitly converted to QVariantMap. To get the map in the slot, use QVariant::toMap() method, if it indeed is a map (if it isn't, this method returns empty map, and you need to do some debugging).
It works the other way around, because QVariantMap can be implicitly converted to QVariant.

Related

Qt connect doesn't recognize with lambda expression

I'm designed a QTableWidget with QPushButton, I would like to connect these buttons with a slot to hide some rows.
I'm using a lambda expression to pass a number of a row. But the compiler doesn't recognized this expression :
connect(this->ui->tableWidget->cellWidget(i,0),&QPushButton::clicked,[this,i]{hideRows(i);});
I have this error:
error: no matching function for call to 'SoftwareUdpater::MainWidget::connect(QWidget*, void (QAbstractButton::*)(bool), SoftwareUdpater::MainWidget::displayTable()::<lambda(int)>)'
The function hideRows(int) is declared as a function. And, as a slot, it doesn't work,
CONFIG += c++11 is added in pro file,
My class MainWidget inherits from QWidget,
Q_OBJECT is added in the header.
So I don't udnerstand why connect() is not recognized by Qt 5.9.1 MinGw 32bit.
Edit: [this,i]() instead of [this](const int i) for the lambda expression
Your connection is wrong. You can't connect a function that doesn't take parameters (clicked()) with a function that takes parameters (your lambda). To verify that this is the case, just do this:
connect(this->ui->tableWidget->cellWidget(i,0),&QPushButton::clicked,[this](){});
And see that it will compile. You have to make your design in such a way that signals and slots are compatible.
Also avoid using lambdas in signals and slots. Read the caveats here.
I was reading your comments on the accepted answer and noticed the root of the problem: This error is being thrown because the effective type of the object — as supplied to QObject::connect; i.e QWidget in your case — does not define the referenced signal QPushButton::clicked.
What likely happened is that the QPushButton pointer was cast into a QWidget and then that pointer was given to connect instead of the original which defines the signal.
Cast the pointer back to a QPushButton * and the error should go away.

Sending a list of strings from QML to C++

I have ListModel which I manage in QML. After editing I want to send this model to C++ (as a QStringList for example, or something else I can work with in c++).
I know I can expose a list from C++ to qml and edit it from there, but for my app logic that does not really make sense. Can I somehow create a list like datatype in QML which I can send to c++ with a signal?
I think you can just send the QStringList to the C++ code and it ill be handled. See the docs linked below.
string QML Basic Type
When integrating with C++, note that any QString value passed into QML
from C++ is automatically converted into a string value, and
vice-versa.
Data Type Conversion Between QML and C++
Sequence Type to JavaScript Array
Certain C++ sequence types are supported transparently in QML as
JavaScript Array types.
In particular, QML currently supports:
QList<int>
QList<qreal>
QList<bool>
QList<QString> and QStringList
QList<QUrl>
If you for some reason QVariants to send data (as I do), you can convert it the following way:
// Test if the value is of the correct type (not nessary of one is sure that it is a list of strings)
if (val.canConvert<QStringList>() && val.convert(QVariant::StringList))
{
return val.toStringList();
}

How do I cast QML items to corresponding C++ Item in Qt Quick

My question is best clarified by an example. I have QML with a Text{} item. In C++ I can get to this item and I have no problem using qobject_cast to turn anything into a QQuickItem*. But how do I turn it into the closest corresponding item so that I can call more specific methods directly like setText() the same way I might call setWidth()? I realize I can use the generic setProperty() method but I'm after the compile time checking that casting offers.
I'm after a more general answer for finding the correspondence between QML and their C++ classes, so that I can find out how to do this for Rectangles, MenuBars etc. I can't seem to find this in the docs. For those that prefer code examples:
auto text_object = app_item->findChild<QObject*>("myTextArea");
text_object->setProperty("text","New Text set from Code"); //THIS WORKS BUT...
auto text_qitem = qobject_cast<QQuickItem*>(text_object);
text_qitem->setWidth(128);
auto text_quick_text = qobject_cast<WHATGOESHERE???*>(text_object);
text_quick_text->setText("new Text for qml item"); //I WANT TO DO THIS
Q: but I'm after the compile time checking that casting offers.
qobject_cast does not offer any compilation-time checking. It is all runtime and dynamic, thus this request is not plausible. The context property is fine, or you could also get the class name with QMetaObject. Then, you could build a static LUT, but the effort may not be worth it overall...
All QML properties and methods are exposed to the meta-object system and can be called from C++ using Object::setProperty and QMetaObject::invokeMethod() respectively. invokeMethod parameters and return values passed from QML are always translated into QVariant values in C++:
QString msg("That's it");
auto text_object = app_item->findChild<QObject*>("myTextArea");
if (text_object)
QMetaObject::invokeMethod(text_object, "append", Q_ARG(QString, msg));

Map QWidget to variable

The idea was to connect QWidget with a variable so that when text changes on a widget it will be also changed in a variable.
And do this with just one line like this
WidgetMapper::connect(ui->lineEdit, SIGNAL(textChanged(QString)), someClass.var);
which would connect for example QLineEdit with a variable.
1) This would display var in a lineEdit
2) when lineEdit fires an textChanged(QString) signal - WidgetMapper would convert this QString to correct mapped type with stringstream and write it to var.
But i dont really know templates that well, and dont know if it is possible at all.
I dont think it is possible to use one WidgetMapper for every type, so i also tried creating separate instances for each type (WidgetMapper<int> mapper;) which would still be betten then writing setters and onTextChangedSlots for each QLiteEdit but i could not figure out how to make it work as well (converter part still could not figure out the correct type).
WidgetMapper is using QSignalMapper to map signal to QWidget, and it worked fine, the part i have troubles with - is converting QString to template variable.
So is it possible? And if yes how could i do this? Or maybe there already a solution for this problem? (Somehow use QDataWidgetMapper with a class that contains variables maybe?)
First of, connecting the variable would do nothing else than calling some function if it were possigle.
Second try using QSignalMapper, this way you could use a single slot for all widgets, given you keep their pointers in an array with the index being the signal(int) emitted by the SignalMapper. This way your slot can just use MyWidgetArray[i]->text().

Qt slot with default argument

I have a block of spin controls which change individual elements of an array
Rather than having separate receiver slot functions, I wanted to just specify which control sent the message in the signal
You can do this with a QSignalMapper - but is there anyway of doing it simply as below?
spin0 = new QDoubleSpinBox;
connect(spin0,SIGNAL(valueChanged(double)),this,SLOT(handler(0,double));
spin1 = new QDoubleSpinBox;
connect(spin1,SIGNAL(valueChanged(double)),this,SLOT(handler(1,double));
....
private slot:
void handler(int element,double value);
From any slot handler you can can use sender() to get a pointer to the object that sent the signal. Then you can use the objectName() property to communicate any further identifying information.
I don't believe so, at least not using that syntax ... the SIGNAL and SLOT macros turn their arguments into strings which are then parsed and used by the Qt runtime to look-up the associated functions and/or class methods in the tables created by moc during the pre-processing phase of compilation. So if you encoded a default argument into the SLOT macro, then that's not a valid function signature that can be used by Qt for run-time lookup of the actual slot function in the moc-generated function tables.