How can I test a QAbstractListModel via Qt Quick Test? - c++

I have written a QAbstractListModel class to expose data from my library to QML. It seems to work correctly together with the QML ListView component, but I'd like to write some tests for it to ensure it continues to behave.
I've been testing other parts of my QML module via Qt Quick Test, but couldn't work out how to access the model directly from QML. I'm after simple things like checking the row count, and accessing role data values for arbitrary rows in the model.
Is this actually possible, or would I need to write the tests in C++?

This is a subject that I always have to look up myself, and I'm still a bit unsure, so there may be better ways than what I suggest. Anyway, it seems that QAbstractItemModel doesn't provide the functions that you're trying to test. I can think of two ways to get around that.
Add them yourself
#include <QtGui/QGuiApplication>
#include <QQmlContext>
#include <QQuickView>
#include <QDebug>
#include "qtquick2applicationviewer.h"
#include "QAbstractListModel"
class Model : public QAbstractListModel
{
Q_OBJECT
public:
Model() {}
int rowCount(const QModelIndex &parent) const
{
return mList.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const{
if (index.row() < 0 || index.row() >= mList.size()) {
return QVariant();
}
return mList.at(index.row());
}
Q_INVOKABLE QVariant get(int index) {
return data(createIndex(index, 0));
}
Q_INVOKABLE void append(QVariant element) {
beginInsertRows(QModelIndex(), mList.size() + 1, mList.size() + 1);
mList.append(element.toMap().value("name").toString());
endInsertRows();
}
private:
QList<QString> mList;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QtQuick2ApplicationViewer viewer;
Model model;
viewer.rootContext()->setContextProperty("model", &model);
viewer.setMainQmlFile(QStringLiteral("qml/quick/main.qml"));
viewer.showExpanded();
return app.exec();
}
#include "main.moc"
Here's a small hack of a QML test case:
import QtQuick 2.2
Item {
width: 360
height: 360
Component.onCompleted: {
model.append({name: "blah"});
console.assert(model.get(0) === "blah");
}
}
Use QQmlListProperty
This is only useful if your model is or can be wrapped by a QObject subclass, as it is, as its name suggests, used as a property of an object. It's useful for it's convenience, but is less flexible than the first option, as it can only store QObject-derived object pointers. I won't go into details on this, as I'm assuming that this is not relevant for your use case.
You can find more information below:
Using C++ Models with Qt Quick Views

I know this topic is old, but anyway here is your answer.
Once your QAbstractListModel is defined on cpp side, you access delegate's roles via delegate's property:
import QtQuick 2.5
import QtTest 1.0
Item {
width: 1 // the minimum size of visible compoment
height: 1 // the minimum size of visible compoment
ListView {
id: testView
model: yourModelId
delegate: Item {
property variant dataModel: model
}
}
TestCase {
when: windowShown
function test_dataModelReadDelegate() {
testView.currentIndex = 0
testView.currentItem.dataModel["yourRole"]);
}
}
}
Be careful, ListView must be visible (also TestCase waits until windowShown is true), otherwise the delegates will not be created (TestCase itself is invisible component).
And for some reason, when the root Item has no size, I'll get warning about invalid component size.

Related

Passing a pointer to QML string property to C++

