How to make a JSON-like QObject - qt

In short, I want to make an JSON-like object, which is easily accessible to both QML / Qt C++ side.
In QML, I can make a settings object like this:
Item {
id: settings
property alias snapshot: snapshot
QtObject {
id: snapshot
property string saveDirectory: "~/hello/world/"
}
}
And make it a singleton, now we can access data via settings.snapshot.saveDirectory .
But accessing it in C++ is very inconvenience, so I'm trying to do this task in C++: create a class Settings, establish instance as settings, then pass it into QQmlApplicationEngine as a context. (the whole application only has only one instance of Settings)
state.cpp
// =============================================================
/// Settings is parent
class Settings : public QObject
{
Q_OBJECT
Q_PROPERTY(SnapshotSettings* snapshot READ snapshot) // <- It's pointer, very important!
public:
explicit Settings(QObject *parent=0) : m_snapshot_settings(new SnapshotSettings) {}
private:
SnapshotSettings* m_snapshot_settings;
}
// =============================================================
// SnapshotSettings is the child of Settings
class SnapshotSettings : public QObject
{
Q_OBJECT
Q_PROPERTY(QString saveDirectory READ saveDirectory)
public:
explicit Settings(QObject *parent=0) : m_save_directory("~/hello/world/") {}
private:
QUrl m_save_directory;
}
main.cpp
I register SnapshotSettings* type. Otherwise, it said:
QMetaProperty::read: Unable to handle unregistered datatype 'SnapshotSettings' for property 'Settings::snapshot'
main.qml:52: TypeError: Cannot read property saveDirectory' of undefined
qmlRegisterType<>() seems unnecessary because I pass its instance into QML as a context, instead of instantialize it in QML?
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QQmlContext>
#include "state.cpp"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
Settings settings;
// ....Am I right?
qRegisterMetaType<SnapshotSettings*>("SnapshotSettings");
// This seems unnecessary because I use its instance as a context?
// qmlRegisterType<Settings>("com.aisaka.taiga", 0, 1, "Settings");
engine.rootContext()->setContextProperty("settings", &settings);
engine.addImportPath(QStringLiteral(":/qml"));
engine.load(QUrl("qrc:/qml/main.qml"));
return app.exec();
}
One told me I made an anti-pattern of C++/QML…, saying "I never see anyone do like this".
Now it seems ok, I can use settings.snapshot.saveDirectory in QML. But in QtCreator, auto-completion work incorrectly.(saveDirectory won't show after settings.snapshot.)
My question is: Maybe I really did an anti-pattern? If yes, is there any better way to do this?
Thanks.

Your snapshot property needs to be of type SnapshotSettings*, i.e. a pointer to SnapshotSettings because that class is a QObject and cannot be copied.
You should also mark that property as CONSTANT, since the pointer never changes.
The saveDirectory property will either need a NOTIFY signal or also CONSTANT depending on whether it can change during runtime.

Related

When is it mandatory to call qRegisterMetaType()?

I have studied the qt documentation of qRegisterMetaType() where it says that this function must be called before the corresponding type can be used in signal/slot mechanism. However I couldn't find any code example where this has to be done by hand.
This page states, that the registration is done automatically by the moc if it can determine that the type may be registered as meta-type. It looks like this is right, because I tested QSignalSpy, QObject::connect() (direct and queued connection) and QVariant - with just using Q_DECLARE_METATYPE(type) and none of them needed a explicit call to qRegisterMetaType to work.
So my question is: when do I have to call qRegisterMetaType(), because otherwise the code won't work?
The Qt docs say that Q_DECLARE_METATYPE is necessary in case one has a connect being a queued connection.
Adding a Q_DECLARE_METATYPE() makes the type known to all template
based functions, including QVariant. Note that if you intend to use
the type in queued signal and slot connections or in QObject's
property system, you also have to call qRegisterMetaType() since the
names are resolved at runtime.
For this I build a small testing app, that exemplifies the behavior.
Just try to remove the Q_DECLARE_METATYPE(Message) and watch the warnings and output change. In case of the normal connect the macro seems to be unnecessary.
main.cpp
#include <QApplication>
#include <QThread>
#include "MyHeaderView.h"
Q_DECLARE_METATYPE(Message);
int main(int argc, char **args)
{
QApplication app(argc, args);
{
TestObject sender;
TestObject receiver;
QObject::connect(&sender, &TestObject::sendMessage, &receiver, &TestObject::onMessage);
sender.emitMessage(1, 2);
}
// This requires Q_DECLARE_METATYPE(Message);
QThread workerThread;
TestObject sender2;
TestObject receiver2;
receiver2.moveToThread(&workerThread);
workerThread.start();
QObject::connect(&sender2, &TestObject::sendMessage, &receiver2, &TestObject::onMessage, Qt::ConnectionType::QueuedConnection);
sender2.emitMessage(3, 4);
app.exec();
}
TestObject.h
#pragma once
#include <QObject>
#include <QDebug>
struct Message
{
int x;
int y;
};
class TestObject : public QObject
{
Q_OBJECT
public:
void emitMessage(int x, int y) { emit sendMessage(Message{ x,y }); }
signals:
void sendMessage(const Message&);
public slots:
void onMessage(const Message& m) { qDebug() << m.x << m.y; }
};

Qt: Track mouse position while QDrag is running

I am developing a Qt application with multiple windows and want to implement cross-window drag&drop functionality for some elements in my program.
To do so, I attach an event filter to the to-be-dragged QML elements and listen for the MousePress/MouseMove events to start the drag procedure as follows:
QDrag *drag = new QDrag(quickItem);
QMimeData* mimeData = new QMimeData();
mimeData->setText("Test");
drag->setHotSpot(QPoint(0, 0));
drag->setMimeData(mimeData);
drag->exec();
This works fine, but now I would like to show a little tooltip (being a QWidget) while dragging, following the mouse cursor and displaying a short text depending on the element the mouse is currently over (similar to the "Copy to ..." or "Move to..." labels appearing when you drag files around in Windows Explorer).
However, while dragging the element, I don't receive any MouseMove events neither on the QDrag object nor on the quickItem itself, which makes it impossible to track the mouse position. Since the mouse is grabbed during dragging, there should be some event in Qt that frequently reports the mouse position, no matter where on the screen the mouse is.
I am aware of the QDrag::setPixmap method, however this won't allow me to change my tooltip text during dragging and has some other limitations I would like to avoid.
Is there some way to listen to mouse move events while QDrag is running, without using platform-specific system APIs?
Update:
I don't think that there is way of doing this without using OS libraries nor getting the mouse position every X miliseconds. It looks like a really specific problem that Qt framework does not contemplate. You will need to write your own class to control this using win32 for windows, x11 for linux and the equivalent of Mac.
If you want to get the mouse position when your window is active and you are dragging something, check this:
Searching a bit I've found a solution for getting it when your window has the focus using QObject::eventFilter.
Create a class (for example EventListener) that inherits from QObject and overrides eventFilter and a method to set this as your qml window (which inherits from QObject) event filter with installEventFilter.
eventslistener.h:
#include <QEvent>
#include <QObject>
#include <QDebug>
#include <QDropEvent>
class EventsListener : public QObject
{
Q_OBJECT
public:
EventsListener(QObject * ptr) : QObject (ptr) {
}
Q_INVOKABLE void handleEventsOf(QObject *object) {
if (object)
object->installEventFilter(this);
}
bool eventFilter(QObject *object, QEvent *event) override {
if(event->type() == QEvent::DragMove) {
QDragMoveEvent *mouseEvent = static_cast<QDragMoveEvent*>(event);
qDebug() << "Mouse position dragging (x, y): (" << mouseEvent->pos().x() << ", " << mouseEvent->pos().y() << ")";
return false; //this is must return false or drop event will be handled by this method and drag&drop won't work correctly
}
return false;
}
};
Now we need to access to an instance (singleton in this case) of this class with qmlRegisterSingletonType. You may wish to use qmlRegisterType instead to register this eventlistener as a type (instead of a singleton) and use signals to notify directly qml the mouse position.
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "eventlistener.h"
static QObject *eventsListenerInstance(QQmlEngine *qmlEngine, QJSEngine *engine)
{
return new EventsListener(engine);
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterSingletonType<EventsListener>("AppEventListener", 1, 0, "EventsListener", eventsListenerInstance);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main.qml:
import ...
import AppEventListener 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
id: item
property string display
property alias dropEnabled: acceptDropCB.checked
color: dropArea.containsDrag ? "#CFC" : "#EEE"
Component.onCompleted: EventsListener.handleEventsOf(item)
...
}

Variadic signals and generic lambdas

Is it possible to create variadic signal and connect generic lambda as slot? I mean something like (say, all definitions of involved functions are visible where needed (e.g. at points of instantiation)):
#include <QCoreApplication>
#include <QObject>
#include <QTime>
class A
: public QObject
{
Q_OBJECT
public :
A(QObject * const parent = Q_NULLPTR)
: QObject{parent}
{ ; }
signals :
template< typename ...Ts >
void infoMessage(Ts... args);
public slots :
void run()
{
emit infoMessage("Started at ", QTime::currentTime());
}
};
#include <QTimer>
#include <QtDebug>
#include "main.moc"
int main(int argc, char * argv [])
{
QCoreApplication a{argc, argv};
A a;
auto printInfoMessage = [&] (auto... args)
{
(qInfo() << ... << args);
};
QObject::connect(&a, SIGNAL(infoMessage), printInfoMessage);
QTimer::singleShot(0, &a, &A::run);
return a.exec();
}
Currently it gives an error message:
AUTOGEN: error: process for main.cpp:18: Error: Template function as signal or slot
moc failed...
Here macro SLOT() instead of &A::infoMessage does not help a lot. Is there any workarounds to overcome this limitation?
I know, that some of the answers will contain a using of std::make_tuple and std::index_sequence stuff. But is there less verbose solution?
There is no direct workaround for having template. On of thea reason is that the moc indexes all signals and slots, and this cannot be done for function templates as function templates will generate several functions depending code that is generally not accessible from the moc.
I don't think you can make it work with tuple and such as these are also templates.
A solution could be to use QVariant and/or QVariantList for your arguments.
Please note that the error is not caused by the QObject::connect line, but the the signal declaration in class A.
Also, you cannot replace SIGNAL() and SLOT() at your will, it is either a signal or a slot, it cannot be both.
And finally you should be using this form:
QObject::connect(&a, &A::infoMessage, printInfoMessage);
And since printInfoMessage is using auto parameters, you might need to force the auto resolution using qOverload:
QObject::connect(&a, &A::infoMessage, qOverload<QVariantList>(printInfoMessage));

Why is my mainwindow closing automatically when called from a differnet class?

I can't seem to figure out what went wrong so I'm here to ask you. I have made a simple class called BOBSNetworkSessionManager defined below. It is a simple class that inherits the QOBject so that I can use signals and slots but it does not have a dialog or any kind of window associated with it. It will eventually call a log in dialog and use the credentials to connect to a tcp server that I have created. This class serves as a layer to manage the connection state of the program because it will only run properly when connected to the server and when being used within 15 minutes without break due to p.c.i. compliance. If these conditions are not true this class will lock the window and force a new login. As of right now I just try to arbitrarily open the main window as though credentials had passed and i wasbconnected to the server. The problem is when I open the mainwindow it disapears right away. I cannot seem to figure out why it is diappearing. I have included all of my files.
BOBSDCNetworkSessionManager .h header file
#ifndef BOBSDCNETWORKSESSIONMANAGER_H
#define BOBSDCNETWORKSESSIONMANAGER_H
#include <QObject>
#include <QSettings>
class BOBSDCNetworkSessionManager : public QObject
{
Q_OBJECT
public:
explicit BOBSDCNetworkSessionManager(QObject *parent = 0);
protected:
void destroyed(QObject *);
signals:
public slots:
private:
void readSettings();
void writeSettings();
QSettings networkSettings;
};
#endif // BOBSDCNETWORKSESSIONMANAGER_H
BOBSDCNetworkSessionManager Implementation .cpp file
#include "bobsdcnetworksessionmanager.h"
#include "bobsmainwindow.h"
BOBSDCNetworkSessionManager::BOBSDCNetworkSessionManager(QObject *parent) :
QObject(parent)
{
BOBSMainWindow w;
w.show();
}
Main.cpp file
#include "bobsdcnetworksessionmanager.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setApplicationName("Enterprise Management Suite");
a.setApplicationVersion("Beta Version: 0.0.0.01");
a.setOrganizationName("Enigma Web Consulting");
a.setOrganizationDomain("http://www.EnigmaWebCo.com");
BOBSDCNetworkSessionManager netMgr;
return a.exec();
}
The problem is here:
{
BOBSMainWindow w;
w.show();
}
w.show() is not a blocking call. So you're creating a window, showing it, and then it immediately is destructed when it goes out of scope. You should either declare w as a member variable or construct it on the heap:
BOBSMainWindow *w = new BOBSMainWindow(this);

