QtQuick app and C++ slots binding error - c++

I am in the process of learning of coding QtQuick applications using C++ on the background. My plan is to write as most as possible code in QtQuick (QML), but there is one thing I could not find, so I decided to use C++. Basically, I need to create an app which provides an user with certain widgets and one button. Upon clicking on the button an CLI command is created and called. I can create this CLI command using javascript, but I did not find a way how to execute this command, so I decided for C++.
The plan is to register a signal in on the button in QML and connect this signal with a slot in C++. I opened the QML documentation where I found several helpful examples. My code of an testing app contains two source files:
main.qml:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import "scripts.js" as Logic
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
minimumHeight: 480
minimumWidth: 640
ColumnLayout{
spacing: 2
height: parent.height - 100
width: parent.width
GridLayout {
id: grid
columns: 3
Label { text: "Vstupni soubor"; }
TextField {
id: input_file_field
}
Button {
text: "..."
onClicked: fileDialogInputFile.open()
}
Label { text: "Vystupni adresar"; }
TextField {id: output_dir_field}
Button {
text: "..."
onClicked: fileDialogOutputDir.open()
}
CheckBox{
id: opt1
text: "Volba 1"
}
CheckBox{
id: opt2
text: "Volba 2"
}
CheckBox{
id: opt3
text: "Volba 3"
}
}
Button {
signal qmlSignal(string msg)
objectName: "myButton"
text: "Analyzuj"
onClicked: {
qmlSignal("Hello from QML")
console.log("Nahodne cislo " + Logic.func())
}
}
}
TextArea {
id: log_output
width: parent.width
height: 100
y: parent.height - 100
readOnly: true
text: "Log zprav";
}
FileDialog {
id: fileDialogInputFile
title: "Please choose a file"
onAccepted: {
input_file_field.text = fileDialogInputFile.fileUrl
}
}
FileDialog {
id: fileDialogOutputDir
title: "Please select output directory"
selectFolder: true
onAccepted: {
output_dir_field.text = fileDialogOutputDir.fileUrl
console.log("You chose: " + fileDialogOutputDir.fileUrl)
}
}
}
main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QDebug>
#include <QObject>
class MyClass : public QObject
{
Q_OBJECT
public slots:
void cppSlot(const QString &msg) {
qDebug() << "Called the C++ slot with message:" << msg;
}
};
int main(int argc, char *argv[]) {
qDebug() << "Hello World - main!";
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
MyClass myClass;
QObject *win = engine.rootObjects()[0];
QObject *item = win->findChild<QObject*>("myButton");
QObject::connect(item, SIGNAL(qmlSignal(QString)),
&myClass, SLOT(cppSlot(QString)));
return app.exec();
}
Program can be compiled and run. When I run the program, I can see this on console:
QML debugging is enabled. Only use this in a safe environment.
Hello World - main!
QObject::connect: No such slot MyClass::cppSlot(QString) in ../pokus/main.cpp:30
QObject::connect: (sender name: 'myButton')
Which means, the program is running but can not connect cppSlot onto the signal generated by my button. The problem I have is that I followed QML manual, and blogs deal with most sophisticated problems, i.e., I spent a day by googling without any outcome..

Your code doesn't compile:
debug/main.o: In function `MyClass::MyClass()':
/home/micurtis/dev/test/guitest/main.cpp:6: undefined reference to `vtable for MyClass'
If I #include "main.moc" and remove the references to Logic.js, it works for me when I click on "Analyzuj":
// ...
return app.exec();
}
#include "main.moc"
Output:
Starting /home/micurtis/dev/test/guitest/guitest...
QML debugging is enabled. Only use this in a safe environment.
Hello World - main!
Called the C++ slot with message: "Hello from QML"
/home/micurtis/dev/test/guitest/guitest exited with code 0

Related

How to use QThread from QML while having QML function async too