How do you pass a pointer to string property from QML to C++?
When I attempted to do it the obvious way, I got this error:
qrc:/NewAccount.qml:236: Error: Unknown method parameter type: QString*
Which means, QML engine can't convert new_account.error_string property to C++ when calling save_account (Q_INVOKABLE) method
This is my code in QML:
import myproject.aewm.ethkey 1.0
import myproject.aewm.walletaccount 1.0
...
id: new_account
property EthKey key: EthKey{}
property WalletAccount account: WalletAccount{}
property string error_string: ""
....
if (aewm_obj.save_account(key,account,new_account.error_string)) {
wallet_accounts_tabbar.currentIndex=0
} else {
console.log("error occurred:"+new_account.error_string)
}
Where aewm_obj is a C++ type (registered in QML) and save_account is declared in C++ as :
Q_INVOKABLE bool save_account(EthKey *key, WalletAccount *account, QString *err_str);
The docs say that a (QML) string property is a QString object, and that these types are automatically converted from from C++ and QML . I am passing pointers to my custom QObject-derived classes without any problems after qmlRegisterType() call, so why I can't do the same with strings?
I thought that maybe string pointers are not supported and I tried to add this line:
qRegisterMetaType<QString*>("QString*");
but after this change, the pointer I received at C++ side was 0x0 and I got a segfault.
So, how do you pass pointers to QML string properties from QML to C++ ?
Or, do you think I should register QString class with qmlRegisterType() too? I tried it but I have encountered some compilation issues, so I think it won't compile.
The last solution would be to create a custom object with a QString inside, and send a pointer to it from QML to C++. But, this would be an overkill, if QString exists why not find a way to use it?
Will appreciate very much your comments. I definitely want to use pointers , it is safer than dealing with object ownership when exchanging data between C++ and QML.
As I said in the comments, QML only passes pointers from the QObjects, and QString is not a QObject.
I think you are giving an incorrect approach to the problem, you could create a property in the object that performs the calculations that have the error message as shown below.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
class Foo: public QObject{
Q_OBJECT
Q_PROPERTY(QString errorMessage READ errorMessage)
public:
using QObject::QObject;
Q_INVOKABLE bool process(int a, int b, int res){
bool status;
// some operation
status = (a+b) == res;
mErrorMessage = status? "": QString("error message: %1 + %2 is different to %3").arg(a).arg(b).arg(res);
return status;
}
QString errorMessage() const{
return mErrorMessage;
}
private:
QString mErrorMessage;
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Foo>("com.eyllanesc.org", 1, 0, "Foo");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import com.eyllanesc.org 1.0
Window {
visible: true
width: 640
height: 480
Foo{ id: foo }
Row{
SpinBox{ id: a }
SpinBox{ id: b }
SpinBox{ id: c }
Button{
text: "process"
onClicked: {
var message;
if(foo.process(a.value, b.value, c.value)){
message = "correct"
console.log("successful")
}
else{
message = foo.errorMessage
console.log("error is "+ message)
}
txt.text = message
}
}
Label{ id: txt }
}
}
With Qt, the C++ API and the QML API are completely different, and practically incompatible layers. There is a lot of conversion of data back and forth in order to make the whole thing work. And you don't really have control over it when it comes to primitives like strings. So just get that idea out of your head.
If you want to access a particular property of a particular object, you need to pass the object that will be received as a pointer, and the property name, which then you can access via its name string via QObjects property() and setProperty().
But in your case that is entirely redundant, simply pass the string itself.

How to catch C++ signal in QML signal handler after type registration?

I am developing a basic qml-cpp application to understand how one interacts with another. I have a MessageSetter C++ class and one main.qml. Since I wish to understand two-way communication, I exposed MessageSetter properties to qml using setContextProperty and also registered MessageSetter class with qml (instantiable registration). Exposed properties work fine. Now when a qml button is clicked, then the signal (qmlBtnClicked) is successfully caught in a MessageSetter slot(onQmlButtonClicked). This slot further emits another MessageSetter signal (colorChanged). This new (C++) signal should be caught in qml registered MessageSetter's signal handler (onColorChanged) but it does not arrive here in any case. Below is main.cpp code:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<MessageSetter>("com.SkillLotto.MessageSetter", 1, 0, "SetMessage");
MessageSetter data;
engine.rootContext()->setContextProperty("msgSetter", &data);
QQmlComponent component(&engine, QUrl::fromLocalFile("main.qml"));
QObject *object = component.create()->findChild<QObject*>("setTextBtn");
QObject::connect(object, SIGNAL(qmlBtnClicked()), &data, SLOT(onQmlButtonClicked()));
return app.exec();
}
This is MessageSetter slot that emits another signal:
void MessageSetter::onQmlButtonClicked()
{
emit colorChanged("red");
}
This is qml code, this signal handler never gets called:
SetMessage{
onColorChanged: {
rect.color = color //rect is some rectangle in this file.
}
}
As I stated, qml signal is successfully caught in C++ slot but I am unable to catch this C++ signal in qml signal handler. Any help please.
This question, as I see, is focussed on qmlRegisterType() and should not be duplicate of this question? I also want to know whether qmlRegisterType() and setContextProperty() cant be used simultaneously or not ?
I think your code should work well.
I don't have the whole code so I don't know if you have the right methods implemented.
In order to get the signal using qmlRegisterType you need some requirements. Check if you have the Q_PROPERTY macro call implemented. Any property that is writable should have an associated NOTIFY signal that is emitted whenever the property value has changed.
If so, when you change the color property in the SetMessage component, the signal onColorChanged should be triggered.
Here you have an example where two signals are emitted: the first one when the size property is updated and the second one if the C++ mouseClick method is called using a MouseArea.
By the way, you don't need setContextProperty to integrate your C++ class with QML. qmlRegisterType should be enough. Or vice versa, depending on your needs. You can use both, but then you will have two different objects to work with. It really depends on what you want to achieve.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "customitem.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<CustomItem>("CustomItem", 1,0, "CustomItem");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
customitem.h
#ifndef CUSTOMITEM_H
#define CUSTOMITEM_H
#include <QObject>
class CustomItem: public QObject
{
Q_OBJECT
/*
* Any property that is writable should have an associated NOTIFY signal.
* Ref: http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html#exposing-properties
*/
Q_PROPERTY(int size READ size WRITE setSize NOTIFY sizeChanged)
public:
CustomItem(QObject *parent = 0);
int size() const;
void setSize(int);
Q_INVOKABLE void mouseClick();
private:
int m_size;
signals:
void sizeChanged(int size);
void clicked();
public slots:
};
#endif // CUSTOMITEM_H
customitem.cpp
#include "customitem.h"
#include <QDebug>
CustomItem::CustomItem(QObject *parent)
: QObject(parent), m_size(0)
{
}
int CustomItem::size() const
{
return m_size;
}
void CustomItem::setSize(int size)
{
m_size = size;
emit sizeChanged(m_size);
}
void CustomItem::mouseClick()
{
qDebug() << "CustomItem::mouseClick()";
emit clicked();
}
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
import CustomItem 1.0
Window {
visible: true
TextInput {
id: mySize
x: 0
y: 0
text: "100"
}
CustomItem {
id: customItem
size: mySize.text
onSizeChanged: console.log("size changed:", size)
onClicked: console.log("onClicked!")
}
Rectangle {
id: rect
x: 50
y: 50
width: customItem.size
height: customItem.size
color: "red"
MouseArea {
anchors.fill: parent
onClicked: { customItem.mouseClick() }
}
}
}
Because you are using two different instances of your MessageSetter, one is data in main.cpp and other is new instance SetMessage. Use only one to connect both signals/slots.
You are expecting onColorChanged signal from SetMessage but the signal is coming from data (in main.cpp).
Why do you need instantiable if you want to create a context property?
Add this in your main.qml file
Connections {
target: msgSetter
onColorChanged: {
console.log("received color changed signal");
}
}

