Passing Q_GADGET as signal parameter from C++ to QML - c++

Can't get a property of a C++ object inside a QML code.
Object is passed as a parameter to the signal.
Expected that in QML, the property text of the Record object can be extracted. And the value should be abc. QML sees the object as QVariant(Record), and its property text as undefined.
Record is a value-type like QPoint, so it uses Q_GADGET declaration.
hpp:
#ifndef LISTENP_HPP_
#define LISTENP_HPP_
#include <QObject>
#include "Record.hpp"
class ListenP: public QObject
{
Q_OBJECT
public:
ListenP();
virtual ~ListenP();
void emitGotRecord();
signals:
void gotRecord(Record r);
};
#endif /* LISTENP_HPP_ */
cpp:
#include "ListenP.hpp"
ListenP::ListenP() :
QObject()
{
}
ListenP::~ListenP()
{
}
void ListenP::emitGotRecord()
{
emit gotRecord(Record("abc"));
}
hpp for Record:
#ifndef RECORD_HPP_
#define RECORD_HPP_
#include <QObject>
#include <QMetaType>
class Record
{
Q_GADGET
Q_PROPERTY(QString text READ text WRITE setText)
public:
Record(const QString& text = "");
~Record();
QString text() const
{
return m_text;
}
void setText(const QString& text)
{
m_text = text;
}
private:
QString m_text;
};
Q_DECLARE_METATYPE(Record)
#endif /* RECORD_HPP_ */
cpp for Record:
#include "Record.hpp"
Record::Record(const QString& text) :
m_text(text)
{
}
Record::~Record()
{
}
namespace
{
const int RecordMetaTypeId = qMetaTypeId<Record>();
}
QML piece:
Connections {
target: listenPModel
onGotRecord: {
console.log(r)
console.log(r.text)
}
}
main piece:
QGuiApplication app(argc, argv);
auto listenP = std::make_shared<ListenP>();
QQuickView view;
view.rootContext()->setContextProperty("listenPModel", &*listenP);
view.setSource(QStringLiteral("src/qml/main.qml"));
view.show();
QtConcurrent::run([=]
{
QThread::sleep(3);
listenP->emitGotRecord();
});
return app.exec();
Log shows:
qml: QVariant(Record)
qml: undefined

The release notes for Qt 5.5 says for the new features:
Qt Core
You can now have Q_PROPERTY and Q_INVOKABLE within a Q_GADGET, and there is a way to query the QMetaObject of such gadget using the QMetaType system
Indeed, compiling and running your example with Qt 5.4 gives the same result as yours whereas with Qt 5.5 I got Record correctly recognised, i.e. I got as a result:
qml: Record(abc)
qml: abc
Also, as stated in the Q_DECLARE_METATYPE documentation, the type passed to the macro - Record in this case, should provide (1) a public default constructor, (2) a public copy constructor and (3) a public destructor. Since Record is a very simple class, there's no need to provide a copy constructor as the default one is sufficient.

Related

Qt C++ My Program runs perfectly when using one class, but crashes when I try to run a QThread with the same purpose

