How to hide C++ Slot in Qml / Private Slots - qt

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.

Related

QML doesn't recognise enums registered in a separate namespace

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

Update a MapCircle on QML using a signal from C++

I'm trying to update a MapCircle in QML from a signal in C++ and I'm veen having several issues with it all day.
In my class I have a Q_PROPERTY which is read only and holds the GPS positions of 4 UAVs in a QVariantList
class GCS: public QObject
{
Q_PROPERTY(QVariantList getUavPosition READ getUavPosition NOTIFY uavPositionSet)
public:
QVariantList getUavPosition() ;
signals:
void uavPositionSet();
public slots:
void setUavPosition();
void triggerPosition();
private:
QVariantList connected_uavs;
QVector<QGeoCoordinate> uav_positions;
};
I then define the functions as:
void GCS::setUavPosition()
{
double i = 0.0;
QGeoCoordinate uav_id;
uav_id.setLatitude(0.5);
uav_id.setLongitude(0.5 + i);
uav_id.setAltitude(5);
uav_positions.insert(0, uav_id);
connected_uavs.append( QVariant::fromValue(QGeoCoordinate(uav_positions[0].latitude(), uav_positions[0].longitude())));
i+=0.15;
emit uavPositionSet();
}
QVariantList GCS::getUavPosition()
{
return connected_uavs;
}
void GCS::triggerPosition()
{
setUavPosition();
ROS_INFO("Pos trig");
}
In my main function, I connect triggerPosition to a Timer so as to update the position periodically
int main(int argc, char *argv[])
{
ros::init(argc, argv, "planner");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
GCS gcs;
context->setContextProperty("planner", &gcs);
engine.load(QUrl(QStringLiteral("qrc:/planner.qml")));
QTimer *timer = new QTimer();
timer->setInterval(1000);
QObject::connect(&gcs, SIGNAL(uavPositionSet()), &gcs, SLOT(setUavPosition()));
QObject::connect(timer, SIGNAL(timeout()), &gcs, SLOT(triggerPosition()));
timer->start();
return app.exec();
}
However, when I run my program, there's a slight delay, my mouseArea becomes unusable and the program crashes. When I try to print the longitude to see if it updates, The initial value is printed out multiple times to the terminal but then the program crashes and there's is no MapCircle present on the map
The relevant part of My Qml file looks like this:
Map{
id: map
anchors.fill:parent
plugin: mapPlugin
center: QtPositioning.coordinate(0.5, 0.5)
zoomLevel:50
MapCircle{
id:uavPos
radius:2
color:'black'
border.width:3
}
Connections{
id:uavConnection
target: planner
onUavPositionSet:{
var data = planner.getUavPosition
uavPos.center = QtPositioning.coordinate(data[0].latitude, data[0].longitude)
console.log(data[0].longitude)
}
}
}
Can someone please kindly point me in the right direction here?
If you are going to handle information from several elements then it is better to use a model (together with a Repeater to create several elements), so it is only necessary to modify the role of an item:
gcs.h
#ifndef GCS_H
#define GCS_H
#include <QObject>
class QStandardItemModel;
class QAbstractItemModel;
class GCS: public QObject
{
Q_OBJECT
Q_PROPERTY(QObject* uavModel READ uavModel CONSTANT)
public:
enum UAVRoles {
PositionRole = Qt::UserRole + 1000
};
GCS(QObject *parent=nullptr);
QObject *uavModel() const;
public Q_SLOTS:
void triggerPosition();
private:
QStandardItemModel* m_uavModel;
};
#endif // GCS_H
gcs.cpp
#include "gcs.h"
#include <QGeoCoordinate>
#include <QStandardItemModel>
#include <random>
GCS::GCS(QObject *parent):
QObject(parent), m_uavModel(new QStandardItemModel(this))
{
m_uavModel->setItemRoleNames({{PositionRole, "position"}});
for(int i =0; i < 4; i++){
QStandardItem *item = new QStandardItem;
item->setData(QVariant::fromValue(QGeoCoordinate()), PositionRole);
m_uavModel->appendRow(item);
}
}
QObject *GCS::uavModel() const{
return m_uavModel;
}
void GCS::triggerPosition(){
std::mt19937 rng;
rng.seed(std::random_device()());
std::normal_distribution<> dist(-0.0001, +0.0001);
if(QStandardItem *item = m_uavModel->item(0)){
QGeoCoordinate uav_id;
uav_id.setLatitude(0.5 + dist(rng));
uav_id.setLongitude(0.5 + dist(rng));
uav_id.setAltitude(5);
item->setData(QVariant::fromValue(uav_id), PositionRole);
}
}
main.cpp
#include "gcs.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
GCS gcs;
QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();
context->setContextProperty("planner", &gcs);
const QUrl url(QStringLiteral("qrc:/planner.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);
QTimer timer;
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout, &gcs, &GCS::triggerPosition);
timer.start();
return app.exec();
}
planner.qml
import QtQuick 2.14
import QtQuick.Window 2.14
import QtLocation 5.14
import QtPositioning 5.14
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Plugin {
id: mapPlugin
name: "osm"
}
Map{
id: map
anchors.fill:parent
plugin: mapPlugin
center: QtPositioning.coordinate(0.5, 0.5)
zoomLevel:50
MapItemView{
model: planner.uavModel
delegate: MapCircle{
id:uavPos
radius: 2
color:'black'
border.width:3
center: QtPositioning.coordinate(model.position.latitude, model.position.longitude)
}
}
}
}