QAbstractVideoSurface example

I'm trying to make myself a QML Camera item which has more functions, and also provide a source to the VideoOutput element. Such as this:
VideoOutput{
source:mycamera
}
MyCustomCamera{
id:mycamera
}
in the document it says
If you are extending your own C++ classes to interoperate with
VideoOutput, you can either provide a QObject based class with a
mediaObject property that exposes a QMediaObject derived class that
has a QVideoRendererControl available, or you can provide a QObject
based class with a writable videoSurface property that can accept a
QAbstractVideoSurface based class and can follow the correct protocol
to deliver QVideoFrames to it.
I have tried giving my object a private property mediaObject, which is of type QCamera, but looks like QCamera does not have a QVideoRenderControl (or its my fault not knowing how to do it correctly).
I need to achieve the effect I've shown in the beginning, anyway is welcomed.
Or otherwise can anyone give me a short example on what is meant by "a writable videoSurace property that accept blablabla and follow the correct protocol"?
I can't help you with your main concern but i can give you an example usage of the videoSurface.You can use the "writable videoSurface" like this:
My example consists of three main steps:
You write a class that has a QAbstactVideoSurface property. This class will be your video provider which can display frames on the VideoOutput via calling its present() function.
videoadapter.h
#ifndef VIDEOADAPTER_H
#define VIDEOADAPTER_H
#include <QObject>
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
#include <QTimer>
class VideoAdapter : public QObject
{
Q_OBJECT
Q_PROPERTY(QAbstractVideoSurface* videoSurface READ videoSurface WRITE setVideoSurface NOTIFY signalVideoSurfaceChanged)
public:
explicit VideoAdapter(QObject *parent = nullptr);
QAbstractVideoSurface *videoSurface() const;
void setVideoSurface(QAbstractVideoSurface *videoSurface);
signals:
void signalVideoSurfaceChanged();
private slots:
void slotTick();
private:
void startSurface();
private:
QAbstractVideoSurface *mVideoSurface;
QVideoSurfaceFormat *mSurfaceFormat;
QImage *mImage;
QTimer mTimer;
};
#endif // VIDEOADAPTER_H
videoadapter.cpp
#include "videoadapter.h"
#include <QDebug>
VideoAdapter::VideoAdapter(QObject *parent)
: QObject(parent), mVideoSurface(nullptr), mSurfaceFormat(nullptr)
{
mTimer.setInterval(1000);
connect(&mTimer, &QTimer::timeout, this, &VideoAdapter::slotTick);
}
QAbstractVideoSurface *VideoAdapter::videoSurface() const
{
return mVideoSurface;
}
void VideoAdapter::setVideoSurface(QAbstractVideoSurface *videoSurface)
{
if(videoSurface != mVideoSurface)
{
mVideoSurface = videoSurface;
emit signalVideoSurfaceChanged();
startSurface();
// This is the test timer that will tick for us to present the image
// on the video surface
mTimer.start();
}
}
void VideoAdapter::slotTick()
{
QVideoFrame frame(*mImage);
mVideoSurface->present(frame);
}
void VideoAdapter::startSurface()
{
mImage = new QImage("../resources/images/test.jpg");
auto pixelFormat = QVideoFrame::pixelFormatFromImageFormat(mImage->format());
mSurfaceFormat = new QVideoSurfaceFormat(mImage->size(), pixelFormat);
if(!mVideoSurface->start(*mSurfaceFormat))
{
qDebug() << "Surface couldn't be started!";
}
}
This class only loads an image file and displays it with the usage of a timer but in your case you will be having a frame source so you can change this to suit your needs. If you can convert your frame to QImage of QVideoFrame you can display it like this.
You have to make this class usable in QML. In my case i created an object and made it visible to QML via setting it as a property.
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlDebuggingEnabler enabler;
VideoAdapter adapter;
// When you do this this object is made visible to QML context with the
// given name
engine.rootContext()->setContextProperty("videoAdapter", &adapter);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
You give this object to the VideoOutput as source in QML.
Window {
visible: true
width: 640
height: 480
color: "black"
title: qsTr("Video Player")
VideoOutput {
id: videoPlayer
anchors.fill: parent
source: videoAdapter
}
}
This example as i said is a simple one that only loads an image and only displays that one image periodically.
This question is an old one and you probably moved on but hope this can at least help other people.
The code provided by #U.Tuken works fine, except if I change the name of property name in Q_PROPERTY from "videoSurface" to any other word, it doesn't work. That is very strange behaviour cause from Qt's point of view "videoSurface" is just a name.
Additionally I got error
"qt.gui.icc: fromIccProfile: failed minimal tag size sanity".
This error pops up if the imported "JPG" is not of correct format
as per this link.
Changing the "JPG" file helped me get rid of the above warning.