QVector inside QML/Javascript

How Can I Use QVector inside QML/Javascript?
Example:
C++:
Custom class I use in QML. The class is including function that return QVector of registered ElementType
class CustomType : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE QVector<ElementType*> getElements();
//.....
}
//.........
qmlRegisterType<CustomType>("CustomComponents", 1, 0, "CustomType");
qmlRegisterType<ElementType>("CustomComponents", 1, 0, "ElementType");
qRegisterMetaType<ElementType*>("ElementType*");
QML:
QML code receives instance of CustomType class (custom) and try to get QVector<ElementType*> of elements and reads its properties. But QML can't recognize QVector type.
//.......
function(){
var elements = custom.getElements()
elements[0].property //?
}
QML-accessible list properties need to be constructed with QDeclarativeListProperty. This applies to everything that's list-shaped, also vectors. This part of the Qt documentation has the details.
I am answering this years late, But, here goes. This is a simple problem to resolve. Just use a Qt QVariantList type in your C++ code instead of QVector. Qt's QVariantList is essentially a QVector containing QVariant elements. And QVariants can be damn near anything once you know what you are doing. Also, QML automatically converts a C++ QVariant to its QML equivalent. Hence, if you do this in main.cpp:
#include <QCoreApplication>
#include <QGuiApplication>
#include <QQmlContext>
#include <QQuickView>
int main(int argc, char *argv[])
{
// Prepare to enter the QML GUI world
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
// Create a QVariantList to be used instead of a QVector
QVariantList dataList;
dataList << "Item 1";
dataList << 2;
dataList << 3.3;
// Create QML UI
QQuickView view;
// make your QVariantList visible to QML
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel",
QVariant::fromValue(dataList));
// show QML UI
view.setSource(QUrl("qrc:/main.qml"));
view.show();
return 1;
} // main()
You can then do this in your main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Item
{
// Print contents of C++ QVariantList to console
function printMeBruh()
{
console.log("modelData contains:")
console.log("-------------------")
for (var i = 0; i < 3; i++)
{
console.log(myModel[i])
}
}
Component.onCompleted: printMeBruh()
}
In your example you just need to return QList of QObject* from a Q_INVOKABLE function. Note, that all objects returned that way will have JavaScript ownership and will be garbage collected when your JavaScript function returns. To prevent this behavior use setObjectOwnership before pushing objects into your QList collection.
For more information about using collections in QML see QML Data Models

Resources