I have qtquick frontend and c++ backend. In Qml I have a checkbox with random initial value. I have a signal emitted onCheckedChanged which is never received. I believe, it is because the component is created before connect statements are made. When user interacts, I can catch those events but I miss the initial value. I cannot make the connections sooner because the QMl engine has to first create the components so I can have a reference to them to make the signal slot connection. So how to find out the initial value? Do I have to make a timer which will emit the value few seconds after startup? Is there a better way?
here is minimum example (when the initial value is true, the slot is never triggered)
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
signal checkboxStateChanged(enabled: bool)
CheckBox{
checked: Math.random() > 0.5
text: "value"
onCheckedChanged: checkboxStateChanged(checked)
}
}
backend.h
#include <QObject>
#include <QDebug>
class Backend: public QObject{
Q_OBJECT
public:
Backend(){}
public slots:
void logChecked(bool checked){
qDebug()<<checked;
}
};
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include "backend.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
const auto root=engine.rootObjects();
QObject *window = root[0];
Backend b;
QObject::connect(window,SIGNAL(checkboxStateChanged(bool)),
&b,SLOT(logChecked(bool)));
return app.exec();
}
I am not sure about the approach you are trying but a cleaner method would be to bind the QML checked value to the c++ class. Then you can handle everything in your backend class which is a lot easier to debug and maintain. So using your example,
#include <QObject>
#include <QDebug>
class Backend: public QObject{
Q_OBJECT
// Add a Q_PROPERTY to bind in QML
Q_PROPERY(Qt::CheckState checked READ getChecked WRITE setChecked NOTIFY checkedChanged)
signals:
void checkedChanged();
public:
Backend() { m_checked = <random_value>;}
// implement the q_property methods
void setChecked(const Qt::CheckState value) {
if (m_checked != value) {
m_checked = value;
emit checkedChanged();
}
}
Qt::CheckState getChecked() const { return m_checked; }
public slots:
void logChecked(bool checked){
qDebug()<<checked;
}
private:
m_checked;
};
Then in QML:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQml 2.12 // Binding
import Backend 1.0
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
signal checkboxStateChanged(enabled: bool)
CheckBox{
id: qmlCheckBox
checked: backend.checked // C++ to QML binding
text: "value" // you can bind this too
}
// QML to C++ binding
Binding {
target: backend
property: "checked"
value: qmlCheckBox.checked
}
}
In main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include "backend.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// Register backend class for qml
qmlRegisterType<Backend>("Backend", 1, 0, "Backend");
engine.rootContext()->setContextProperty("backend", new Backend);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
Related
I have registered an enum in a separate namespace using Q_ENUM_NS. Then I would like to communicate between C++ and QML using signals. In particular, I have a class sending emitting signals where the signature of the signal contains an enum from my namespace. However, QML doesn't seem to recognise the enum value (which is always "undefined").
Here is an example that showcases the problem:
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QObject>
#include <QTimer>
namespace Enums {
Q_NAMESPACE
enum class MyEnum { First, Second, Third };
Q_ENUM_NS(MyEnum)
}
class TestObject : public QObject {
Q_OBJECT
public:
explicit TestObject() {
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout, this, [&](){
auto myEnum = Enums::MyEnum::First;
qDebug () << "CPP" << myEnum;
emit testSignal(myEnum);
});
timer.start();
}
signals:
void testSignal(Enums::MyEnum myEnum);
private:
QTimer timer;
};
#include "main.moc"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterUncreatableMetaObject(Enums::staticMetaObject, "Enums", 1, 0, "Enums", "Error: enums can't be created");
qmlRegisterType<TestObject>("TestObject", 1, 0, "TestObject");
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine.load(url);
return app.exec();
}
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import Enums 1.0
import TestObject 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
TestObject{
id: object
onTestSignal: (myEnum) => { console.debug(myEnum) }
}
}
I would expect QML to recognise the value of the enum and print "Enums.First"
you need to register meta type before registering meta object:
qRegisterMetaType<Enums>("Enums");
Then import it like:
import Enums 1.0
I want to know the line number in editText where the cursor is. How do I get that?
I checked the documentation, and there's no
ps:Is the textedit QML,not QTextEdit
You have to use textDocument and cursorPosition properties in c++:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickTextDocument>
#include <QTextBlock>
class Helper: public QObject{
Q_OBJECT
public:
Q_INVOKABLE int currentLineNumber(QQuickTextDocument *textDocument, int cursorPosition){
if(QTextDocument * td = textDocument->textDocument()){
QTextBlock tb = td->findBlock(cursorPosition);
return tb.blockNumber();
}
return -1;
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Helper helper;
engine.rootContext()->setContextProperty("helper", &helper);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 640
height: 480
visible: true
TextEdit{
id: textEdit
anchors.fill: parent
onCursorPositionChanged: function(){
let line = helper.currentLineNumber(textEdit.textDocument, textEdit.cursorPosition);
console.log(line);
}
}
}
I want get json file from local and send it to QML using this:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine.rootContext()->setContextProperty("text_json", "{\"text1\": \"Loading\"}");
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreated,
&app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection
);
engine.load(url);
return app.exec();
}
But it say that QQmlEngine::setContextForObject(): Object already has a QQmlContext but I don't understand anything from that default file.
I don't have found anything from now.
-- Added Main.qml --
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
id: window
width: 640
height: 480
visible: true
title: qsTr("Stack")
property var text_json: ({"text1": "Loading"})
header: ToolBar {
contentHeight: toolButton.implicitHeight
ToolButton {
id: toolButton
icon.source: "./images/ruby.png"
font.pixelSize: Qt.application.font.pixelSize * 1.6
onClicked: {
drawer.open()
}
}
Label {
text: stackView.currentItem.title
anchors.centerIn: parent
}
}
}
Maybe a bit late, but did you try importing in your Main.qml without version number?
import QtQuick
import QtQuick.Controls
I don't want to expose a slot to QML, but is required to have a slot, because the slot is connected to internal signal.
I marked the slot as private, but the slots is accessible in QML e.g. Code completion/suggestion etc. My CPP Custom Class is registered in the main cpp.
h File:
#ifndef MYQMLTYPE_H
#define MYQMLTYPE_H
#include <QObject>
#include <QTimer>
class MyQMLType : public QObject
{
Q_OBJECT
Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)
public:
explicit MyQMLType(QObject *parent = nullptr);
public slots:
int increment(int value);
private slots:
void hideslot(void);
signals:
void messageChanged();
public:
QString message() const;
void setMessage(const QString& value);
private:
QString m_message;
QTimer *m_timer;
};
#endif // MYQMLTYPE_H
Cpp File:
#include "myqmltype.h"
MyQMLType::MyQMLType(QObject *parent) : QObject(parent)
{
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &MyQMLType::hideslot);
m_timer->start(1000);
}
int MyQMLType::increment(int value)
{
return value + 1;
}
void MyQMLType::hideslot()
{
// private slot
}
QString MyQMLType::message() const {
return m_message;
}
void MyQMLType::setMessage(const QString& value) {
if(m_message != value) {
m_message = value;
messageChanged(); // trigger signal of property change
}
}
Main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "MyQMLType.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<MyQMLType>("com.yourcompany.xyz", 1, 0, "MyQMLType");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
import com.yourcompany.xyz 1.0
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
MyQMLType{
id: myqmltype
}
Rectangle{
id: rect
anchors.centerIn: parent
width: 100
height: 100
color: myqmltype.hideslot(); // This slot is avaiable in qml
}
}
Question:
Why is the private slot exposed to QML?
How to hide the slot?
Edit
Optimized Cpp File with private slot
Your premise is wrong
I don't want to expose a slot to QML, but is required to have a slot, because the slot is connected to internal signal.
Using the new Qt 5 connect syntax with pointer to member function you don't need a function to be a slot to be able to connect to it.
Just put your hideslot declaration in the private section of your class and you'll have what you want. It won't be exposed to QML and you would still be able to connect to it in C++.
As to why the private slot is exposed to QML, it is because all slots and Q_INVOKABLE functions are exposed to QML, regardless of their access.
I have a small issue while dynamically changing the language of my application on the fly.
The language can be selected from a Menu in a MenuBar, whenever I switch to another language the changes are immediately visible in the UI, except on the MenuBar. If I do hover the MenuBar or resize the window a bit the MenuBar gets updated.
Here's the problem in action:
I've been trying to find a refresh function on QML types but couldn't find any.
Question
How to ensure a MenuBar gets refreshed after changing language?
Edit
Here's the full source of my application:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "Translator.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
Translator translator(engine);
engine.rootContext()->setContextProperty("translator", &translator);
return app.exec();
}
Translator.h
#ifndef TRANSLATOR_H
#define TRANSLATOR_H
#include <QObject>
#include <QTranslator>
#include <QGuiApplication>
#include <QQmlEngine>
class Translator : public QObject
{
Q_OBJECT
public:
explicit Translator(QQmlEngine &engine, QObject *parent = nullptr);
signals:
void languageChanged();
public slots:
void setLanguage(const QString &language);
private:
QQmlEngine &m_engine;
QTranslator m_trans_fr;
};
#endif // TRANSLATOR_H
Translator.cpp
#include "Translator.h"
Translator::Translator(QQmlEngine &engine, QObject *parent) : QObject(parent),
m_engine(engine)
{
}
void Translator::setLanguage(const QString &language)
{
if (language == QString("fr"))
{
m_trans_fr.load("WipeoutViewer_fr.qm");
qApp->installTranslator(&m_trans_fr);
}
else if (language == QString("en"))
{
qApp->removeTranslator(&m_trans_fr);
}
m_engine.retranslate();
emit languageChanged();
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Wipeout viewer")
menuBar: MenuBar {
Menu {
title: qsTr("&File")
MenuItem {
text: qsTr("&Quit")
onTriggered: Qt.quit()
}
}
Menu {
title: qsTr("&Language")
MenuItem {
text: qsTr("&English")
onTriggered: translator.setLanguage("en")
}
MenuItem {
text: qsTr("&French")
onTriggered: translator.setLanguage("fr")
}
}
Menu {
title: qsTr("&Help")
MenuItem {
text: qsTr("&About")
}
}
}
}