I have a QML plugin which has a QThread inside. It is used to capture and display videoframes into the QMLview.
The problem is: how can I stop this thread when the window is closed?
QML
Repeater {
model: 8
CVCamScreen {
Layout.fillWidth: true
Layout.fillHeight: true
url: Controller.urlCanal(index + 1)
CustomBorder {
commonBorder: true
color: "#228e14"
commonBorderWidth: 3
}
}
}
C++ Component
class CVCamScreen : public QQuickPaintedItem
{
CVCamScreen::CVCamScreen(QQuickItem *parent):
QQuickPaintedItem(parent)
{
m_worker = new CamWorker(this);
}
CVCamScreen::~CVCamScreen()
{
m_worker->stop();
delete m_worker;
}
private:
CamWorker* m_worker; // inherits from QThread;
}
The current behavior is: the thread keeps running after the window is closed which means the CVCamScreen instances are not being destroyed.
Related
My goal is to create a custom Component (lets call it ComponentLoader) which can instantiate another component (lets call it DelegateComponent) via delegate.
The problem is that the instance of DelegateComponent isn't displayed after is was created (white screen). Note: tst_component is loaded message IS present which means that DelegateComponent instance was actually created.
Here is a minimal example:
main.qml
ApplicationWindow {
id: mainWindow
width: 640
height: 480
visible: true
Component {
id: tst_component
Rectangle {
anchors.fill: parent
Component.onCompleted: {
console.debug("tst_component is loaded");
}
Label {
text: "hello world"
anchors.centerIn: parent
}
}
}
// doesn't work
ComponentLoader {
anchors.fill: parent
delegate: tst_component
}
// works
// Loader {
// anchors.fill: parent
// sourceComponent: tst_component
// }
}
componentloader.h + componentloader.cpp
#pragma once
#include <QQuickItem>
class ComponentLoader : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
public:
QQmlComponent* delegate() const;
void setDelegate(QQmlComponent*);
signals:
void delegateChanged();
private:
void generate();
protected:
void componentComplete() override;
private:
QQmlComponent* mDelegate = nullptr;
};
// componentloader.cpp
#include "componentloader.h"
#include <QtWidgets/QtWidgets>
#include <QtQmlModels/QtQmlModels>
#include <QQuickWindow>
QQmlComponent* ComponentLoader::delegate() const
{
return mDelegate;
}
void ComponentLoader::setDelegate(QQmlComponent* delegate)
{
if (delegate != mDelegate)
{
mDelegate = delegate;
emit delegateChanged();
}
}
void ComponentLoader::componentComplete()
{
QQuickItem::componentComplete();
generate();
}
void ComponentLoader::generate()
{
QQmlEngine* engine = qmlEngine(this);
QQmlContext* root_ctx = engine->rootContext();
QQmlContext* ctx = new QQmlContext(root_ctx);
QObject* item = mDelegate->create(ctx);
QQuickItem* quickItem = qobject_cast<QQuickItem*>(item);
QQuickItem* quickParent = qobject_cast<QQuickItem*>(parent());
quickItem->setParent(quickParent);
quickItem->setParentItem(quickParent);
}
Your quickParent pointer is taking the QObject parent() value instead of the QQuickItem parentItem() value.
Simply changing it to this worked for me when I tried your code:
QQuickItem* quickParent = qobject_cast<QQuickItem*>(parentItem());
I also don't think you need to call setParent() at all. Just setParentItem().
I am doing a project, basically what I want to do is to synchronize a property theProperty between 2 boxes in 2 QML files(2 boxes both own that property), I bind theProperty to a C++ Q_PROPERTY, so by binding the 2 boxes to the same C++ Q_PROPERTY, the synchronization can be achieved.
Here are my codes in Box A and B. theProperty can be independently changed by Box A and B
Box_A {
id: box_A
// sth
Binding { target:box_A; property: "theProperty"; value:model.CppModel.theProperty }
onThePropertyChanged: {
model.CppModel.theProperty = theProperty
}
}
Box_B {
id: box_B
// sth
Binding { target:box_B; property: "theProperty"; value:model.CppModel.theProperty }
onThePropertyChanged: {
model.CppModel.theProperty = theProperty
}
}
In cpp:
Class Item: QObject{
Q_OBJECT
Q_PROPERTY(bool theProperty READ theProperty WRITE theProperty NOTIFY theProperty Changed)
//sth
}
Within Box_A and B, there is a mouse area by which the theProperty can be changed:
MouseArea{
onClicked: theProperty=!theProperty
}
The problem is that once I change theProperty in either box A or B, the qt creator complains that loop binding detected for value: model.CppModel.theProperty at the other side, is there a way of walking around this problem?
Why don't you just do:
Box_A {
id: box_A
// sth
theProperty:model.CppModel.theProperty
}
Did that not work for you?
I have a timer in qml (view in StackView) which I try to start from C++ code, calling javascript function. But my timer is never triggered. What Am I doing wrong? My flow is a.qml -> b.qml (on Button clicked)
File b.qml :
Item {
function connectionConfirmed() {
myTimer.start()
console.log("started timer")
}
Timer {
interval: 1000; running: false; repeat: false
id: myTimer
onTriggered: {
console.log("timer triggered")
}
}
}
file main.qml:
ApplicationWindow {
id: root
visible: true
width: 320
height: 530
StackView {
id: stackView
initialItem: "qrc:/a.qml"
anchors.fill: parent
}
}
file a.qml
MouseArea{
anchors.fill: parent
onClicked: {
stackView.push("qrc:/b.qml")
}
}
C++ part:
connect(&myObjectInstance, &X::somethingHappend, this, [this](){
QQmlComponent component(&engine_, "qrc:/b.qml");
QObject *obj = component.create();
QVariant returnedValue;
QMetaObject::invokeMethod(obj, "connectionConfirmed",
Q_RETURN_ARG(QVariant, returnedValue));
delete obj;
});
Output is:
started timer
When I tried to set running: true timer is triggered, does it mean that I am not able to start the timer from JS function?
The problem is caused because you are assuming that the component created on the C++ side is the same that is created on the QML side. The .qml file is the source code, when you invoke it, they generate a different object each time.
Explanation of the behavior:
connect(&myObjectInstance, &X::somethingHappend, this, [this](){
QQmlComponent component(&engine_, "qrc:/b.qml");
QObject *obj = component.create();
QVariant returnedValue;
QMetaObject::invokeMethod(obj, "connectionConfirmed",
Q_RETURN_ARG(QVariant, returnedValue));
delete obj;
});
In this code you are first creating the component, and then you create the object, call the function written in js, that function has just been executed and print the message, and after that you delete it, so when you delete it, it will never fire. new Timer, for that reason it seemed that the one that printed the first message was the item created in QML, but it is not, the new item printed it.
Calling a C ++ QML function brings these problems because many times you do not access the object you want.
The recommendation indicated by Qt is, on the contrary, to make the connection on the QML side, for this you must export the C ++ object using setContextProperty().
In the following example using its implementation of QML, we create a class that has a signal called mysignal, it is triggered every 5 seconds for the purpose of testing. An object of that class is exported to QML and connected so that it invokes the js function from QML
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
class Helper: public QObject{
Q_OBJECT
public:
using QObject::QObject;
signals:
void mysignal();
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Helper helper;
engine.rootContext()->setContextProperty("helper", &helper);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&helper](){
emit helper.mysignal();
});
timer.start(5000);
return app.exec();
}
#include "main.moc"
b.qml
import QtQuick 2.0
Item {
id: it
function connectionConfirmed() {
myTimer.start()
console.log("started timer")
}
Timer {
interval: 1000; running: false; repeat: false
id: myTimer
onTriggered: {
console.log("timer triggered")
}
}
Connections{
target: helper
onMysignal: it.connectionConfirmed()
}
}
Finally, in a StackView, items are created and destroyed each time a page is changed.
I'm trying to make a very basic timetable app just to learn the basics of Qt. I've researched this quite a bit and cant seem to get an answer to my problem, I emit the signal when I change the value of my subject but the emitting signal does not update the GUI. It definitely is changing the value in the code but the QML GUI does not update it during run time.
I have a class called Day with the following Q_PROPERTY's:
Q_PROPERTY(Period *getPeriod READ getPeriod WRITE setPeriod NOTIFY periodChanged)
Q_PROPERTY(QString getDay READ getDay WRITE setDay NOTIFY dayChanged)
and a member to hold the periods
Period *m_period = new Period[10]; //Of type Period (my other class) which holds my subject string
I also have getters and setters for the days and periods(as seen in the Q_PROPERTY) and these two for setting/getting the subject:
Q_INVOKABLE QString getSubject(int t){
return m_period[t].getSub();
};
Q_INVOKABLE void setSubject(int t, QString subj){
m_period[t].setSub(subj);
emit periodChanged();
};
With the following signals:
void periodChanged();
void dayChanged();
I also have a class called Period with the following Q_PROPERTY:
Q_PROPERTY(QString getSub READ getSub WRITE setSub NOTIFY subChanged)
and a member to hold the subject names:
QString subject;
My Period class hold the functions called in day to actually change the QString subject:
QString getSub(){
return subject;
};
void setSub(QString sub){
subject = sub;
emit subChanged();
};
With the following signal:
void subChanged();
So surely when the subject gets changed using setSubject() in my QML, shouldn't it be emitting the periodChanged() and the subChanged() signals, which should update the GUI? I thought it would but it doesn't seem to be working.
For reference, this is basically how I implemented it in QML:
Label { text: monday.getSubject(2) } //How I display the Subject, the parameter being the period number
Button{
text: "Enter"
onClicked:{
monday.setSubject(2, "RANDOM_TEXT") //How I set the subject at run time, with the first argument being the period number and second the text I want to change it to
}
Here are the main and class files:
main.cpp
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qmlRegisterType<Day>("Timetable", 1,0, "Day");
qmlRegisterType<Period>("Timetable", 1,0, "Period");
QQmlApplicationEngine engine;
Day monday;
engine.rootContext()->setContextProperty("monday", &monday);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QQmlComponent component(&engine, QUrl::fromLocalFile("main.qml"));
component.create();
return app.exec();
}
Day.h
class Day : public QObject
{
Q_OBJECT
Q_PROPERTY(Period *getPeriod READ getPeriod WRITE setPeriod NOTIFY periodChanged)
private:
Period *m_period = new Period[10];
public:
Day(QObject *parent = 0);
~Day();
Period* getPeriod();
Q_INVOKABLE void setPeriod(Period *p);
Q_INVOKABLE QString getSubject(int t){
return m_period[t].getSub();
};
Q_INVOKABLE void setSubject(int t, QString subj){
m_period[t].setSub(subj);
emit periodChanged();
};
signals:
void periodChanged();
};
Period.h
class Period: public QObject
{
Q_OBJECT
Q_PROPERTY(QString getSub READ getSub WRITE setSub NOTIFY subChanged)
private:
QString subject;
public:
Period(QObject *parent = 0);
~Period();
QString getSub(){
return subject;
};
void setSub(QString sub){
subject = sub;
emit subChanged();
};
signals:
void subChanged();
};
main.qml
ApplicationWindow {
id: mainWindow
visible: true
title: "Timetable App"
property int margin: 11
width: mainLayout.implicitWidth + 2 * margin
height: mainLayout.implicitHeight + 2 * margin
minimumWidth: 800
minimumHeight: 600
ColumnLayout {
id: mainLayout
anchors.fill: parent
anchors.margins: margin
GroupBox{
id: timetable
title: "Timetable"
Layout.fillWidth: true
Layout.fillHeight: true
GridLayout {
id: gridLayout
rows: 11
flow: GridLayout.TopToBottom
anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
Label { }
Label { text: "8:00" }
Label { text: ...} // Added Labels for times from 8:00 to 17:00
Label { text: "Monday" }
Label { text: monday.getSubject(0) }
Label { text: monday.getSubject(1) }
Label { text: ...} // Added Labels to display subjects for monday at different times, also did the same for the rest of the week
}
}
RowLayout{
Button{
text: "Enter"
onClicked:{
monday.setSubject(1, "RANDOM_TEXT") // Set the second period of monday to "RANDOM_TEXT"
console.log(monday.getSubject(1)) // To check if actually set
}
}
}
}
}
Your problem is that you are retrieving values via method calls in QML, not via property bindings.
A QML code like this should update
text: monday.getPeriod.getSub
Obviously calling properties "getSomething" is a bit weird.
Now, your m_period suggests that a single Day object will have more than one Period, which would suggest that you might want a list property instead of a single Period object. Something like
Q_PROPERTY(QList<Period*> periods READ getPeriods NOTIFY periodsChanged);
Using that from QML a bit like this
text: monday.periods[0].getSub
Or even with e.g. a Repeater
Label { text: "Monday" }
Repeater {
model: monday.periods
Label {
text: modelData.getSub
}
}
Not related to your update problem but also important to consider:
NOTIFY signals trigger binding updates, so you don't want to emit them unnecessarily. I.e. setters that emit those should first check if the new value is actually different than the old value and only emit if they are.
I've got following code:
main.cpp
QDeclarativeView *qmlView = new QDeclarativeView();
qmlView->setSource(QUrl("qrc:/nav.qml"));
ui->nav->addWidget(qmlView);
Blockschaltbild bild;
QObject *value = qmlView->rootObject();
QObject::connect(value, SIGNAL(testSig()), &bild, SLOT(BlockSlot()));
The signals and slots connect correctly. (QObject::connect returns "true")
qml file:
Rectangle {
id: rectangle1
....
signal testSig()
MouseArea{
id: mousearea
anchors.fill: parent
onEntered: parent.color = onHoverColor
onExited: parent.color = parent.buttonColor
onClicked: {
rectangle1.testSig()
console.log("Button clicked")
}
}
}
This is where the slot is located:
Blockschaltbild.h
class Blockschaltbild: public QObject
{
Q_OBJECT
public slots:
void BlockSlot(){
cout << "Slot is working" << endl;
}
public:
....
}
If I click on the mouse area, the console shows "Button clicked" but not "Slot is working".
I use Qt 4.8.4 with QtQuick 1.1. Where is my mistake?
If you simply require to work with your Blockschaltbild object in QML, you can also decide against a loose coupling with signal and slots and simply pass your object as a context parameter, to make it available in QML.
QDeclarativeView *qmlView = new QDeclarativeView();
qmlView->setSource(QUrl("qrc:/nav.qml"));
ui->nav->addWidget(qmlView);
Blockschaltbild* bild;
QObject *value = qmlView->engine().rootContext()->setContextProperty("bild", bild);
You can then call the BlockSlot() slot of your object from QML with:
Rectangle {
id: rectangle1
....
MouseArea{
id: mousearea
anchors.fill: parent
onEntered: parent.color = onHoverColor
onExited: parent.color = parent.buttonColor
onClicked: {
bild.BlockSlot() // call BlockSlot of "bild" context property
console.log("Button clicked")
}
}
}
It is also possible to use qmlRegisterType, which allows you to create instances of your Blockschaltbild class with QML. See here for more information.