Receiving c++ signals with arguments in QML QList<QPair>

I'm trying to figure out how to get QList> from C++ signal in QML, i'm only getting either QVariant(RecordList, ) or QVariant(QList, ). Tried with different supported sequence type and they work perfectly (QList. I'll appreciate if somebody can help me to understand my error. Kind regards.
jsonreaderasync.h
typedef QPair<qreal,qreal>Record;
typedef QList<Record>RecordList;
class JsonReaderAsync : public QObject
{
Q_OBJECT
public:
explicit JsonReaderAsync(QObject *parent = nullptr);
Q_INVOKABLE void start(const QString& fileName);
signals:
void started();
//void finished(QList<qreal> record);
void finished(RecordList record);
//void finished(QList<Record> record);
};
jsonreaderasync.cpp
#include <QDebug>
#include "jsonreaderasync.h"
Q_DECLARE_METATYPE(Record)
Q_DECLARE_METATYPE(RecordList)
//Q_DECLARE_METATYPE(QList<Record>)
JsonReaderAsync::JsonReaderAsync(QObject *parent) : QObject(parent)
{
qRegisterMetaType<Record>("Record");
qRegisterMetaType<RecordList>("RecordList"); // qml prints QVariant(RecordList, )
//qRegisterMetaType<QList<Record>>("QList<Record>"); //qml prints QVariant(QList<Record>, )
}
void JsonReaderAsync::start(const QString &fileName)
{
QList<Record> record;
record.append(qMakePair(1,1));
record.append(qMakePair(1,2));
record.append(qMakePair(1,3));
// QList<qreal> foo;
// foo.append(1);
// foo.append(1);
// foo.append(1);
emit finished(record);
}
main.cpp
#include "jsonreaderasync.h"
typedef QPair<qreal,qreal>Record;
typedef QList<QPair<qreal,qreal>>RecordList;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
JsonReaderAsync* dataReaderAsync = new JsonReaderAsync();
engine->rootContext()->setContextProperty("JsonReaderAsync", dataReaderAsync);
engine->load(QUrl(QStringLiteral("main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.13
import QtQuick.Window 2.13
import QtQuick.Controls 2.12
import QtCharts 2.3
Window {
id: appWindow
visible: true
minimumWidth : 400
minimumHeight: 400
Connections
{
target: JsonReaderAsync
onStarted:{
console.log("onStarted")
}
onFinished:{
console.log("onFinished")
console.log(record)
console.log(record[0])
}
}
Button {
width : 40
height: 40
anchors.centerIn: parent
onClicked: {
JsonReaderAsync.start("")
}
}
}
In QML, the meta-type system is the only way that QML engine can access C++ structures from a QML environment.
That is, only Predefined C++ Types and custom objects that have Q_PROPERTY declarations could access from QML environment.
Here's my recommend (simplest) modification:
jsonreaderasync.h
#include <QObject>
#include <QVariant>
class Record : public QPair<qreal, qreal> {
Q_GADGET
Q_PROPERTY(qreal first MEMBER first CONSTANT FINAL)
Q_PROPERTY(qreal second MEMBER second CONSTANT FINAL)
public:
Record() = default;
Record(qreal a, qreal b) : QPair<qreal, qreal>(a, b) {}
};
class JsonReaderAsync : public QObject
{
Q_OBJECT
public:
explicit JsonReaderAsync(QObject *parent = nullptr);
Q_INVOKABLE void start(const QString& fileName);
signals:
void started();
void finished(QVariantList record); // RecordList
};
jsonreaderasync.cpp
#include <QDebug>
#include "jsonreaderasync.h"
JsonReaderAsync::JsonReaderAsync(QObject *parent) : QObject(parent)
{
qRegisterMetaType<Record>("Record");
}
void JsonReaderAsync::start(const QString &fileName)
{
QVariantList record;
record.append(QVariant::fromValue(Record(1,1)));
record.append(QVariant::fromValue(Record(1,2)));
record.append(QVariant::fromValue(Record(1,3)));
emit finished(record);
}
Now you can access records from QML:
onFinished: {
for (var i = 0; i < record.length; ++i)
console.log(record[i].first + "->" + record[i].second);
}
Note that converting between C++ objects and QML objects does incur a bit overhead, if these codes are performance sensitive, please consider using C++ Data Models.
While #GPBeta solution works, I wanted more flexibility for Qml supported pairs. I tried to work with templates, but Q_GADGET doesn't support it. There might be a smart wrapper (Template + QVariant) solution, I guess... Nonetheless, here is my approach to the problem:
class PairQml {
Q_GADGET
Q_PROPERTY(QVariant first MEMBER first CONSTANT FINAL)
Q_PROPERTY(QVariant second MEMBER second CONSTANT FINAL)
public:
PairQml() = default;
PairQml(QVariant f, QVariant s): first(f), second(s) {}
QVariant first;
QVariant second;
};
Don't forget to register: qRegisterMetaType<PairQml>("PairQml");

Reference to QML Singleton Class Instance?

I have created a class called LoginService.
I registered it to QT QML file by using qmlRegisterSingletonType, now the problem is I can't get the loginservice instance which instantiated by QML. My current c++ code is:
static QObject *qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine) {
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
LoginService::m_pThis = new LoginService;
return m_pThis;
}
qmlRegisterSingletonType<LoginService>("com.test.LoginService", 1, 0, "LoginService", &LoginService::qmlInstance);
If you want to access the singleton from C++, create a method that returns an instance other than qmlInstance:
loginservice.h
#ifndef LOGINSERVICE_H
#define LOGINSERVICE_H
#include <QObject>
class QQmlEngine;
class QJSEngine;
class LoginService : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
static LoginService *instance();
static QObject *qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
QString name() const;
void setName(const QString &name);
Q_SIGNAL void nameChanged();
private:
explicit LoginService(QObject *parent = nullptr);
static LoginService* m_pThis;
QString mName;
};
#endif // LOGINSERVICE_H
loginservice.cpp
#include "loginservice.h"
#include <QQmlEngine>
LoginService* LoginService::m_pThis = nullptr;
LoginService::LoginService(QObject *parent) : QObject(parent)
{
}
QString LoginService::name() const
{
return mName;
}
void LoginService::setName(const QString &name)
{
if(mName == name)
return;
mName = name;
Q_EMIT nameChanged();
}
LoginService *LoginService::instance()
{
if (m_pThis == nullptr) // avoid creation of new instances
m_pThis = new LoginService;
return m_pThis;
}
QObject *LoginService::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine) {
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
// C++ and QML instance they are the same instance
return LoginService::instance();
}
main.cpp
#include "loginservice.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterSingletonType<LoginService>("com.test.LoginService", 1, 0, "LoginService", &LoginService::qmlInstance);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
// get instance in C++
LoginService *service = LoginService::instance();
qDebug()<<service->name();
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import com.test.LoginService 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Component.onCompleted: LoginService.name = "testing"
}