I'm looking for the way to use QThread in QML.
I want to pass parameters to the QThread function and return a bool value from it.
Another thing I want from the QML side is to not block the app when it's executing a script that will happen before calling/executing the QThread.
Below is an example code:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "testasync.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<testAsync>("testAsync",1,0,"thread");//not working on main.qml
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
//import testAsync 1.0
ApplicationWindow {
id: window
title: "Stack"
visible: true
width: 1400
Page {
id: page
anchors.fill: parent
property int responsiveWidth: 1000
property int maximumWidth: 900
ScrollView {
id:configScroll
anchors.fill: parent
GridLayout {
columns: 2
width: page.width > page.responsiveWidth ? page.maximumWidth : page.width
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: page.width > page.responsiveWidth ? (page.width - childrenRect.width)/2 : 10
anchors.rightMargin: page.width > page.responsiveWidth ? 0 : 10
//this function needs to be processed and will return the values we need for the testasync. this can't block UI thread too
function teste() {
for(var i=0; i<10000000; i++)
{
console.log(i)
}
return "teste"
}
Button {
property bool test: true
text: "async"
onClicked: {
var val = parent.teste()
// if(test)
// val=thread.start()
// else
// val=thread.quit()
console.log(val)
test=!test
}
}
}
}
}
}
testasync.h
#ifndef TESTASYNC_H
#define TESTASYNC_H
#include <QThread>
#include <QObject>
class testAsync : public QThread
{
Q_OBJECT
public:
testAsync();
void run();
private:
QString name;
};
#endif // TESTASYNC_H
testasync.cpp
#include "testAsync.h"
#include <QDebug>
#include <QtCore>
testAsync::testAsync(){}
void testAsync::run() {
for(int i = 0; i <= 100; i++)
{
qDebug() << this->name << " " << i;
}
//return true
}
How can these be done?
Register the type correctly:
qmlRegisterType<testAsync>("TestAsync", 1, 0, "TestAsync");
Make a instance of your type in the qml file and call the methods of it.
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import TestAsync 1.0
ApplicationWindow {
id: window
title: "Stack"
visible: true
width: 1400
TestAsync {
id: threadAsync
}
Page {
....
Button {
property bool test : true
text: "async"
onClicked: {
if(test) {
val=threadAsync.start()
} else {
val=threadAsync.quit()
}
console.log(val)
test=!test
}
}
....
}
You've done several errors that drives you away from desired.
As it was already mentioned by #folibis and by #Hubi -- you've used C++ class names which starts from small letter. QML has problems with it.
Regarding multi-threading, there are a lots of ways to do it. It really depends on your particular tasks.
I do really recommend you to read next articles (from official Qt documentation):
Threading Basics
Multithreading Technologies in Qt
Since you have signals in Qt and QML, you may implement all what you want in C++ and then just drop it to QML.
You may refer to this simple project on GitHub I've prepared for you. There is moveToThread approach implemented.

QML: Asking confirmation before closing application

I have a QtQuick application. When the user tries to close the application, I want an "Are you sure?" window to pop up.
My main C++ class has this:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
And my main QML class has an application window:
ApplicationWindow {
id: root
...
}
Where and how would I catch the close event? I read about overriding closeEvent() from QMainWindow method or something, but I don't have a QMainWindow and I don't know where I'd put that code.
So I'd like to know how to prevent the app from closing and have something else happen instead, and how I'd close the app later when the user clicks "ok" in the confirmation dialog.
As far as I can see, the ApplicationWindow's "onClosing" only allows me to do some clean up before the inevitable close, but it doesn't prevent the close (please correct me if I'm wrong)
I solved it.
ApplicationWindow {
id: root
onClosing: close.accepted = false
}
This prevents the app from closing.
root.close()
This closes the app.
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.13
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.3
ApplicationWindow {
property bool closing: false
MessageDialog {
id: exitMessageDialogId
icon: StandardIcon.Question
text: "Are you sure to exit?"
standardButtons: StandardButton.Yes | StandardButton.No
onYes: {
closing = true
mainWindowId.close()
}
}
onClosing: {
close.accepted = closing
onTriggered: if(!closing) exitMessageDialogId.open()
}
id: mainWindowId
}
In this case it just close current window.
//use Qt 5.11.2
// for android y desktop
import QtQuick 2.9
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
ApplicationWindow {
title: qsTr("xxxx xxxx")
id: mainWindow
visible: true
//desision para salir
MessageDialog {
id: messageDialogQuit
title: "Deseas salir?"
icon: StandardIcon.Question
text: "xxxxxxxxxxxxxxxxxxxxxxxxx."
standardButtons: StandardButton.Yes |StandardButton.No
// Component.onCompleted: visible = true
onYes: Qt.quit()
// onNo: console.log("didn't copy")
}
onClosing:{
close.accepted = false
onTriggered: messageDialogQuit.open()
}
menuBar: MenuBar {
id: m_menu
LayoutMirroring.enabled: true
LayoutMirroring.childrenInherit: true
anchors.left: parent.left
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: messageDialogQuit.open()
}
}
}
width: 400
height: 300
}

