WorkerScript access to Controller class - c++

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;
}
}
}

Related

Update c++ model on qml delegate visual model change

There's a simple QStandardItemModel defined in c++ which I am displaying in a QML ListView via custom Delegates and a DelegateModel. The ListView can be reordered via Drag'n Drop:
// The DropArea is part of the delegate `comp_container`
DropArea{
anchors{fill: parent}
keys: ["pageitem"]
onEntered: {
let from = drag.source.DelegateModel.itemsIndex
let to = dragAreaPage.DelegateModel.itemsIndex
if ( pageItemDragOperationStartIndex === -1 ){
pageItemDragOperationStartIndex = from
}
pageItemDragOperationFinalIndex = to
console.log(from + "->" + to)
visualModel.items.move(from,to)
}
}
Here is the delegate model and pageproxymodel is the c++ model.
DelegateModel {
id: visualModel
model: pageproxymodel
delegate: comp_container
}
How do I want to update the c++ model?
The delegate's top level item is a MouseArea and I handle the reordering in the release handler:
onReleased: {
if ( pageItemDragOperationStartIndex !== -1 && pageItemDragOperationFinalIndex !== -1 ){
console.log("Page item final drag operation: " + pageItemDragOperationStartIndex + "->" + pageItemDragOperationFinalIndex)
pageproxymodel.move(pageItemDragOperationStartIndex, pageItemDragOperationFinalIndex)
pageItemDragOperationStartIndex = -1
pageItemDragOperationFinalIndex = -1
}
}
The c++ model's move function forwards the call to this handler:
bool PageModel::moveRow(const QModelIndex &sourceParent,
int sourceRow,
const QModelIndex &destinationParent,
int destinationChild)
{
if ( sourceRow < 0 || sourceRow > rowCount()-1 ||
destinationChild < 0 || destinationChild > rowCount() )
{
return false;
}
beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationChild);
QList<QStandardItem*> rowItems = takeRow(sourceRow);
insertRow(destinationChild, rowItems);
endMoveRows();
return true;
}
With the above c++ model code, it crashes at the release handler in QML:
I've tried other things to see the effect, no crashes, but also not the expected behaviour.
deleting a single row (which deletes 2 (!) rows in the QML ListView)
deleting a single row without begin/end calls (deletes 1 rows in the QML ListView, but can't be right)
remove and insert a single row without begin/end calls (QML ListView looks fine for a while but comes out of sync after a few moves)
Basically all I want to do is to save the ListView state via the c++ model, after all that is a standard use case and something simple must be wrong on my side, yet I can't see it.
One thing I like to do with DelegateModel makes use of DelegateModelGroup. By declaring a group named "all", it introduces an attached property allIndex which is useful for tracking an item after it has been reordered. The following example implements a DelegateModel with both MouseArea and DropArea. When in dragging mode, I disable all MouseArea so that the DropArea can have a chance at responding.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
property int activeMouseArea: -1
ListView {
id: listView
width: 420
height: parent.height
model: SampleDelegateModel { }
ScrollBar.vertical: ScrollBar {
width: 20
policy: ScrollBar.AlwaysOn
}
}
footer: Text { id: dbg }
}
// SampleData.qml
import QtQuick
import QtQuick.Controls
ListModel {
ListElement { name: "Steve Jobs" }
ListElement { name: "Jeff Bezos" }
ListElement { name: "Bill Gates" }
ListElement { name: "Elon Musk" }
}
// SampleDelegateModel.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
DelegateModel {
id: delegateModel
model: SampleData { }
delegate: SampleDelegate { }
groups: [
DelegateModelGroup {
id: allItems
name: "all"
includeByDefault: true
}
]
filterOnGroup: "all"
function moveItem(from, to) {
dbg.text = `Debugging: moveItem(${from},${to})`;
allItems.move(from, to);
}
}
// SampleDelegate.qml
import QtQuick
import QtQuick.Controls
import QtQml.Models
Rectangle {
property int allIndex: DelegateModel.allIndex
width: 400
height: labelText.height + 20
border.color: "grey"
z: mouseArea.drag.active || mouseArea.pressed ? 2 : 1
property int dragTo: -1
Drag.active: mouseArea.drag.active
Text {
id: labelText
anchors.centerIn: parent
text: allIndex + ": [" + index + "] " + name
}
DropArea {
anchors.fill: parent
onEntered: drag.source.dragTo = allIndex
}
MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: parent
property point startPoint
enabled: activeMouseArea === -1
onPressed: {
activeMouseArea = allIndex;
dragTo = -1;
startPoint = Qt.point(parent.x, parent.y);
}
onReleased: {
activeMouseArea = -1;
[parent.x,parent.y] = [startPoint.x, startPoint.y];
Qt.callLater(delegateModel.moveItem, allIndex, dragTo);
}
}
}
You can Try it Online!
Found the mistake:
The pageItemDragOperationStartIndex and pageItemDragOperationFinalIndex variables where part of each delegate, but not of the page.
Also, as was pointed out in the comments, using a QStandardItemModel it is not necessary to call the begin/end functions. Now it works like a charm.

Black screen after existing FullScreen mode