Update TextArea in QML from C++

My question is pretty simple I think. Nevertheless I was not able to figure it out. I have a TextArea defined in my .qml file, which needs to be updated dynamically from the C++ code.
Unfortunately, I don't know how to access/update the TextArea from within the imserver.cpp class.
Can anyone help me out please?
Here is the .qml file:
import QtQuick 2.2
import QtQuick.Controls 1.1
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("IMServer")
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
TextArea {
id: serverInformation
x: 0
y: 0
width: 247
height: 279
}
}
My main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlEngine>
#include <QtQml>
#include "imserver.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
IMServer server(2000);
qmlRegisterUncreatableType<IMServer>("App", 1, 0, "IMServer", "");
engine.rootContext()->setContextProperty("imserver", &server);
server.startServer();
return app.exec();
}
imserver.h
#ifndef IMSERVER_H
#define IMSERVER_H
#include <QTcpServer>
#include <QTcpSocket>
#include <QAbstractSocket>
#include <QThreadPool>
class IMServer : public QTcpServer {
Q_OBJECT
Q_PROPERTY(QString text WRITE setText NOTIFY textChanged)
public:
explicit IMServer(int port, QObject *parent = 0);
void startServer();
void setText(const QString &txt);
signals:
void textChanged();
public slots:
protected:
void incomingConnection(qintptr fd);
private:
int port;
QThreadPool *pool;
QString m_text;
};
#endif // IMSERVER_H
imserver.cpp:
#include "imserver.h"
#include "clienthandler.h"
IMServer::IMServer(int port, QObject *parent) : QTcpServer(parent) {
this->pool = new QThreadPool(this);
this->pool->setMaxThreadCount(100);
this->port = port;
}
void IMServer::startServer() {
setText("TEST");
if (!this->listen(QHostAddress::Any, this->port)) {
qDebug() << "Server could not be started";
} else {
qDebug() << "Server started, listening...";
}
}
void IMServer::setText(const QString &txt) {
m_text = txt;
emit textChanged();
}
void IMServer::incomingConnection(qintptr fd) {
ClientHandler *client = new ClientHandler();
client->setAutoDelete(true);
client->fd = fd;
this->pool->start(client);
}
There are several ways. Here is how I'd do it.
First you should register your IMServer class:
qmlRegisterUncreatableType<IMServer>("App", 1, 0, "IMServer", "");
Then you add your IMServer instance to QML:
enigne.rootContext()->setContextProperty("imserver", &server);
Your IMServer class then needs a signal, that your TextArea can be connected to, or even better add a property (you need to add the getText() function and textChanged() signal here as well, for a read-only property):
Updated:
Q_PROPERTY(QString text READ getText NOTIFY textChanged)
In the TextArea you can then create a binding:
TextArea {
text: imserver.text
}
Then whenever you emit textChanged in your IMServer class, the TextArea's text will be updated.
For more information: http://doc.qt.io/qt-5/qtqml-cppintegration-topic.html

Resources