QML: Asking confirmation before closing application - c++

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
}

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.

Interaction with qml objects from C++ code

I am trying to interact with an qml object from C++ file using QtQuick. But unfortunatelly unsuccessfully for now. Any idea what Im doing wrong? I tried 2 ways how to do it, result of the first was that findChild() returned nullptr, and in second try I am getting Qml comnponent is not ready error. What is the proper way to do it?
main:
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;
// 1-st attempt how to do it - Nothing Found
QObject *object = engine.rootObjects()[0];
QObject *mrect = object->findChild<QObject*>("mrect");
if (mrect)
qDebug("found");
else
qDebug("Nothing found");
//2-nd attempt - QQmlComponent: Component is not ready
QQmlComponent component(&engine, "Page1Form.ui.qml");
QObject *object2 = component.create();
qDebug() << "Property value:" << QQmlProperty::read(object, "mwidth").toInt();
return app.exec();
}
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
ApplicationWindow {
visible: true
width: 640
height: 480
Page1 {
}
Page {
}
}
}
Page1.qml:
import QtQuick 2.7
Page1Form {
...
}
Page1.Form.ui.qml
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
Item {
property alias mrect: mrect
property alias mwidth: mrect.width
Rectangle
{
id: mrect
x: 10
y: 20
height: 10
width: 10
}
}
findChild takes object name as first parameter. But not ID.
http://doc.qt.io/qt-5/qobject.html#findChild.
Here in your code, you are trying to query with id mrect. So it may not work.
Add objectName in your QML and then try accessing with findChild using object name.
Something like below (I did not try it. So chances of compile time errors):
Add objectName in QML
Rectangle
{
id: mrect
objectName: "mRectangle"
x: 10
y: 20
height: 10
width: 10
}
And then your findChild as shown below
QObject *mrect = object->findChild<QObject*>("mRectangle");

WorkerScript access to Controller class

I have a BusyIndicator which should spin while heavy computations are happening and stop when the computations are done.
I thought WorkerScript was the right way to go but but from here, it seems that the secondary (computation thread) in the .js file does not have access to the objects of the primary .qml thread.
This is problematic as all my computations are performed through a Controller C++ defined QObject instantiated by the primary thread.
Here is my code:
main.qml
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.0
import QtQuick.Controls.Styles 1.2
import QtQuick.Dialogs 1.2
import LcQml 1.0
ApplicationWindow
{
id: window
UiController
{
id: uiController
}
WorkerScript
{
id: importScanWorkerScript
source: "importScanWorkerScript.js"
onMessage:
{
busyIndicator.running = false;
}
}
FileDialog
{
id: importScanDialog
visible: false
title: "Import a [scan] file"
folder: "MyScannedScenesFolder"
nameFilters: [ "STL files (*stl)" ]
selectedNameFilter: "STL files (*stl)"
onAccepted:
{
importScanWorkerScript.sendMessage({'filepath': importScanDialog.fileUrl})
busyIndicator.running = true;
}
}
BusyIndicator
{
id: busyIndicator
running: false
anchors.centerIn: parent
}
}
importScanWorkerScript.js
WorkerScript.onMessage = function(message)
{
uiController.onImportScanDevMenuClicked(message.filepath);
WorkerScript.sendMessage()
}
Pb: uiController is not defined in importScanWorkerScript.js.
Should I understand that WorkerScript can only handle simple situations?
As you already noticed, WorkerScript cannot access UI controls. But your separate thread can "talk" to the main UI thread using messages. As for me that works exactly as in all other languages/frameworks. Just send a message from the thread whenever you want to update UI or your object.
For example:
WorkerScript.onMessage = function(message) {
WorkerScript.sendMessage({ 'action': 'start' })
// do some heavy task here
WorkerScript.sendMessage({ 'action': 'finish', 'data': somedata })
}
and so your main qml may look like this:
WorkerScript {
id: myWorker
source: "func.js"
onMessage: {
switch(messageObject.action)
{
case 'start':
spinner.running = true;
uiController.doSomething();
break;
case 'finish':
spinner.running = false;
uiController.doSomethingAnother();
break;
}
}
}

Access to existing QML component from C++

I am a newbie in QML, and cannot resolve a simple issue. I want to get access to the QML components from C++, but I cannot.
The pointer test is always 0. What can be the reason?
The code is the following:
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication &app=reg6::Bonder::BonderGuiApplication::instance();
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QObject* test=engine.rootObjects().first()->findChild<QObject*> ("cameraArea");
test->setProperty("color","black");
return app.exec();
}
main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 1800
height: 900
SplitView
{
anchors.fill: parent
orientation: Qt.Vertical
SplitView {
Layout.fillHeight: true
SplitView {
orientation: Qt.Vertical
width:400
Layout.minimumWidth: 400
Layout.maximumWidth: 500
Camera {
id: cameraArea
height: 400
Layout.maximumHeight: 400
Layout.minimumHeight: 300
}
List {
id: listArea
}
}
Bonder {
id: mainArea
Layout.fillWidth: true
}
Properties {
id: propertiesArea
Layout.minimumWidth: 300
Layout.maximumWidth: 400
}
}
Error {
id: errorArea
Layout.minimumHeight: 100
height: 200
}
}
}
Camera.qml
import QtQuick 2.5
Rectangle {
color: "lightblue"
}
You have to set the objectName property also of the QML component to get a valid pointer to your QObject because T QObject::findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const needs the objectName not the ID

QtQuick app and C++ slots binding error

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