I am trying to make a such thing:
I have a main window with a single button.
After pressing this button two semi transparent windows appear on all screens. They are in a FullScreen mode.
After 4 seconds screens dissapear.
Everything is ok. But when I cklick one of the screens, during process of disappearing, it becomes totaly black. How can I fix it?
// main.qml
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Controls 2.2
Window {
id: main
visible: true
width: 100
height: 50
title: "Hello Splash World"
Button {
anchors.fill: parent
text: "Show splash"
onClicked: {
for (var i = 0; i < Qt.application.screens.length; ++i) {
var component = Qt.createComponent("SplashScreen.qml");
var window = component.createObject(main, {screen: Qt.application.screens[i]});
window.height = Qt.application.screens[i].height
window.width = Qt.application.screens[i].width
window.showSplash()
}
}
}
}
// SplashScreen.qml
import QtQuick 2.10
import QtQuick.Controls 2.2
ApplicationWindow {
id: splash
flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WA_TranslucentBackground
color: "transparent"
Timer {
running: true
interval: 4000
onTriggered: hideSplash()
}
function showSplash() {
appearAnimation.start()
}
function hideSplash() {
disappearAnumation.start()
}
background: Rectangle {
id: bg
color: "black"
opacity: 0.8
}
SequentialAnimation {
id: appearAnimation
PropertyAction { target: splash; property: "visibility"; value: ApplicationWindow.FullScreen }
NumberAnimation { target: bg; property: "opacity"; duration: 1000; to: 0.8 }
}
SequentialAnimation {
id: disappearAnumation
NumberAnimation { target: bg; property: "opacity"; duration: 2000; to: 0 }
PropertyAction { target: splash; property: "visibility"; value: ApplicationWindow.Hidden }
}
}
I've come across some strange problems with repainting during further development of my program. For example, changing the size of the main form led to black form to. The solution I've found is to use OpenGL for rendering. You can do it by inserting this code:
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
Denis Popov answer is correct, but in this mode my application was a bit laggy. Problem wasn't occuring if mode was set to:
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
From the other hand this time I was getting following warning in the output everytime I was creating window:
DXGI WARNING: IDXGIFactory::CreateSwapChain: Blt-model swap effects (DXGI_SWAP_EFFECT_DISCARD and DXGI_SWAP_EFFECT_SEQUENTIAL) are legacy swap effects that are predominantly superceded by their flip-model counterparts (DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL and DXGI_SWAP_EFFECT_FLIP_DISCARD). Please consider updating your application to leverage flip-model swap effects to benefit from modern presentation enhancements. More information is available at http://aka.ms/dxgiflipmodel. [ MISCELLANEOUS WARNING #294: ]
Best solution I come up with so far is to run application in debug mode with one flag and in release/deployment with another:
#ifdef QT_DEBUG
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
#else
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
#endif

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
}

QML : Navigation between qml pages from design perception

We need to develop a QtQuick project, where we have about 100 screens.
I had tried to make a demo project for the navigation which has three screens on button click. I had used the concepts of 'States' in the navigation between the pages. Initially I tried the same using 'Loader' but loader was not able to retain the previous state of page, it was re-loading the entire page during the navigation.
Below is the code snippet of main.qml
// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5
import QtQuick 1.1
Rectangle {
id:main_rectangle
width: 360
height: 640
Page1{
id:page1
}
Page2{
id:page2
}
Page3{
id:page3
}
states: [
State {
name: "page2"
PropertyChanges { target: page3; visible:false; }
PropertyChanges { target: page1; visible:false; }
PropertyChanges { target: page2; visible:true; }
},
State {
name: "page1"
PropertyChanges { target: page3; visible:false; }
PropertyChanges { target: page2; visible:false; }
PropertyChanges { target: page1; visible:true; }
},
State {
name: "page3"
PropertyChanges { target: page1; visible:false; }
PropertyChanges { target: page2; visible:false; }
PropertyChanges { target: page3; visible:true; }
}
]
}
This runs well with the small POC with three screens, but its not feasible to define states for 100 screens.
From designing aspect we concluded to make a C++ controller we controls the states, visibility of various pages.
Need suggestions how to implement the 'State' logic in C++.
Here is the simplest solution in plain QML, using a configurable page list (like a model) + a Repeater + Loader items to not load everything at startup (lazy instanciation) and not destroy a page after hiding it (to not have to reload it if we come back to it) :
import QtQuick 1.1
Rectangle {
id: main_rectangle;
width: 360;
height: 640;
// Put the name of the QML files containing your pages (without the '.qml')
property variant pagesList : [
"Page1",
"Page2",
"Page3",
"Page4",
"Page5"
];
// Set this property to another file name to change page
property string currentPage : "Page1";
Repeater {
model: pagesList;
delegate: Loader {
active: false;
asynchronous: true;
anchors.fill: parent;
visible: (currentPage === modelData);
source: "%1.qml".arg(modelData)
onVisibleChanged: { loadIfNotLoaded(); }
Component.onCompleted: { loadIfNotLoaded(); }
function loadIfNotLoaded () {
// to load the file at first show
if (visible && !active) {
active = true;
}
}
}
}
}
Hope it helps !
I suggest to use StackView from Qt Quick Components. Here is its documentation.