The program I have written has a function that takes a QList and returns the information in said QList as a QString. This works as intended
I however want to run this function in a thread in order to serialize the process, so I created a new class that runs a QThread.
//header file
#include "item.h"
#include <QThread>
#include <QObject>
#include <QList>
#include <QString>
class String_Writer_Thread : public QThread
{
Q_OBJECT
public:
explicit String_Writer_Thread(QObject *parent = nullptr);
void run();
void setList(QList<ITEM*> IList);
signals:
void EMIT_STRING(QString);
private:
QList<ITEM*> ITEM_LIST;
};
//implementation file
#include "string_writer_thread.h"
#include <QMutex>
String_Writer_Thread::String_Writer_Thread(QObject *parent)
: QThread{parent}
{
}
void String_Writer_Thread::run()
{
QString XString;
foreach(auto *I, ITEM_LIST)
{
XString.append(I->getString());
}
/*getString() is a void function that returns the data from an ITEM item as follows:
Name: ITEM_NAME //QString
Size: ITEM_SIZE //three int values (height, breadth, length)
Color: ITEM_COLOR //QString
*/
emit EMIT_STRING(XString); //should emit the resulting QString
terminate();
wait();
}
//this function is called before the QThread is started in the main class to insert the QList
//into the threaded class
void String_Writer_Thread::setList(QList<ITEM*> IList)
{
ITEM_LIST.clear();
foreach(auto I, IList)
{
ITEM_LIST.append(I);
}
}
Then in my main class, I initialize an item as such:
//main class header
public slots:
void update_Output(QString String_from_Thread); //handles the emitted string
private:
String_Writer_Thread *TString;
QString getThreadString; //this will hold the output from the String_Writer_Thread class
QList<ITEM*> Item_List;
and in the main class' implementation I connect the Signals and Slots as
//main class implementations
//initializing TString
TString = new String_Writer_Thread();
//connecting the signals and slots
connect(TString, &String_Writer_Thread::EMIT_STRING, this, &MainWindow::update_Output);
...
void MainWindow::update_Output(QString String_from_Thread)
{
getThreadString = String_from_Thread;
Text_Output.append(getThreadString); //Text_Output is a QTextEdit in the ui.
//I use it instead of QDebug because QDebug is a pain to
//work with sometimes
}
void MainWindow::add_Item_to_List(ITEM newI)
{
Item_List.append(newI);
TString->setList(Item_List);
TString->start();
}
To compare, the original function is simply
QString MainWindow::List_to_String(QList<ITEM*> ITL)
{
QString XString;
foreach(auto *I, ITL)
{
XString.append(I->getString());
}
/*getString() is a void function that returns the data from an ITEM item as follows:
Name: ITEM_NAME //QString
Size: ITEM_SIZE //three int values (height, breadth, length)
Color: ITEM_COLOR //QString
*/
return XString;
}
Despite the Program building without giving me red or yellow flags, it still crashes.

Qt QML Access a QObject by a String

I have a Model which is exposed to the qml context property and it contains a List of Lists. These Lists are created dynamically and are then exposed to the qml context as well.
Model:
#include <QtCore>
#include <QQmlContext>
class Model : public QObject
{
Q_OBJECT
public:
Model(QQmlContext* rootContext) {
rootContext->setContextProperty(QML_NAME, this);
dataLists.push_back( new DatedValueList(rootContext, "List1", "m"));
names.push_back("List1");
}
QList<DatedValueList*> dataLists;
QString QML_NAME = "Model";
QList<QString> names;
Q_INVOKABLE QList<QString> getListNames() { return names };
};
DatedValueList:
#include <QObject>
#include <QtCore>
#include <QQmlContext>
class DatedValueList: public QObject, public QList<int>
{
Q_OBJECT
public:
DatedValueList(QQmlContext* rootContext, QString i_name, QString i_unit) : name(i_name), unit(i_unit)
{
rootContext->setContextProperty(name, this);
}
QString name; //under this name it is exposed
QString unit;
Q_INVOKABLE QString getName() {return name;}
Q_INVOKABLE QString getUnit() {return unit;}
};
Main:
#include <QtCore>
#include <QQmlContext>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
//create Controller w/ thread
QQmlContext* rootContext = engine.rootContext();
Model model(rootContext );
//load QML into engine
engine.load(QUrl(QStringLiteral("qrc:/resource/qml/MainWindow.qml")));
return app.exec();
}
in QML I now want to access one of these DatedValueLists like this. MainWindow.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: mainview
width: 1800
height: 768
visible: true
flags: Qt.Window
Timer {
interval: 1000 // 1 Hz
running: true
repeat: true
onTriggered: {
console.log( Model.getListNames()[0] ) // Output: List1
console.log( Model.getListNames()[0].getUnit() ) // Not working
}
}
}
The second output line is not working. It is a String and I want to call a function of on it. But how can I cast it or use the String as a qml context id?
As minimal requirement you will have to implement a function that returns the DatedValueList on your Model class:
Q_INVOKABLE DatedValueList getList(const QString& name) { return ... };
But this immediately poses a small challenge, since your data structure does not allow lookup and thus should iterate the whole dataLists member.
I think you are better of providing a QVariantMap from your Model class, you can give a name to each list and also obtain the list itself:
//C++
Q_PROPERTY(QVariantMap listMap ...)
//QML
model.listMap.keys()[0]
model.listMap[model.listMap.keys()[0]].getUnit()
But this can even be a bad decision depending on how you want to shape the rest of your UI, which I'm most sure will not be console.log's only ;-)
Another note, context properties should not have a capital for the first letter. You are likely instantiating a Model inside the console.log call instead of referencing the one instantiate in main