Image browser in Qt

The code below displays thumbnails in a left pane. When a thumbnail is clicked, the full-size image appears in the right pane.
I have the impression that even though this code is rather brief, it is not the most natural way to do this task in Qt. Am I reinventing the wheel? Are there Model-View classes that are more suitable for this task?
// main.cpp
#include "PixmapPair.h"
#include <QLabel>
#include <QWidget>
#include <QApplication>
#include <QSplitter>
#include <QGridLayout>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QSplitter* page = new QSplitter;
QGridLayout* gridLayout = new QGridLayout;
QWidget* leftPane = new QWidget(page);
leftPane->setLayout(gridLayout);
QLabel* rightPane = new QLabel(page);
PixmapPair pair1(":/images/ocean.jpg", gridLayout, rightPane);
PixmapPair pair2(":/images/forest.jpg", gridLayout, rightPane);
page->setWindowTitle("Images");
page->show();
return app.exec();
}
// PixmapPair.h
#include <QPixmap>
#include <QIcon>
#include <QLabel>
#include <QPushButton>
#include <QGridLayout>
class PixmapPair : public QObject
{
Q_OBJECT
public:
PixmapPair(QString file, QGridLayout * gridLayout, QLabel* rp)
: rightPane(rp), largePixmap(file)
{
smallPixmap = largePixmap.scaled(QSize(100,100), Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPushButton* pushButton = new QPushButton;
pushButton->setIcon(QIcon(smallPixmap));
pushButton->setFlat(true);
pushButton->setIconSize(QSize(100,100));
QObject::connect(pushButton, SIGNAL(clicked()), SLOT(displayInRightPane()));
gridLayout->addWidget(pushButton);
}
public slots:
void displayInRightPane()
{
rightPane->setPixmap(largePixmap);
}
private:
QLabel* rightPane;
QPixmap largePixmap;
QPixmap smallPixmap;
};
The left part of the SplitView is basically a list presenting all the available pictures. Qt provides a way to handle this using the model/view pattern.
The class for showing a list is a QListView, it will do the job automatically based on a model given with the function setModel().
This function requires a QAbstractItemModel, since this class is a pure abstract one we will need to create a custom class deriving from it.
Inheriting from it will require a lot of glue code but thankfully Qt already provides a class that takes care of most of it when we want to represent a list, it is called QAbstractListModel.
So I created an ImageListModel like this :
///////////////////////
// imagelistmodel.h ///
#ifndef IMAGELISTMODEL_H
#define IMAGELISTMODEL_H
#include <QAbstractListModel>
#include <QPixmap>
struct PixmapPair
{
QString _file;
QPixmap _small;
QPixmap _large;
};
class ImageListModel : public QAbstractListModel
{
Q_OBJECT
public:
// QAbstractItemModel retrieves various information (like text, color, ...)
// from the same index using roles. We can define custom ones, however to
// avoid a clash with predefined roles, ours must start at Qt::UserRole.
// All numbers below this one are reserved for Qt internals.
enum Roles
{
LargePixmapRole = Qt::UserRole + 1
};
explicit ImageListModel(std::initializer_list<QString> files, QObject *parent = 0);
virtual ~ImageListModel();
// QAbstractItemModel interface ===========================
public:
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
// ========================================================
private:
QList<PixmapPair*> _data;
};
#endif // IMAGELISTMODEL_H
///////////////////////
// imagelistmodel.cpp /
#include "imagelistmodel.h"
ImageListModel::ImageListModel(std::initializer_list<QString> files, QObject *parent)
: QAbstractListModel(parent)
{
auto iter = files.begin();
while (iter != files.end())
{
QPixmap large(*iter);
PixmapPair *pair = new PixmapPair();
pair->_file = *iter;
pair->_large = large;
pair->_small = large.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation);
_data.append(pair);
++iter;
}
}
ImageListModel::~ImageListModel()
{
qDeleteAll(_data);
}
int ImageListModel::rowCount(const QModelIndex &parent) const
{
// This function should return the number of rows contained in the parent
// parameter, the parent parameter is used for trees in order to retrieve the
// number of rows contained in each node. Since we are doing a list each element
// doesn't have child nodes so we return 0
// By convention an invalid parent means the topmost level of a tree. In our case
// we return the number of elements contained in our data store.
if (parent.isValid())
return 0;
else
return _data.count();
}
QVariant ImageListModel::data(const QModelIndex &index, int role) const
{
if (index.isValid())
{
switch (role)
{
case Qt::DecorationRole:
{
// DecorationRole = Icon show for a list
return _data.value(index.row())->_small;
}
case Qt::DisplayRole:
{
// DisplayRole = Displayed text
return _data.value(index.row())->_file;
}
case LargePixmapRole:
{
// This is a custom role, it will help us getting the pixmap more
// easily later.
return _data.value(index.row())->_large;
}
}
}
// returning a default constructed QVariant, will let Views knows we have nothing
// to do and we let the default behavior of the view do work for us.
return QVariant();
}
///////////////////////
Our list is now ready and we are almost done.
// main.cpp ///////////
#include <QApplication>
#include <QSplitter>
#include <QLabel>
#include <QListView>
#include "imagelistmodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSplitter page;
QListView *imageList = new QListView(&page);
imageList->setModel(new ImageListModel({ "ocean.jpg", "forest.jpg" }, imageList));
// We tell the list view to show our icon, this mode will call the data function
// of our model with the role : DecorationRole.
imageList->setViewMode(QListView::IconMode);
// We want our list to show data vertically
imageList->setFlow(QListView::TopToBottom);
// We allow only one selection at a time in the list
imageList->setSelectionMode(QListView::SingleSelection);
QLabel *imagePresenter = new QLabel(&page);
// We connect to the signal emitted when the selection is changed
// to update the image presenter.
QObject::connect(imageList->selectionModel(), &QItemSelectionModel::selectionChanged, [imageList, imagePresenter] {
QModelIndex selectedIndex = imageList->selectionModel()->selectedIndexes().first();
// We use our custom role here to retrieve the large image using the selected
// index.
imagePresenter->setPixmap(selectedIndex.data(ImageListModel::LargePixmapRole).value<QPixmap>());
});
page.setWindowTitle("Images");
page.show();
return a.exec();
}
Advantages for this solution are:
- We can easily add filtering by wrapping our custom ListModel into a QSortFilterProxyModel.
- No need to create and manage a lot of buttons.
- The model never needs to know who shows it on screen.
- The QListView will autoscroll if necessary.
- Using a custom role allows us to easily retrieve the large image. If we added the large image in another column, it would show when using this model with a QTableView and when we want retrieve it from the selected index we would have to create a new index pointing to the right column. (Not really hard but require a little more code, and prone to error if we wrap the model in a ProxyModel)
Lambda explanation
For the lambda in C++ the full syntax is:
[CAPTURES]\(PARAMETERS\)->RESULT {FUNCTION}.
Between brackets we capture variables to be able to use them inside the FUNCTION without having to pass them as parameters.
The PARAMETERS between parenthesis have the same signification as any other function, if omitted the lambda takes no parameters.
RESULT is the return type of the FUNCTION and can be omitted.
FUNCTION the body to execute
In this example I decided to ignore the parameters given by the signal so I omitted the parenthesis. I use the captured controls to retrieve the user selection and update the picture shown.

