I am working on a program which uses an user interface created in QML. The program at it's core have two threads: the main thread in which UI is running and the second thread which will handle all the rest of the work. So program have once class for interfacing with UI and a second class which it's the backend process. The problem is with connecting the slots/signals from UI class to the second class on the second thread.
The code is the following:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "QmlInterface.h"
#include "MainClass.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<QmlInterface>("CppInterface",1,0,"CppInterface");
/* Ui Stuff */
QmlInterface qml;
qml.SetupUI();
/* Main class */
MainClass work;
QObject::connect(&qml, SIGNAL(onButtonClicked()), &work, SLOT(on_ButtonClicked()) );
return app.exec();
}
QmlInterface.h
#ifndef QMLINTERFACE_H
#define QMLINTERFACE_H
#include <QObject>
#include <QDebug>
#include <QQmlApplicationEngine>
class QmlInterface : public QObject
{
Q_OBJECT
public:
explicit QmlInterface();
virtual ~QmlInterface();
void SetupUI();
public slots:
Q_INVOKABLE void buttonClicked();
signals:
void onButtonClicked();
private:
QQmlApplicationEngine *engine;
};
#endif // QMLINTERFACE_H
QmlInterface.cpp
#include "QmlInterface.h"
QmlInterface::QmlInterface()
{
}
QmlInterface::~QmlInterface()
{
}
void QmlInterface::SetupUI()
{
engine = new QQmlApplicationEngine;
engine->load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine->rootObjects().isEmpty())
{
qDebug() << "Failed to load UI";
}
}
void QmlInterface::buttonClicked()
{
qDebug() << "Button clicked! Signal to be emited!";
emit onButtonClicked();
}
MainClass.h
#ifndef MAINCLASS_H
#define MAINCLASS_H
#include <QThread>
#include <QtDebug>
class MainClass : public QThread
{
Q_OBJECT
public:
MainClass();
virtual ~MainClass() {}
public slots:
void on_ButtonClicked();
private:
void run();
};
#endif // MAINCLASS_H
MainClass.cpp
#include "MainClass.h"
MainClass::MainClass()
{
}
void MainClass::on_ButtonClicked()
{
qDebug() << "Button click received in main class!";
}
void MainClass::run()
{
while(1)
{
QThread::sleep(1);
}
}
And finally main.qml
import QtQuick 2.11
import QtQuick 2.8
import QtQuick.Controls 2.1
import QtQuick.Window 2.1
import CppInterface 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
CppInterface
{
id: cpp
}
Button
{
text: "Click me"
onPressed: {
cpp.buttonClicked();
}
}
}
The connection between QML and QmlInterface works fine! The problem is with connection between QmlInterface and MainClass.
To be more specific, the problem is that connect() function called in main.cpp seems not to be able to link given signal with given slot from MainClass:
QObject::connect(&qml, SIGNAL(onButtonClicked()), &work, SLOT(on_ButtonClicked()) );
The problem is that you have created 2 instances of QmlInterface:
QmlInterface qml;
qml.SetupUI();
and
CppInterface
{
id: cpp
}
And you have connected the signal of the first instance, and you are emitting the signal using the second instance.
So instead of creating 2 QmlInterface just create one, for convenience you do not create the object in QML and only export the object created in C++ using setContextProperty():
// ...
#include <QQmlContext>
// ...
void QmlInterface::SetupUI()
{
engine = new QQmlApplicationEngine;
engine->load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine->rootObjects().isEmpty())
qDebug() << "Failed to load UI";
else
// export
engine->rootContext()->setContextProperty("cpp", this);
}
*.qml
import QtQuick 2.11
import QtQuick.Controls 2.1
import QtQuick.Window 2.1
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Button
{
text: "Click me"
onPressed: cpp.buttonClicked();
}
}
On the other hand it is not necessary to register as a type to QmlInterface so you can remove it:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
/* Ui Stuff */
QmlInterface qml;
qml.SetupUI();
/* Main class */
MainClass work;
QObject::connect(&qml, &QmlInterface::onButtonClicked, &work, &MainClass::on_ButtonClicked );
return app.exec();
}
Related
The following little QML application blocks forever when run (Qt 5.14.2, Windows 10). My idea was to create a generic App containing the business logic and have an QtUI on top of it. The blocking does not happen when I remove the Scene3D in the QML, so I think the issue is somehow related to Qt3D. I'm stuck, what am I doing wrong?
main.cpp:
#include "QtUI.h"
int main(int argc, char** argv) {
App app;
QtUI gui(app, argc, argv);
app.setUI(&gui);
app.init();
QtUI::exec();
}
App.h:
#ifndef APP_H
#define APP_H
class App {
public:
class UI {
public:
virtual void onFirst() = 0;
virtual void onSecond() = 0;
};
void setUI(UI* ui);
void init();
private:
UI* ui_ = nullptr;
};
#endif
App.cpp:
#include "App.h"
void App::init() {
ui_->onFirst();
ui_->onSecond();
}
void App::setUI(App::UI* ui) {
ui_ = ui;
}
QtUI.h:
#ifndef QTUI_H
#define QTUI_H
#include <QApplication>
#include <QQmlApplicationEngine>
#include "App.h"
class QtUI : public QGuiApplication, public App::UI {
Q_OBJECT
public:
explicit QtUI(App& app, int& argc, char** argv);
void onFirst() override;
void onSecond() override;
signals:
void first();
void second();
private:
App& app_;
QQmlApplicationEngine engine_;
};
#endif
QtUI.cpp
#include "QtUI.h"
#include <QQmlContext>
QtUI::QtUI(App& app, int& argc, char** argv)
: QGuiApplication(argc, argv),
app_(app) {
engine_.rootContext()->setContextProperty("app", this);
engine_.load("qrc:/Main.qml");
}
void QtUI::onFirst() {
emit first();
}
void QtUI::onSecond() {
emit second();
}
Main.qml
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Window 2.14
import QtQuick.Scene3D 2.14
import Qt3D.Core 2.14
import Qt3D.Render 2.14
import Qt3D.Input 2.14
import Qt3D.Extras 2.14
ApplicationWindow {
visible: true
StackView {
id: stack
anchors.fill: parent
}
Connections {
target: app
onFirst: stack.push(first);
onSecond: stack.push(second);
}
Component {
id: first
Item {
Scene3D {
Entity {
components: [
RenderSettings {
activeFrameGraph: ForwardRenderer {
clearColor: "red"
}
}
]
}
}
}
}
Component {
id: second
Item {
Button{ text: "Foo"}
}
}
}
I've used replace method of a StackView as a workaround.
I have a small problem. Can someone show me how can I update a qml text from c++. I have an example using threads but I don't want to apply this method because I don't know how to set a
parameter in run() function.To fully understand me here is my code.In the main function when I start the thread I want to put my custom text or a string variable that has a text.
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
Thread(QObject *parent=nullptr);
~Thread() override;
Q_SLOT void stop();
Q_SIGNAL bool textChanged(const QString & text);
protected:
void run() override;
};
#endif // THREAD_H
thread.c
#include "thread.h"
#include <QDebug>
Thread::Thread(QObject *parent):
QThread(parent)
{
}
Thread::~Thread()
{
}
void Thread::stop()
{
requestInterruption();
wait();
}
void Thread::run() //here I want to specify a QString variable such that in main function to call the function with my text, and not specified the text from here
{
int i=0;
while(!isInterruptionRequested()){
QString text;
text = QString("I changed the text"); // I don't want to declare from here the text.
Q_EMIT textChanged(text);
QThread::msleep(20);
qDebug() <<i++;
}
}
main.cpp
...
Thread thread;
QQmlApplicationEngine engine;
QObject::connect(&app, &QGuiApplication::aboutToQuit, &thread, &Thread::stop);
thread.start();
engine.rootContext()->setContextProperty("thread", &thread);
engine.load(QUrl("qrc:/main.qml"));
thread.stop();
...
main.qml
.....
Text {
objectName: "myLabel"
id: txt
width: 200
height: 29
color: "#faf9f9"
text: qsTr("Text")
font.pixelSize: 12
}
Connections{
target: thread
onTextChanged: txt.text = text
}
.....
You can send data from C++ to qml using signals, like this:
//main.cpp
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: gapi
onSignalData: {
console.log("Data: " + data)
textToChange.text = "Changed to: " + data
}
}
Text {
id: textToChange
text: "beforeChange"
}
}
//yourClass.h
class YourClass : public QObject
{
signals:
// Signal from YourClass to QML
void signalData(QString data);
}
//yourClass.cpp
emit signalData("Hello QML"); // Signal from GAPI to QML
This page https://felgo.com/cross-platform-development/how-to-expose-a-qt-cpp-class-with-signals-and-slots-to-qml have a complete tutorial about "How to Expose a Qt C++ Class with Signals and Slots to QML"
I have 2 classes, MyApp and MyAppView. MyApp class will hold other classes and the implementation will be here. (you can call it Manager class or System class). MyAppView class only interacts with main.qml like it'll have lots of "Q_PROPERTY"ies. I think you understand the point. I don't want MyApp will have "Q_PROPERTY"ies.
The scenerio is as the following;
//------------------------------------
//---------------------------main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "myapp.h"
#include "myappview.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<MyAppView>("org.myappview", 1, 0, "MyAppView");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
MyApp myApp;
return app.exec();
}
//------------------------------------
//---------------------------myappview.h
#include <QObject>
class MyAppView : QObject
{
Q_OBJECT
Q_PROPERTY(QString myString READ getMyString NOTIFY myStringChanged)
public:
MyAppView();
QString getMyString() { return m_myString; }
void setMyString(QString newString)
{
m_myString = newString;
emit myStringChanged;
}
signals:
void myStringChanged();
private:
QString m_myString;
}
//------------------------------------
//---------------------------main.qml
import QtQuick 2.0
import QtQuick.Window 2.0
import org.myappview 1.0
Window {
visible: true
MyAppView {
id: backend
}
Text {
text: qsTr(backend.myString)
}
}
//------------------------------------
//---------------------------myapp.h
#include <QObject>
#include "myappview.h"
class MyApp : QObject
{
Q_OBJECT
public:
MyApp();
private:
MyAppView appView;
void changeMyStringInAppView()
{
// This will automatically update main.qml
appView.setMyString("This is new string");
}
}
Also it is okay to reaching existing QML instance from MyApp, instead of instantiating QML from MyApp. So the main point is instantiating or reaching View class from Manager class so that I can control it easily. Maybe at some part, my logic is wrong. Please tell me if I am. I'm okay with all the suggestions.
The problem in your code is that the MyAppView of MyApp is different from the one created in QML, so if you update the appView text it will not be reflected in the backend text, so the solution is to expose an object from MyApp to QML with setContextProperty() and will call a function to establish the MyAppView created in QML (Keep in mind to create only one MyApp but you will have the same problem)
// myappview.h
#ifndef MYAPPVIEW_H
#define MYAPPVIEW_H
#include <QObject>
class MyAppView : public QObject
{
Q_OBJECT
Q_PROPERTY(QString myString READ getMyString NOTIFY myStringChanged)
public:
explicit MyAppView(QObject *parent = nullptr) : QObject(parent)
{}
QString getMyString() const { return m_myString; }
void setMyString(const QString & newString)
{
if(m_myString != newString){
m_myString = newString;
emit myStringChanged();
}
}
signals:
void myStringChanged();
private:
QString m_myString;
};
#endif // MYAPPVIEW_H
// myapp.h
#ifndef MYAPP_H
#define MYAPP_H
#include "myappview.h"
#include <QObject>
class MyApp : public QObject
{
Q_OBJECT
public:
explicit MyApp(QObject *parent = nullptr) : QObject(parent),
appView(nullptr)
{}
Q_INVOKABLE void setAppView(MyAppView *value){
appView = value;
}
void changeMyStringInAppView()
{
if(appView)
appView->setMyString("This is new string");
}
private:
MyAppView *appView;
};
#endif // MYAPP_H
// main.cpp
#include "myapp.h"
#include "myappview.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<MyAppView>("org.myappview", 1, 0, "MyAppView");
MyApp myapp;
QTimer::singleShot(1000, &myapp, &MyApp::changeMyStringInAppView);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myapp", &myapp);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
// main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import org.myappview 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MyAppView {
id: backend
}
Text {
text: qsTr(backend.myString)
}
Component.onCompleted: myapp.setAppView(backend)
}
I am trying to send a list from QML to c++. I've tried with a string and an integer with success but when I try with a QVariant I get the error:
QObject::connect: No such slot MyClass::cppSlot(QVariant) in ../test_2206/main.cpp:31
My main.qml
import QtQuick 2.4
import QtQuick.Layouts 1.1
import Material.ListItems 0.1 as ListItem
import Material.Extras 0.1
import QtQuick.Controls 1.3 as QuickControls
import QtQuick.Controls 1.4
import Material 0.2
Window {
visible: true
property var listCloud: ["nuage1", "nuage2", "nuage3", "nuage4"]
id: item
width: 100; height: 100
signal qmlSignal(variant msg)
/* MouseArea {
anchors.fill: parent
onClicked: item.qmlSignal("Hello from QML")
}*/
Rectangle{
id:sousRect
color:"red"
width:100; height:100
Button{
id:buttonTest
onClicked: {
item.qmlSignal(listCloud)
}
}
}
}
My main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QQmlComponent>
#include <QDebug>
#include "myclass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
QObject *object = component.create();
MyClass myClass;
QObject::connect(object, SIGNAL(qmlSignal(QVariant)),
&myClass, SLOT(cppSlot(QVariant)));
return app.exec();
}
My myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QDebug>
#include <QString>
#include <QList>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0);
signals:
public slots:
void cppSlot(QVariant &msg);
};
#endif // MYCLASS_H
My myclass.cpp
#include "myclass.h"
MyClass::MyClass(QObject *parent):
QObject(parent)
{
}
void MyClass::cppSlot(QVariant &msg){
qDebug() << "c++: bien reçu chef " << msg;
}
I don't understand why I can't put a QVariant parameter in this signal.
Any help is welcome :)
Change your slot to receive his parameter by value, rather than by reference. Like this:
void cppSlot(QVariant msg);
I think it has something to do with QML refusing to pass Window's property by reference. Anyway, Qt often sends signal by-value even if your signal/slot signature[s] declare arguments by-reference. For more on the topic see this and that
Don't use references between QML and C++, they won't work.
void cppSlot(QVariant & msg);
You can also create Q_INVOKABLE function in C++ and call it directly from QML. Here is how this looks like:
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QQmlComponent>
#include <QDebug>
#include "myclass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
auto myobject = new MyClass(&app);
engine.rootContext()->setContextProperty(QStringLiteral("myobject"), myobject);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
{
return -1;
}
return app.exec();
}
mycass.h:
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QDebug>
#include <QString>
#include <QList>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0);
Q_INVOKABLE void cppSlot(QVariant msg);
};
#endif // MYCLASS_H
myclass.cpp:
#include "myclass.h"
MyClass::MyClass(QObject *parent):QObject(parent){}
void MyClass::cppSlot(QVariant msg)
{
qDebug() << "c++: bien reçu chef " << msg;
}
main.qml:
import QtQuick 2.4
import QtQuick.Layouts 1.1
import Material.ListItems 0.1 as ListItem
import Material.Extras 0.1
import QtQuick.Controls 1.3 as QuickControls
import QtQuick.Controls 1.4
import Material 0.2
Window
{
visible: true
property var listCloud: ["nuage1", "nuage2", "nuage3", "nuage4"]
id: item
width: 100; height: 100
Rectangle
{
id:sousRect
color:"red"
width:100; height:100
Button
{
id:buttonTest
onClicked: myobject.cppSlot(listCloud)
}
}
}
But if you like to use SIGNAL SLOTS there is Connections Object in QML. Your main.qml will look then like that:
Window
{
visible: true
property var listCloud: ["nuage1", "nuage2", "nuage3", "nuage4"]
id: item
width: 100; height: 100
Connections
{
target: buttonTest
onClicked: myobject.cppSlot(listCloud)
}
Rectangle
{
id:sousRect
color:"red"
width:100; height:100
Button
{
id:buttonTest
}
}
}
Q_INVOKABLE is nothing else as an public SLOT, it will be called through meta-object system.
I am trying to make a simple program that allow user to connect to the specific websites via clicking the image.
Here is my code:
account.h:
#ifndef ACCOUNTS_H
#define ACCOUNTS_H
#include <QObject>
#include <QUrl>
#include <QDesktopServices>
class accounts : public QObject
{
Q_OBJECT
public:
explicit accounts(QObject* parent = 0) : QObject(parent){}
public slots:
void gmailOpen(const QString &msg)
{
QUrl gmailUrl(msg);
QDesktopServices::openUrl(gmailUrl);
}
};
#endif // ACCOUNTS_H
main.cpp:
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickView>
#include <QtQml>
#include "accounts.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView *view = new QQuickView;
QObject *gmail = view->rootObject().findChild<QObject*>("gmailLink");
accounts *gmailAccount = new accounts;
QObject::connect(gmail, SIGNAL(gmailSignal(QString)),gmailAccount,SLOT(gmailOpen(QString)));
view->setSource(QUrl::fromLocalFile("/Users/yudelin/Documents/MyCrazyProjects/Managers4ManyAccounts/main.qml"));
view->show();
return app.exec();
}
main.qml:
import QtQuick 2.0
Rectangle {
width: 360
height: 360
color: "silver"
Image {
id:gmailLink
objectName: "gmailLink"
width: 102
height: 199
fillMode: Image.PreserveAspectFit
source: "...." //the url is too long so I omit it.
anchors.centerIn: parent
signal gmaiSignal (string msg)
MouseArea {
anchors.fill: parent
onClicked:
gmailLink.gmailSignal("http://mail.google.com")
}
}
}
As you can see, I am trying to connect Qml Image with c++ object accounts.
So I use QObject *gmail = view->rootObject().findChild<QObject*>("gmailLink"); to fetch the qml object.
But it does not work. I guess it is suit for the older Qt version.
How could I fix this problem?
You want gmailOpen slot to be called on clicking on the Image. There are simpler and preferred ways of doing it other than doing this.
QObject *gmail = view->rootObject().findChild("gmailLink");
As #Kunal pointed out, you can use Qt.openUrlExternally(string).
If you want to open the Url by using QDesktopServices::openUrl from the gmailOpen slot, you can call the slot directly by setting the context property.
4, QObject *gmail = view->rootObject().findChild<QObject*>("gmailLink");... Here before setting the qml file, you are trying to get a reference for the gmailLink object. Its not created yet.
Following code explains my take on the problem
5a. accounts.h file
#ifndef ACCOUNTS_H
#define ACCOUNTS_H
#include <QObject>
#include <QUrl>
#include <QDesktopServices>
class accounts : public QObject
{
Q_OBJECT
public:
explicit accounts(QObject* parent = 0) : QObject(parent){}
public slots:
void gmailOpen(const QString &msg)
{
QUrl gmailUrl(msg);
QDesktopServices::openUrl(gmailUrl);
}
};
#endif // ACCOUNTS_H
5b. main.cpp file
#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickView>
#include <QtQml>
#include "accounts.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
accounts *gmailAccount = new accounts;
QQuickView *view = new QQuickView;
view->engine()->rootContext()->setContextProperty("accounts",gmailAccount);
view->setSource(QUrl::fromLocalFile("qml/SO_OpenExternalLink/main.qml"));
view->show();
return app.exec();
}
5c. main.qml file
import QtQuick 2.1
import QtQuick.Dialogs 1.0
Rectangle {
width: 360
height: 360
color: "silver"
Image {
id:gmailLink
objectName: "gmailLink"
width: 102
height: 199
fillMode: Image.PreserveAspectFit
source: "http://upload.wikimedia.org/wikipedia/commons/4/41/Flag_of_India.svg"
anchors.centerIn: parent
MouseArea {
anchors.fill: parent
onClicked:Qt.openUrlExternally("http://mail.google.com")
}
}
Image{
id:secondImage
width:102
height:199
fillMode:Image.PreserveAspectFit
source: "http://upload.wikimedia.org/wikipedia/commons/5/55/Emblem_of_India.svg"
anchors.left:gmailLink.right
anchors.top: gmailLink.top
MouseArea {
anchors.fill: parent
onClicked:fileDialog.visible = true
}
}
FileDialog {
id: fileDialog
title: "Please choose a file"
nameFilters: [ "Image files (*.jpg *.png)", "All files (*)" ]
visible:false;
selectMultiple:false
onAccepted:secondImage.source = fileDialog.fileUrl
}
}
5d. You need to change the following from the above code
i. Image source
ii. QML file path in the main.cpp
iii. Possibly import QtQuick 2.0 if you don't have QtQuick 2.1 yet.
If you just want to open URL then you can use Qt.openUrlExternally(string) directly from QML. Here is documentation. No need to create a QObject for this.
And for your code. I don't see any method named gmailSignal.
You should be calling gmailOpen.
like
gmailLink.gmailOpen("http://mail.google.com")