How to implement rich text logic on QML TextEdit with QSyntaxHighlighter class in Qt?

I have a TextEdit in my QML file and I have a QSyntaxHighlighter C++ class. I want to specify the highlighting logic in the C++ class and apply it to the TextEdit, but I am not sure how to make the connection between the QML object and the C++ class. Can you also please provide some sample code? I couldn't find how to implement it with the Qt documentation.
You can use TextEdit::textDocument, which holds an instance of QQuickTextDocument, to gain access to the underlying QTextDocument that you can pass to QSyntaxHighlighter constructor.
In case someone needs a more detailed explanation for this.
First, I created a Q_PROPERTY inside a custom C++ class.
Q_PROPERTY(QQuickTextDocument* mainTextEdit READ mainTextEdit WRITE setMainTextEdit NOTIFY mainTextEditChanged)
Then I assign textEdit.textDocument to the Q_PROPERTY in the QML.
customClass.mainTextEdit = textEdit.textDocument
Then I call initHighlighter() (the function has to be Q_INVOKABLE) in my QML which calls the constructor of my highlighter class and passes it the text document of the textEdit.
void initHighlighter()
{
Highlighter *highlighter;
highlighter = new Highlighter(m_mainTextEdit->textDocument());
}
Note: The custom highlighter class needs to be derived from QSyntaxHighlighter.
Sample implementation:
HighlightComponent.h
class HighlightComponent : public QObject
{
Q_OBJECT
//#formatter:off
Q_PROPERTY(QString text
READ getText
WRITE setText
NOTIFY textChanged)
//#formatter:on
using inherited = QObject;
public:
explicit HighlightComponent(QObject* parent = nullptr);
static void registerQmlType();
const QString& getText()
{
return _text;
}
void setText(const QString& newText)
{
if (newText != _text)
{
_text = newText;
emit textChanged();
}
}
Q_INVOKABLE void onCompleted();
signals:
void textChanged();
private:
std::unique_ptr<QSyntaxHighlighter> _highlight;
QString _text = "";
};
HighlightComponent.cpp
HighlightComponent::HighlightComponent(QObject* parent)
: inherited(parent)
{
}
void HighlightComponent::onCompleted()
{
auto property = parent()->property("textDocument");
auto textDocument = property.value<QQuickTextDocument*>();
auto document = textDocument->textDocument();
//TODO init here your QSyntaxHighlighter
}
void HighlightComponent::registerQmlType()
{
qmlRegisterType<HighlightComponent>("com.HighlightComponent", 1, 0, "HighlightComponent");
}
main.cpp
int main(int argc, char* argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
HighlightComponent::registerQmlType();
engine.load(QUrl(QStringLiteral("qrc:/view/main.qml")));
return app.exec();
}
Sample QML
TextArea {
id: testTextArea
text: testTextArea.text
//inputMethodHints: Qt.ImhNoPredictiveText
onTextChanged: {
testTextArea.text = text
}
HighlightComponent {
id: testTextArea
Component.onCompleted: onCompleted()
}
}
Links:
https://wiki.qt.io/How_to_Bind_a_QML_Property_to_a_C%2B%2B_Function
http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html
http://wiki.qt.io/Spell-Checking-with-Hunspell
http://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html
http://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html

QT/QML c++ Program crash on access a QList from QML