Qt: Connect list in c++ to listview in QML

I just started learning Qt. To be honest, some of confuses my a lot. I'm trying to load a predefined .csv table into a listview.
I created a qml file with the simple layout of a textfield, a listview and a button to load a file. The file looks like this:
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: applicationWindow
objectName: "App"
visible: true
width: 640
height: 480
title: qsTr("Rosinenpicker")
ColumnLayout {
id: columnLayout
anchors.rightMargin: 40
anchors.leftMargin: 40
anchors.bottomMargin: 40
anchors.topMargin: 40
anchors.fill: parent
TextField {
id: name
text: qsTr("Text Field")
Layout.preferredHeight: 50
Layout.fillWidth: true
}
ListView {
id: list
x: 0
y: 0
width: 110
height: 160
spacing: 0
boundsBehavior: Flickable.DragAndOvershootBounds
Layout.fillHeight: true
Layout.fillWidth: true
objectName: "listView"
model: guestModel
delegate: Item {
x: 5
width: 80
height: 40
Text {
text: model.modeldata.name
font.bold: true
anchors.verticalCenter: parent.verticalCenter
}
}
}
Button {
id: button
width: 50
text: qsTr("Open File")
Layout.preferredWidth: 100
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: fileDialog.visible = true;
}
FileDialog {
id: fileDialog
title: "Please choose a valid guestlist file"
objectName: "fileDialog"
nameFilters: ["Valid guestlist files (*.csv)"]
selectMultiple: false
signal fileSelected(url path)
onAccepted: fileSelected(fileDialog.fileUrl)
}
}
}
In my main file I create a CsvParser class a connect it to the fileSelected signal of the file dialog.
I can parse the csv-File. But when I try to connect it to the listview, I'm lost.
The main file:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtDebug>
#include <QUrl>
#include <QQuickView>
#include "csvparser.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
auto root = engine.rootObjects().first();
auto fileDialog = root->findChild<QObject*>("fileDialog");
CsvParser parser(engine.rootContext());
QObject::connect(fileDialog, SIGNAL(fileSelected(QUrl)), &parser, SLOT(loadData(QUrl)));
return app.exec();
}
My parser looks like this:
#include "csvparser.h"
#include <QtDebug>
#include <QFile>
#include "guest.h"
CsvParser::CsvParser(QQmlContext *context)
{
this->context = context;
}
void CsvParser::loadData(QUrl path)
{
friendList.clear();
guestList.clear();
QFile file(path.toLocalFile());
file.open(QIODevice::ReadOnly);
// ignore first lines
for(int i=0; i<3; i++)
file.readLine();
while(!file.atEnd()) {
auto rowCells = file.readLine().split(',');
if(rowCells.size() != 6)
continue;
checkedAdd(friendList, rowCells[0], rowCells[1]);
checkedAdd(guestList, rowCells[3], rowCells[4]);
}
qDebug() << guestList.size();
auto qv = QVariant::fromValue(guestList);
context->setContextProperty("guestModel", qv);
}
void CsvParser::checkedAdd(QList<QObject*> &list, QString name, QString familyName)
{
if(name == "" && familyName == "")
return;
list.append(new Guest(name, familyName));
}
And the guest, which has only a name and a familyname looks like this:
#ifndef GUEST_H
#define GUEST_H
#include <QObject>
class Guest : public QObject
{
public:
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString fName READ fName WRITE setfName NOTIFY fNameChanged)
public:
Guest(QString name, QString fName);
QString name();
QString fName();
void setName(QString name);
void setfName(QString fName);
signals:
void nameChanged();
void fNameChanged();
private:
QString m_name, m_fName;
};
#endif // GUEST_H
I get the following output:
qrc:/main.qml:41: ReferenceError: guestModel is not defined
QFileInfo::absolutePath: Constructed with empty filename
2
qrc:/main.qml:49: TypeError: Cannot read property 'name' of undefined
qrc:/main.qml:49: TypeError: Cannot read property 'name' of undefined
What am I doing wrong? Any help is appreciated. Thanks!
There are a few things I think are wrong. QML will not find guestModel, because it tries to render the ListView but it has not been made available to QML since you are setting the context property in loadData(), which is however only invoked when the user interacts with the FileDialog. Also, the Q_OBJECT macro needs to appear in the private section of the class (see http://doc.qt.io/qt-5/qobject.html#Q_OBJECT). Also, I do not think it is nice to make a data container/parser class aware of the UI, i.e. you export the rootContext to the parser. It would be better if you export something from the parser to the rootContext in main. The parser should have no notion of the UI IMHO. Finally, if you want to implement a custom list of data items to be displayed, you really should think about using an AbstractListModel as Yuki has suggested (see, http://doc.qt.io/qt-5/qabstractlistmodel.html) or another suitable List-based model (see, http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html). This way, changes in C++ will automatically result in updates in the UI.

Copy QML Item to Clipboard as Image thru C++

I've been trying on with QML Charts API and decided to export the ChartView as image to the Clipboard. I found a working solution surfing on the net, in which one grabs the item as image thru Javascript and sends the QVariant data to C++. This is nice and works but I'm wondering if it wouldn't be possible to send just a QQuickItem* or something as light as that and do the grab and whatever at C++, as everybody says to avoid Javascript as much as possible and grabbing an image seems to be a heavy operation.
Here is the working code I'm using now.
chartexporter.h
#ifndef CHARTEXPORTER_H
#define CHARTEXPORTER_H
#include
#include
class QQuickItem;
class ChartExporter : public QObject
{
Q_OBJECT
public:
explicit ChartExporter(QObject *parent = 0);
Q_INVOKABLE void copyToClipboard(QVariant data);
};
#endif // CHARTEXPORTER_H
chartexporter.cpp
includes....
ChartExporter::ChartExporter(QObject *parent) : QObject(parent)
{
}
void ChartExporter::copyToClipboard(QVariant data){
QImage img = qvariant_cast(data);
QApplication::clipboard()->setImage(img,QClipboard::Clipboard);
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtCharts 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello Chart World")
ColumnLayout{
spacing: 2
anchors.fill: parent
ChartView{
id: chart
title: "Testing Charts"
anchors.fill: parent
legend.alignment: Qt.AlignTop
antialiasing: true
animationOptions: ChartView.AllAnimations
PieSeries {
id: pieSeries
PieSlice { label: "Volkswagen"; value: 13.5; exploded: true}
PieSlice { label: "Toyota"; value: 10.9 }
PieSlice { label: "Ford"; value: 8.6 }
PieSlice { label: "Skoda"; value: 8.2 }
PieSlice { label: "Volvo"; value: 6.8 }
}
}
Button{
Layout.alignment: Qt.AlignBottom
text: qsTr("Copy to Clipboard")
onClicked: {
var stat = chart.grabToImage(function(result) {
Printer.copyToClipboard(result.image);
});
}
}
}
}
main.cpp
includes ....
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
ChartExporter printer;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("Printer", &printer);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
Do you guys have an idea on how it could be done by simply calling a method to send the desired Item to C++ as proposed below and processing all stuff in there?
Button{
Layout.alignment: Qt.AlignBottom
text: qsTr("Copy to Clipboard")
onClicked: {
Printer.copyToClipboard(chart);
}
}
I kind of found a way to do it in C++!
When I do the Printer.copyToClipboard(chart) inside the onClicked handler (Javascript) I'm actually sending a QObject* to the C++ layer. Inside my C++ method I can do a qobject_cast and grab it as image. One must take into account that the grab operation is asyncronous. So, I needed to do the copy to clipboard inside an slot which gets called when the grab is finished.
So, below is the way I got. If anybody has a better approach it would be nice to know.
chartexporter.h
#ifndef CHARTEXPORTER_H
#define CHARTEXPORTER_H
#include &ltQObject>
#include &ltQVariant>
#include &ltQSharedPointer>
class QQuickItem;
class QQuickItemGrabResult;
class ChartExporter : public QObject
{
Q_OBJECT
private:
QSharedPointer p_grabbedImage;
protected slots:
void doCopy();
public:
explicit ChartExporter(QObject *parent = 0);
Q_INVOKABLE void copyToClipboard(QObject *item);
};
#endif // CHARTEXPORTER_H
chartexporter.cpp
#include &ltQDebug>
#include &ltQImage>
#include &ltQQuickItem>
#include &ltQClipboard>
#include &ltQApplication>
#include &ltQSharedPointer>
#include &ltQQuickItemGrabResult>
#include "chartexporter.h"
ChartExporter::ChartExporter(QObject *parent) : QObject(parent), p_grabbedImage(nullptr)
{
}
void ChartExporter::copyToClipboard(QObject* item){
if(item){
auto itm = qobject_cast(item);
p_grabbedImage = itm->grabToImage(QSize(itm->width()*2,itm->height()*2));
connect(p_grabbedImage.data(), &QQuickItemGrabResult::ready, this, &ChartExporter::doCopy);
}
}
void ChartExporter::doCopy(){
if(p_grabbedImage.data()){
auto img = p_grabbedImage->image();
QApplication::clipboard()->setImage(img, QClipboard::Clipboard);
}
disconnect(p_grabbedImage.data(),&QQuickItemGrabResult::ready, this, &ChartExporter::doCopy);
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtCharts 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello Chart World")
ColumnLayout{
spacing: 2
anchors.fill: parent
ChartView{
id: chart
title: "Testing Charts"
anchors.fill: parent
legend.alignment: Qt.AlignTop
antialiasing: true
animationOptions: ChartView.AllAnimations
PieSeries {
id: pieSeries
PieSlice { label: "Volkswagen"; value: 13.5; exploded: true}
PieSlice { label: "Toyota"; value: 10.9 }
PieSlice { label: "Ford"; value: 8.6 }
PieSlice { label: "Skoda"; value: 8.2 }
PieSlice { label: "Volvo"; value: 6.8 }
}
}
Button{
Layout.alignment: Qt.AlignBottom
text: qsTr("Copy to Clipboard")
onClicked: {
Printer.copyToClipboard(chart);
}
}
}
}
main.cpp
#include &ltQApplication>
#include &ltQQmlContext>
#include &ltQQmlApplicationEngine>
#include "chartexporter.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
ChartExporter printer;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("Printer", &printer);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}