How can I send signals from C to QML? [duplicate]

I want to send a Signal from C++ to a Slot in my QML File.
I already got it working without and primitive type parameters, although if I want to send a QString to my QML Slot I get an error whilst connecting.
I connect in main.cpp
QObject *contentView = rootObject->findChild<QObject*>(QString("contentView"));
QObject::connect(&myObj, SIGNAL(finishedGatheringDataForItem(QString)),
contentView, SLOT(updateViewWithItem(QString)));
the relavant part of my qml File
Rectangle {
objectName: "contentView"
function updateViewWithItem(string) { console.log('got some Items'); } // slot
}
Error:
Object::connect: No such slot QDeclarativeRectangle_QML_2::updateViewWithItem(QString)
You should use Connections in this case (maybe it's the only way to connect).
Put your object myObj to QML file by setContextProperty
qmlVectorForm->rootContext()->setContextProperty("YourObject", myOb);
Your signal is
finishedGatheringDataForItem(QString signalString)
In QML file, add Connectios likes below:
Connections {
target: YourObject
onFinishedGatheringDataForItem: {
qmlString = signalString
}
}
I think it would be best if you check this tutorial:
http://doc.qt.io/qt-4.8/qtbinding.html
especially this section:
http://doc.qt.io/qt-4.8/qtbinding.html#receiving-signals
I think your mistake in this case might either be that you didn't declare it as a slot or you didn't make it invocable. Both options are explained in the Qt Tutorial.
Also, you need to use a QVariant in order to exchange data between C++ and QML.
You can also register types, e.g. Widgets and stuff, so that you can use them in QML as a "native" type like a rectangle. In most cases this is not recommended, except if you need some certain extern class or some data that you cannot display otherwise in your QML Interface.
The reason for the QVariant is the Script based approach of QML. The QVariant basically contains your data and a desription of the data type, so that the QML knows how to handle it properly. That's why you have to specify the parameter in QML with String, int etc.. But the original data exchange with C++ remains a QVariant
I have used the qmlRegisterType before, but it is a very inconvenient Solution for simple data types. It is rather used for more complex data, such as custom Widgets, Canvas or Video elements that QML does not natively support or extended QStandardItemModels . It is a more convenient way to exchange data between QML and C++ and does not need Signals or Slots in first instance, because the QStandardItemModel updates the GUI automatically. For using the QStandardItemModel you need to register the Type with qmlRegisterType.. . The Model can then be used in Model based Views such as the ListView etc.
I attached a tutorial for this topic, it describes how to use the QListModel.
http://doc.qt.io/qt-4.8/qdeclarativemodels.html
For those who also stumbled upon this question, I want to say that Everything is much simpler. You just need the signal from C++ to have QVariant arguments. For example:
QObject::connect(&recipient, SIGNAL(resTalk(QVariant)), engine.rootObjects().at(0)->findChild<QObject*>("winSettings"),
SLOT(showWithErrorNetwork(QVariant)));
My signal is declared like this:
signals:
void resTalk(QVariant res);
So I'm calling the signal:
emit resTalk(true); //For more complex types, use 'emit yourSignal(QVariant(yourArg))'
And here is the slot I have in QML:
function showWithErrorNetwork(isNoError=false) {
if(!isNoError) {
visible = true
warningText.text = "Network error. Check the data."
warningText.visible = true
}
}
Solution without Connections and any context is by connecting not signal-slot, but signal-signal. Found here.
Example code is as follows.
qml:
Window{
signal qmlSend(string textOut)
signal qmlReceive(string textIn)
onQmlReceive:{
console.log(textIn)
}
}
Header file of Background class contains
public signals:
void cppSend(QString textOut);
public slots:
void cppReceive(QString textIn);
And main.cpp connects them in this way:
1.From qml to cpp:
QObject::connect(qmlRootObject, SIGNAL(qmlSend(QString)),
backgroundObject, SLOT(cppReceive(QString)));
2.From cpp to qml:
QObject::connect(backgroundObject, SIGNAL(cppSend(QString)),
qmlRootObject, SIGNAL(qmlReceive(QString)));
I have tried a lot of solutions to succeed in just update QML from a C++ signal but many did not work.
This solution works and has been tested, it is based on this answer: https://stackoverflow.com/a/59502860/2486332 (by #Adriano Campos)
You can send data from C++ to qml using signals, like this:
main.cpp:
#include <QQmlContext>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
// Class init
YourClass yourObject;
// Embedding C++ Objects into QML with Context Properties
QQmlContext* ctx = engine.rootContext();
ctx->setContextProperty("yourObject", &yourObject);
return app.exec();
}
main.qml:
import QtQuick 2.6
Window {
id: mainWindow
Connections {
target: yourObject
onSignalData: {
console.log("Data: " + signal_param)
textToChange.text = "Changed to: " + signal_param
}
}
Text {
id: textToChange
text: "beforeChange"
}
}
yourClass.h:
class YourClass : public QObject
{
Q_OBJECT
signals:
// Signal from YourClass
void signalData(QString signal_param);
}
yourClass.cpp:
emit signalData("Hello QML"); // Signal from yourClass
A complete tutorial about "How to Expose a Qt C++ Class with Signals and Slots to QML" is available on this page: https://felgo.com/cross-platform-development/how-to-expose-a-qt-cpp-class-with-signals-and-slots-to-qml
Why not use rootContext?
in c++ side you have:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
//--------------------------------------------------------
#include <myClass.h>
//--------------------------------------------------------
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
//--------------------------------------------------------
myClass * myobj = new myClass(&app);
//--------------------------------------------------------
//--------------------------------------------------------
engine.rootContext()->setContextProperty("myobj",myobj);
//--------------------------------------------------------
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
and in qml side you have:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: window
visible: true
width: 640
height: 480
title: qsTr("Hello World")
//--------------------------------------------------------
Component.onCompleted: {
myobj.onSomeSignal.connect(signalHandling)
}
//--------------------------------------------------------
//--------------------------------------------------------
function signalHandling(){
console.log("Signal emitted from c++ side")
}
//--------------------------------------------------------
}