I have 2 classes for datahandling (CGameList and CGame).
I define one GameList (_gamelist) object in qml to work with it.
I have a Listviews with show the the games from this GameList (editGames_open()).
If I click on one entry of this list, it open a new list with a detailed view of this game (editGame_open(index)).
This work works like expected.
Now my problem:
If I go back to the list and try open it again, my program crash (not every time, sometimes its work 20x times).
The crash appear after the call of getGame.
If I use the debugger I can see my CGameList-object looks fine(data a correct + item in my QList are correct), but after this the program crash with a Segmentation Fault.
The callstack show only QQMlData::wasDeleted as last entry.
I think the problem is, that my object is delete, but I cant find this.
I had try to change my QList from QList _games to QList* _games but without success.
One other thing(I think its the same problem):
Sometimes getGame give back a NULL-pointer(although the game is in the list, but the data are wrong).
cgamelist.h
#ifndef CGAMELIST_H
#define CGAMELIST_H
#include <QObject>
#include <QList>
#include <qfile.h>
#include <QTextStream>
#include <QDir>
#include <QStandardPaths>
#include <QDateTime>
#include <cgame.h>
class CGameList : public QObject
{
Q_OBJECT
Q_PROPERTY(int itemCount READ getItemCount)
public:
CGameList(QObject *parent = 0);
Q_INVOKABLE bool addGame(QString name,int layout);
Q_INVOKABLE int getItemCount() const;
Q_INVOKABLE void saveGame(int index);
Q_INVOKABLE void loadGames(bool force=true);
Q_INVOKABLE CGame* getGame(int i) const;
Q_INVOKABLE QString getGamename(int i) const;
Q_INVOKABLE QString getGamedate(int i) const;
Q_INVOKABLE void delGame(int i);
private:
QList<CGame*>* _games;
};
#endif // CGAMELIST_H
cgamelist.cpp
CGameList::CGameList(QObject *parent) : QObject(parent)
{
_games=new QList<CGame*>();
_games->clear();
}
...
CGame* CGameList::getGame(int i) const
{
/* CGame*g=new CGame();
g->setGamename("test");
return g;*/
try
{
return _games->at(i);
}
catch(...)
{
return NULL;
}
}
...
cgame.h*
#ifndef CGAME_H
#define CGAME_H
#include <QObject>
#include <QString>
#include <QDateTime>
class CGame : public QObject
{
Q_OBJECT
Q_PROPERTY(QString gamename READ getGamename WRITE setGamename)
Q_PROPERTY(int itemCount READ getItemCount)
Q_PROPERTY(int layout READ getLayout WRITE setLayout)
Q_PROPERTY(int duration READ getDuration WRITE setDuration)
Q_PROPERTY(QDateTime date READ getDate WRITE setDate)
public:
CGame(QObject *parent = 0);
Q_INVOKABLE QString getGamename() const;
Q_INVOKABLE void setGamename(QString name);
Q_INVOKABLE int getItemCount() const;
Q_INVOKABLE int getLayout() const;
Q_INVOKABLE void setLayout(int layout);
Q_INVOKABLE int getDuration() const;
Q_INVOKABLE void setDuration(int duration);
Q_INVOKABLE QDateTime getDate() const;
Q_INVOKABLE void setDate(QDateTime date);
Q_INVOKABLE QString getEvent(int i) const;
Q_INVOKABLE void addEvent(QString ename,int time,int sec,int duration);
private:
QString _name;
int _layout;
int _duration;
QList<QString>* _events;
QDateTime _date;
};
#endif // CGAME_H
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "clayoutlist.h"
#include "clayout.h"
#include "clayoutitem.h"
#include "cgame.h"
#include "cgamelist.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
//QApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterType<CLayoutList>("STSP.Tag",1,0,"TagLayoutList");
qmlRegisterType<CLayout>("STSP.Tag",1,0,"TagLayout");
qmlRegisterType<CLayoutItem>("STSP.Tag",1,0,"TagLayoutItem");
qmlRegisterType<CGame>("STSP.Tag",1,0,"Game");
qmlRegisterType<CGameList>("STSP.Tag",1,0,"GameList");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.2
import STSP.Tag 1.0
ApplicationWindow {
TagLayoutList {
id: _layoutlist
}
GameList
{
id:_gamelist
}
visible: true
MainForm {
id: mainform
anchors.fill: parent
}
MessageDialog{
id:info
}
...
function editGames_open()
{
_gamelist.loadGames()
mainform.p_gameslistmodel.clear()
var i
for (i = 0; i < _gamelist.getItemCount(); i++) {
mainform.p_gameslistmodel.append({
name: _gamelist.getGamename(i),
date: _gamelist.getGamedate(i),
gameindex: i
})
}
mainform.p_editgames.delmode=false
mainform.p_editgames.visible = true
mainform.p_startmenu.visible=false
}
function editGame_open(index)
{
mainform.p_eventlistmodel.clear()
var game={}
try
{
game=_gamelist.getGame(index)
}
catch(exc)
{
console.log("Serious Error 2 "+exc)
return
}
if(game==null)
{
console.log("Reload Games")
_gamelist.loadGames()
game=_gamelist.getGame(index)
}
if(game==null)
{
console.log("Error Game not found")
return
}
var i
var event
var events
var t,s,m,h
for(i=0;i<game.getItemCount();i++)
{
event=game.getEvent(i).split('#')[1]
//console.log(event)
events=event.split(',')
t=events[1]
s=t%60
t=(t-s)/60
m=t%60
h=(t-m)/60
mainform.p_eventlistmodel.append({
name: events[0],
time: h+":"+(m<10?"0":"")+m+":"+(s<10?"0":"")+s,
eventindex: i
})
}
mainform.p_editgame.gname=game.getGamename()
t=game.getDuration()
s=t%60
t=(t-s)/60
m=t%60
h=(t-m)/60
mainform.p_editgame.gtime=h+":"+(m<10?"0":"")+m+":"+(s<10?"0":"")+s
mainform.p_editgames.delmode=false
mainform.p_editgames.visible=false
mainform.p_editgame.visible=true
}
function editGame_back()
{
mainform.p_editgame.visible=false
mainform.p_editgames.visible=true
}
It's hard to say without being able to debug your program, but it sounds like QML is taking ownership of your CGame object:
When data is transferred from C++ to QML, the ownership of the data always remains with C++. The exception to this rule is when a QObject is returned from an explicit C++ method call: in this case, the QML engine assumes ownership of the object, unless the ownership of the object has explicitly been set to remain with C++ by invoking QQmlEngine::setObjectOwnership() with QQmlEngine::CppOwnership specified.
Additionally, the QML engine respects the normal QObject parent ownership semantics of Qt C++ objects, and will not ever take ownership of a QObject instance which already has a parent.
The simplest solution would be to assign a parent to each CGame object before returning it to QML. Alternatively, you can do the following for each object:
QQmlEngine::setObjectOwnership(game, QQmlEngine::CppOwnership);
i think you will create your QList from C++, and put in qml like a contextProperty (with that you dont need declare in qml the class, but you still need register type), but the problem is you need refresh (executing again the sentence of declaration of contextPropertie) for every change in the model, and yes, is better user QObject*.
Maybe is better try use a QmlListProperty (here my case Use QQmlListProperty to show and modify QList in Qml), in youtube exist other tutorial for use that class), or QAbstractListModel (i don't worked before with that class, but tell me is lightly better in some cases).

QML callback from C++ with custom type as parameter

I would like to be able to call a QML function from C++ with an instance of a custom class as a parameter and then manipulate the instance from QML.
Here is what I did so far :
Data.h
class Data : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
public :
Data() : QObject(), _text("Foo") { }
virtual ~Data() { }
Data(const Data & other) { _text = other._text; }
QString text() const { return _text; }
void setText(const QString & text) { _text = text; }
private :
QString _text;
};
Q_DECLARE_METATYPE(Data);
Main.cpp
#include "Data.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Data callBackData;
QQmlEngine engine;
QQmlComponent rootComponent(&engine, QUrl::fromLocalFile("CallBack.qml"));
QObject * rootObj = rootComponent.create();
QMetaObject::invokeMethod(rootObj, "callMeBack",
Q_ARG(QVariant, QVariant::fromValue(callBackData)));
return app.exec();
}
CallBack.qml
import QtQuick 2.0
Item {
function callMeBack(data) {
console.log(data.text)
}
}
The console outputs "Undefined". Did I do something wrong ?
When changing the function body to console.log(data) it outputs "QVariant(Data)" so why can't I access the text property of data ?
I tried registering Data as a QML type using qmlRegisterType<Data>(); but this does not change anything.
Try pass a QObject pointer instead:
Data *callbackData = new Data;
...
QMetaObject::invokeMethod(rootObj, "callMeBack",
Q_ARG(QVariant, QVariant::fromValue(callBackData)));
Not tested, but should work (QML recognize QObject* type).