Accesing QML element from C++ code in QT

I am trying to use QML with C++ in QT, but for now unsuccessfully. I cannot access my QML element from the C++ code using rootObjects() function. What am I doing wrong?
qml part:
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: window
visible: true
width: 640
height: 520
title: qsTr("My app")
Item {
anchors.fill: parent
Rectangle {
id: rectangle1
x: 0
y: 0
width: 640
height: 370
color: "#ffffff"
}
Button {
id: startButton
x: 325
y: 425
text: qsTr("Start")
}
}
}
C++ Part:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
QObject *rootObject = engine.rootObjects().first();
qDebug() << rootObject->objectName();//prints ""
QObject *qmlObject = rootObject->findChild<QObject*>("window");// or "startButton"
//qDebug() << qmlObject->objectName(); //app fails, because window was not found
QList<QObject *> allQObjects = rootObject->findChildren<QObject *>();
for(int i=0;i< allQObjects.length();++i)
{
qDebug() << allQObjects[i]->objectName(); //prints everytime ""
}
qDebug() << "len: " << allPQObjects.length(); //prints 132
return app.exec();
}
At first: If you do not set a object name there will be no!
QML:
Rectangle { id : frame; objectName : "objFrame" color : "blue" }
Qt:
QObject *pRootObject = m_pQmlView->rootObject();
QObject *pobjFrame = m_pRootObject->findChild<QObject *>("objFrame");
The other way arround:
Qt:
m_pQmlView->rootContext()->setContextProperty( "_view", this );
QML:
Component.onCompleted: {
/********************** Connections ***************************/
// connect signal MyView::retranslate() with slot retranslate
_view.retranslate.connect(retranslate)
}
Need to add objectname to QML
ApplicationWindow {
id: window
objectName: "window"
...
}