Qt VTK real-time point cloud rendering - c++

I am working on real-time point cloud rendering project in Qt Quick and VTK. I have a laser sensor with sampling rate of 200Hz. For testing the program generates random points in while loop using QtConcurrent::Run.
Here is my code:
pointcloud.h
#ifndef POINTCLOUD_H
#define POINTCLOUD_H
#include "QQuickVTKRenderItem.h"
#include "qtconcurrentrun.h"
#include "qtmetamacros.h"
#include "vtkActor.h"
#include "vtkMath.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkSmartPointer.h"
#include <QObject>
#include "vtkDoubleArray.h"
#include "vtkPointData.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkSmartPointerBase.h"
#include <math.h>
#include <QTimer>
#include <QtConcurrent>
class PointCloud : public QObject {
Q_OBJECT
public:
explicit PointCloud(QObject *parent = nullptr);
void setVtkItem(QQuickVTKRenderItem *item);
Q_INVOKABLE void addPoint(const double point[3]);
Q_INVOKABLE void clear();
Q_INVOKABLE void startRandom();
private:
vtkSmartPointer<vtkPolyData> polyData;
vtkSmartPointer<vtkActor> actor;
vtkSmartPointer<vtkPoints> m_points;
vtkSmartPointer<vtkDoubleArray> depth;
vtkSmartPointer<vtkCellArray> vertices;
vtkSmartPointer<vtkPolyDataMapper> mapper;
QQuickVTKRenderItem *m_renderItem;
signals:
};
#endif // POINTCLOUD_H
pointcloud.cpp
#include "pointcloud.h"
#include "vtkProperty.h"
#include "vtkSmartPointer.h"
PointCloud::PointCloud(QObject *parent)
: QObject{parent}{
polyData = vtkSmartPointer<vtkPolyData>::New();
mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
vertices = vtkSmartPointer<vtkCellArray>::New();
depth = vtkSmartPointer<vtkDoubleArray>::New();
depth->SetName("depth");
m_points = vtkSmartPointer<vtkPoints>::New();
polyData->SetPoints(m_points);
polyData->SetVerts(vertices);
polyData->GetPointData()->SetScalars(depth);
polyData->GetPointData()->SetActiveScalars("depth");
mapper->SetInputData(polyData);
mapper->SetColorModeToDefault();
mapper->SetScalarVisibility(1);
actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
actor->GetProperty()->SetPointSize(2);
}
void PointCloud::setVtkItem(QQuickVTKRenderItem *item) {
m_renderItem = item;
if (m_renderItem) {
m_renderItem->renderer()->AddActor(actor);
}
}
void PointCloud::addPoint(const double point[3]) {
auto pid = m_points->InsertNextPoint(point);
depth->InsertNextTuple1(point[2]);
vertices->InsertNextCell(1);
vertices->InsertCellPoint(pid);
m_points->Modified();
depth->Modified();
vertices->Modified();
}
void PointCloud::clear() {
vertices->Initialize();
vertices->Modified();
depth->Initialize();
depth->Modified();
m_points->Initialize();
m_points->Modified();
}
void PointCloud::startRandom(){
auto future=QtConcurrent::run([&](){
while(polyData->GetPoints()->GetNumberOfPoints()<1000000){
auto theta=2*vtkMath::Pi() *vtkMath::Random();
double phi=acos(2*vtkMath::Random()-1);
double p[3]={sin(phi)*cos(theta),sin(phi)*sin(theta),cos(phi)};
addPoint(p);
Sleep(5);
}
});
}
main.cpp
#include "pointcloud.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickVTKRenderItem.h>
#include <QQuickVTKRenderWindow.h>
int main(int argc, char *argv[]) {
QQuickVTKRenderWindow::setupGraphicsBackend();
QGuiApplication app(argc, argv);
PointCloud cloud;
qmlRegisterType<QQuickVTKRenderWindow>("VTKPLUS", 9, 1, "VTKRenderWindow");
qmlRegisterType<QQuickVTKRenderItem>("VTKPLUS", 9, 1, "VTKRenderItem");
qmlRegisterSingletonInstance<PointCloud>("PointCloud", 1, 0, "PointCloud",
&cloud);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/VTKTest/main.qml"_qs);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
engine.load(url);
QObject *rootObject = engine.rootObjects()[0];
auto *container = rootObject->findChild<QObject *>("container");
auto *renderItem = container->findChild<QObject *>("renderItem");
QQuickVTKRenderItem *vtkRenderItem =
dynamic_cast<QQuickVTKRenderItem *>(renderItem);
vtkRenderItem->renderer()->SetBackground(1, 1, 1);
vtkRenderItem->update();
cloud.setVtkItem(vtkRenderItem);
return app.exec();
}
main.qml
import QtQuick
import VTKPLUS
import VTKTest 1.0
import PointCloud
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Layouts
ApplicationWindow {
visibility: "Maximized"
visible: true
title: "VTK Test"
menuBar: MenuBar {
Menu {
id: fileMenu
title: 'Files'
Action {
text: "Add Point"
onTriggered: {
PointCloud.startRandom()
}
}
Action {
text: "Clear"
onTriggered: {
PointCloud.clear()
}
}
}
}
Item {
anchors.fill: parent
objectName: "container"
VTKRenderWindow {
objectName: "vtkRenderWindow"
id: vtkwindow
anchors.fill: parent
}
VTKRenderItem {
objectName: "renderItem"
anchors.fill: parent
renderWindow: vtkwindow
}
}
}
The code works but when adding new points, the vtk scene updates are very slow.

Related

Best way to create Elements dynamically in QML based on C++ QMap

This seemed like a simple task at first, but honestly I am struggling. I have a Map of profiles I load at startup, and profiles can be added and deleted at runtime. Those profiles need to be listed as RadioButtons or some other element for selection/editing in my UI.
I've tried using createQMLObject() while looping trough a QVariantMap getter:
profile.h
#ifndef PROFILE_H
#define PROFILE_H
#include <QObject>
class Profile final : public QObject
{
Q_OBJECT
public:
Profile(QObject *parent = nullptr);
~Profile(){}
Profile(QString profileName = "default", QObject *parent = nullptr);
QString getProfileName() const;
void setProfileName(const QString &value);
private:
QString profileName;
};
#endif // PROFILE_H
profile.cpp
#include "profile.h"
Profile::Profile(QString profileName, QObject *parent)
: QObject(parent), profileName{ profileName }{}
QString Profile::getProfileName() const
{
return profileName;
}
void Profile::setProfileName(const QString &value)
{
profileName = value;
}
controller.h
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include "profile.h"
#include <QObject>
#include <QQmlEngine>
class Controller : public QObject
{
Q_OBJECT
public:
Controller(QObject *parent = nullptr);
~Controller(){}
static QObject* controllerSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
static Controller* instance();
Q_INVOKABLE void addProfile(QString profileName);
Q_INVOKABLE QVariantList getLoadedProfiles(){
QVariantList rval;
foreach (QString key, loadedProfiles.keys()) {
rval.append(QVariant::fromValue<Profile*>(loadedProfiles[key].get()));
}
return rval;
};
private:
QMap<QString, std::shared_ptr<Profile>> loadedProfiles;
signals:
};
#endif // CONTROLLER_H
controller.cpp
#include "controller.h"
Controller::Controller(QObject *parent) : QObject(parent){}
void Controller::addProfile(QString profileName)
{
std::shared_ptr<Profile> profile{std::make_shared<Profile>(profileName)};
loadedProfiles[profile->getProfileName()] = profile;
}
QObject* Controller::controllerSingletonProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return Controller::instance();
}
Controller* Controller::instance(){
static Controller* controller = new Controller();
return controller;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <controller.h>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterSingletonType<Controller>("com.controller", 1, 0, "Controller", Controller::controllerSingletonProvider);
Controller::instance()->addProfile("Profile1");
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.12
import QtQuick.Window 2.12
import com.controller 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Column {
id: radioButtonColumn
Component.onCompleted: {
for(var profile in Controller.getLoadedProfiles()){
var obj = Qt.createQmlObject('import QtQuick 2.12; RadioButton { id: '+ profile.getProfileName() +';
text: '+ profile.getProfileName() +'; }', radioButtonColumn, "dynamicSnippet1");
}
}
}
}
I've tried using a signal sent from my Controller Singleton using Connections, but the parameter of the signal does not arrive. I can however call my Controllers c++ functions from qml. I'll only post the changes to the previous code sample:
main.qml changes:
Connections {
target: Controller
onProfileAdded: {
var obj = Qt.createQmlObject('import QtQuick 2.12; RadioButton { id: '+ profileName +';
text: '+ profileName +'; }', radioButtonColumn, "dynamicSnippet1");
console.log("Value changed")
}
}
controller.h changes:
signals:
void profileAdded(QString &profileName);
};
controller.cpp changes:
void Controller::addProfile(QString profileName)
{
std::shared_ptr<Profile> profile{std::make_shared<Profile>(profileName)};
loadedProfiles[profile->getProfileName()] = profile;
emit profileAdded(profileName);
}
main.cpp changes:
engine.load(url);
Controller::instance()->addProfile("Profile2");
return app.exec();
Edit: Curious, that I had to change my onProfileAdded in controller.h to profileAdded for the signal to work. However, I still do not receive the parameter profileName in my qml.
I would like to ask what is the best way to handle a requirement like this. Maybe there is another, simpler way?
Im new to both c++ and QT, so please bear with my perhaps trivial question, but my research didnt yield me anything, that I could understand at least.

Change QML Label text from a C++ function

I have a Label in QML and I want to change its text value when I click on a button. I have tried many different ways to achieve this, but nothing seems to work properly. I have used QObject::setProperty() and it seems to work when I print the new text value with qDebug(), but it does not refresh on the GUI. What am I doing wrong?
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QSettings>
#include <QQuickStyle>
#include <QtQuickControls2>
#include <QQmlContext>
#include <QIcon>
#include "Controllers/Network/network.hpp"
#include "Controllers/NFC/nfc.hpp"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QIcon::setThemeName("gallery");
QQuickStyle::setStyle("Material");
// Property bindings:
qmlRegisterType<RFS::Communication::Network>("rfs.communication.network", 1, 0, "Network");
qmlRegisterType<RFS::Communication::NFC>("rfs.communication.nfc", 1, 0, "NFC");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("availableStyles", QQuickStyle::availableStyles());
engine.load(QUrl("qrc:/main.qml"));
if (engine.rootObjects().isEmpty()) return -1;
return app.exec();
}
nfc.hpp:
#include <QObject>
#include <QtNfc/qnearfieldmanager.h>
#include <QtNfc/qnearfieldtarget.h>
namespace RFS::Communication
{
class NFC : public QObject
{
Q_OBJECT
public:
explicit NFC(QObject *parent = nullptr);
Q_INVOKABLE bool getStatus() { return pairingStatus; }
Q_INVOKABLE void changeTextValue();
private:
bool pairingStatus;
};
}
nfc.cpp:
#include <QtQuick>
#include <QQuickView>
#include "Controllers/NFC/nfc.hpp"
void RFS::Communication::NFC::changeTextValue()
{
QQuickView view;
view.setSource(QUrl("qrc:/Views/overview.qml"));
QObject *rootObject = view.rootObject();
QList<QObject*> list = rootObject->findChildren<QObject*>();
QObject *testLabel = rootObject->findChild<QObject*>("testLabel");
qDebug() << "Object" << testLabel->property("text"); // Successfully prints old value
testLabel->setProperty("text", "test1");
qDebug() << "Object" << testLabel->property("text"); // Successfully prints new value
QQmlProperty(testLabel, "text").write("test2");
qDebug() << "Object" << testLabel->property("text"); // Successfully prints new value
}
overview.qml:
import QtQuick 2.12
import QtQuick.Controls 2.12
import rfs.communication.nfc 1.0
Page {
id: page
NFC {
id: nfc
}
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Pane {
id: overviewTab
width: swipeView.width
height: swipeView.height
Button {
id: pairButton
text: qsTr("Pair new receiver")
onClicked: {
nfc.changeTextValue()
}
}
Label {
id: testLabel
objectName: "testLabel"
text: "hei" // I want to change this value
}
}
}
}
Is there any better way to achieve this? Thanks a lot in advance.
Anyone looking for a simple solution, I just came up with this trying to achieve changing label's text on c++ side by pressing a button in QML
Add to main.cpp:
Backend backend;
engine.rootContext()->setContextProperty("backend", &backend);
Create a new class (backend.h & backend.cpp)
In backend.h:
#ifndef CONTROLLERS_H
#define CONTROLLERS_H
#include <Qt>
#include <QObject>
#include <QCoreApplication>
#include <QWindow>
#include <QString>
class Backend : public QObject
{
Q_OBJECT
public:
explicit Backend(QObject *parent = nullptr);
public slots:
void setText();
QString getText();
};
#endif // CONTROLLERS_H
In backend.cpp:
#include "backend.h"
QString text = ""; // don't forget this
Backend::Backend(QObject *parent) : QObject(parent)
{
}
QString Backend::getText()
{
return text; //returns the text you set or didnt in setText()
}
void Backend::setText()
{
text = "text"; // or do anything you want with global QString text
}
In QML:
Label{
id: myLabel
}
Button{
id: myButton
onClicked:{
backend.setText()
myLabel.text = backend.getText()
}
}

How to display value of 2D Array to QML from C++, using Repeater and GridView

I have problem when display value of 2D array QString on QML from C++
I have data and a qmlRegisterType to use on QML.
This is QML file:
import QtQuick 2.12
import QtQuick.Window 2.2
import io.myserial 1.0
import QtQuick.Controls 2.3
Window {
visible: true
width: 8*square_size
height: 8*square_size
title: qsTr("Matrix LDR Value")
property int square_size: 50
Grid {
id: grid
columns: 8
Repeater{
id: rpt
model: 8*8
Rectangle{
width: square_size
height: square_size
border.color: "black"
border.width: 1
Text {
id: txt
anchors.centerIn: parent
}
MSerial{
onModelDataChanged:{
txt.text = Serial.model_data[i]
}
}
}
}
}
}
This is main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "serial.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<Serial>("io.myserial", 1, 0, "MSerial");
QQmlApplicationEngine engine;
Serial mySerial(&engine);
engine.rootContext()->setContextProperty("Serial", &mySerial);
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();
}
serial.h
#ifndef SERIAL_H
#define SERIAL_H
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QQmlApplicationEngine>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QtDebug>
#include <QObject>
#include <QString>
#include <QString>
#include "toInt16.h"
class Serial: public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QString> modelData READ modelData WRITE setModelData NOTIFY modelDataChanged)
public:
explicit Serial(QObject *parent = nullptr);
QList<QString> modelData();
Q_INVOKABLE void setModelData(const QList<QString> &modelData);
void ReadSerial();
void setUpSerial();
QByteArray serialData;
QSerialPort stm32;
QString portName;
QString buffer;
bool isStm32Avaiable;
private slots:
void readSerial();
signals:
void modelDataChanged();
private:
QList<QString> myModelData;
public slots:
};
#endif // SERIAL_H
serial.cpp
#include "serial.h"
#include "toInt16.h"
Serial::Serial(QObject *parent) : QObject(parent)
{
setUpSerial();
}
QList<QString> Serial::modelData()
{
return myModelData;
}
void Serial::setUpSerial(){
buffer = "";
isStm32Avaiable = false;
portName = "";
qDebug()<<"Num port: "<<QSerialPortInfo::availablePorts().length();
foreach (const QSerialPortInfo &serialPortInfor,QSerialPortInfo::availablePorts()) {
qDebug()<<"Has Vendo ID: "<<serialPortInfor.hasVendorIdentifier();
if(serialPortInfor.hasVendorIdentifier()){
qDebug()<<"Vendo ID: "<<serialPortInfor.vendorIdentifier();
}
qDebug()<<"Has Product ID: "<<serialPortInfor.hasProductIdentifier();
if(serialPortInfor.hasProductIdentifier()){
qDebug()<<"Product ID: "<<serialPortInfor.productIdentifier();
portName = serialPortInfor.portName();
}
if(QSerialPortInfo::availablePorts().length() >0){
isStm32Avaiable = true;
}
}
if(isStm32Avaiable){
stm32.setPortName(portName);
stm32.open(QSerialPort::ReadWrite);
stm32.setBaudRate(QSerialPort::Baud115200);
stm32.setDataBits(QSerialPort::Data8);
stm32.setParity(QSerialPort::NoParity);
stm32.setStopBits(QSerialPort::OneStop);
stm32.setFlowControl(QSerialPort::NoFlowControl);
QObject::connect(&stm32, SIGNAL(readyRead()),this,SLOT(readSerial()));
}else{
qDebug()<<"warning: Port Error . Couldn't find kit";
}
}
void Serial::setModelData(const QList<QString> &modelData)
{
if (modelData == myModelData)
return;
myModelData = modelData;
emit modelDataChanged();
}
void Serial::ReadSerial()
{
serialData.clear();
serialData = stm32.readAll();
QByteArray buffer;
qDebug()<< "Serial Data: "<<serialData;
buffer = serialData;//.split(',');
// QStringList buffer2;
//uint16_t data[64];
union INT_TO_BYTE receivedData[64];
for(int i=0; i<64; i++){
receivedData[i].b[0] = buffer.data()[i*2];
receivedData[i].b[1] = buffer.data()[i*2+1];
receivedData[i].i = (uint16_t)receivedData[i].b[0]|(uint16_t)receivedData[i].b[1]<<8;
qDebug() << "This is data "<<i<<" "<<receivedData[i].i;
myModelData.append(QString::number(receivedData[i].i));
}
//setModelData(myModelData);
emit modelDataChanged();
}
toInt16.h
#ifndef TOINT16_H
#define TOINT16_H
#include <QTextCodec>
union INT_TO_BYTE
{
uint16_t i;
char b[2];
};
//union INT_TO_BYTE receivedData[128];
#endif // TOINT16_H
I received data but I cannot display it on QML whenever data is changed. I set it change 1view/sec.
My logic is: Whenever data change, QML display new data
In your code I see the following errors:
Or you register a type with qmlRegisterType or set it as contextProperty, not both. I recommend you check Choosing the Correct Integration Method Between C++ and QML.
In your repeater you are creating N MySerial but you can only have 1, the idea is that you create a MySerial and the information is passed to the model.
Do not use QList<QString> but QStringList.
Your data should not be written from QML so it is not necessary for the modelData property to be Writeble.
Considering the above and avoiding the part of the decoding of the data since I do not know the format that will replace it creating random data the solution is the following:
serial.cpp
#ifndef SERIAL_H
#define SERIAL_H
#include <QObject>
#include <QSerialPort>
class Serial : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList modelData READ modelData NOTIFY modelDataChanged)
public:
explicit Serial(QObject *parent = nullptr);
QStringList modelData() const;
Q_SIGNALS:
void modelDataChanged();
private Q_SLOTS:
void readSerial();
private:
void setUpSerial();
QStringList m_modelData;
bool isStm32Avaiable;
QString portName;
QSerialPort stm32;
};
#endif // SERIAL_H
serial.cpp
#include "serial.h"
#include "toInt16.h"
#include <QSerialPortInfo>
#include <QDebug>
Serial::Serial(QObject *parent) : QObject(parent){
m_modelData.reserve(64);
setUpSerial();
}
QStringList Serial::modelData() const{
return m_modelData;
}
void Serial::setUpSerial(){
isStm32Avaiable = false;
portName = "";
qDebug()<<"Num port: "<<QSerialPortInfo::availablePorts().length();
for (const QSerialPortInfo &serialPortInfor : QSerialPortInfo::availablePorts()) {
qDebug()<<"Has Vendo ID: "<<serialPortInfor.hasVendorIdentifier();
if(serialPortInfor.hasVendorIdentifier()){
qDebug()<<"Vendo ID: "<<serialPortInfor.vendorIdentifier();
}
if(serialPortInfor.hasProductIdentifier()){
qDebug()<<"Product ID: "<<serialPortInfor.productIdentifier();
portName = serialPortInfor.portName();
}
if(!QSerialPortInfo::availablePorts().isEmpty()){
isStm32Avaiable = true;
break;
}
}
if(isStm32Avaiable){
stm32.setPortName(portName);
stm32.open(QSerialPort::ReadWrite);
stm32.setBaudRate(QSerialPort::Baud115200);
stm32.setDataBits(QSerialPort::Data8);
stm32.setParity(QSerialPort::NoParity);
stm32.setStopBits(QSerialPort::OneStop);
stm32.setFlowControl(QSerialPort::NoFlowControl);
connect(&stm32, &QIODevice::readyRead, this, &Serial::readSerial);
}else{
qDebug()<<"warning: Port Error . Couldn't find kit";
}
}
void Serial::readSerial(){
m_modelData.clear();
// FIXME
static int counter;
for(int i=0; i<64; i++){
m_modelData.append(QString::number(counter));
counter++;
}
//
emit modelDataChanged();
}
main.cpp
#include "serial.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
qmlRegisterType<Serial>("io.myserial", 1, 0, "MSerial");
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);
return app.exec();
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import io.myserial 1.0
Window {
visible: true
width: 8*square_size
height: 8*square_size
title: qsTr("Matrix LDR Value")
property int square_size: 50
MSerial{
id: serial
}
Grid {
columns: 8
Repeater{
model: serial.modelData
Rectangle{
width: square_size
height: square_size
border.color: "black"
border.width: 1
Text {
id: txt
anchors.centerIn: parent
text: model.modelData
}
}
}
}
}

Instantiate QML From C++

I have 2 classes, MyApp and MyAppView. MyApp class will hold other classes and the implementation will be here. (you can call it Manager class or System class). MyAppView class only interacts with main.qml like it'll have lots of "Q_PROPERTY"ies. I think you understand the point. I don't want MyApp will have "Q_PROPERTY"ies.
The scenerio is as the following;
//------------------------------------
//---------------------------main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "myapp.h"
#include "myappview.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<MyAppView>("org.myappview", 1, 0, "MyAppView");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
MyApp myApp;
return app.exec();
}
//------------------------------------
//---------------------------myappview.h
#include <QObject>
class MyAppView : QObject
{
Q_OBJECT
Q_PROPERTY(QString myString READ getMyString NOTIFY myStringChanged)
public:
MyAppView();
QString getMyString() { return m_myString; }
void setMyString(QString newString)
{
m_myString = newString;
emit myStringChanged;
}
signals:
void myStringChanged();
private:
QString m_myString;
}
//------------------------------------
//---------------------------main.qml
import QtQuick 2.0
import QtQuick.Window 2.0
import org.myappview 1.0
Window {
visible: true
MyAppView {
id: backend
}
Text {
text: qsTr(backend.myString)
}
}
//------------------------------------
//---------------------------myapp.h
#include <QObject>
#include "myappview.h"
class MyApp : QObject
{
Q_OBJECT
public:
MyApp();
private:
MyAppView appView;
void changeMyStringInAppView()
{
// This will automatically update main.qml
appView.setMyString("This is new string");
}
}
Also it is okay to reaching existing QML instance from MyApp, instead of instantiating QML from MyApp. So the main point is instantiating or reaching View class from Manager class so that I can control it easily. Maybe at some part, my logic is wrong. Please tell me if I am. I'm okay with all the suggestions.
The problem in your code is that the MyAppView of MyApp is different from the one created in QML, so if you update the appView text it will not be reflected in the backend text, so the solution is to expose an object from MyApp to QML with setContextProperty() and will call a function to establish the MyAppView created in QML (Keep in mind to create only one MyApp but you will have the same problem)
// myappview.h
#ifndef MYAPPVIEW_H
#define MYAPPVIEW_H
#include <QObject>
class MyAppView : public QObject
{
Q_OBJECT
Q_PROPERTY(QString myString READ getMyString NOTIFY myStringChanged)
public:
explicit MyAppView(QObject *parent = nullptr) : QObject(parent)
{}
QString getMyString() const { return m_myString; }
void setMyString(const QString & newString)
{
if(m_myString != newString){
m_myString = newString;
emit myStringChanged();
}
}
signals:
void myStringChanged();
private:
QString m_myString;
};
#endif // MYAPPVIEW_H
// myapp.h
#ifndef MYAPP_H
#define MYAPP_H
#include "myappview.h"
#include <QObject>
class MyApp : public QObject
{
Q_OBJECT
public:
explicit MyApp(QObject *parent = nullptr) : QObject(parent),
appView(nullptr)
{}
Q_INVOKABLE void setAppView(MyAppView *value){
appView = value;
}
void changeMyStringInAppView()
{
if(appView)
appView->setMyString("This is new string");
}
private:
MyAppView *appView;
};
#endif // MYAPP_H
// main.cpp
#include "myapp.h"
#include "myappview.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<MyAppView>("org.myappview", 1, 0, "MyAppView");
MyApp myapp;
QTimer::singleShot(1000, &myapp, &MyApp::changeMyStringInAppView);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myapp", &myapp);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
// main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import org.myappview 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MyAppView {
id: backend
}
Text {
text: qsTr(backend.myString)
}
Component.onCompleted: myapp.setAppView(backend)
}

Passing QVariant from QML to C++

I am trying to send a list from QML to c++. I've tried with a string and an integer with success but when I try with a QVariant I get the error:
QObject::connect: No such slot MyClass::cppSlot(QVariant) in ../test_2206/main.cpp:31
My main.qml
import QtQuick 2.4
import QtQuick.Layouts 1.1
import Material.ListItems 0.1 as ListItem
import Material.Extras 0.1
import QtQuick.Controls 1.3 as QuickControls
import QtQuick.Controls 1.4
import Material 0.2
Window {
visible: true
property var listCloud: ["nuage1", "nuage2", "nuage3", "nuage4"]
id: item
width: 100; height: 100
signal qmlSignal(variant msg)
/* MouseArea {
anchors.fill: parent
onClicked: item.qmlSignal("Hello from QML")
}*/
Rectangle{
id:sousRect
color:"red"
width:100; height:100
Button{
id:buttonTest
onClicked: {
item.qmlSignal(listCloud)
}
}
}
}
My main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QQmlComponent>
#include <QDebug>
#include "myclass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
QObject *object = component.create();
MyClass myClass;
QObject::connect(object, SIGNAL(qmlSignal(QVariant)),
&myClass, SLOT(cppSlot(QVariant)));
return app.exec();
}
My myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QDebug>
#include <QString>
#include <QList>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0);
signals:
public slots:
void cppSlot(QVariant &msg);
};
#endif // MYCLASS_H
My myclass.cpp
#include "myclass.h"
MyClass::MyClass(QObject *parent):
QObject(parent)
{
}
void MyClass::cppSlot(QVariant &msg){
qDebug() << "c++: bien reçu chef " << msg;
}
I don't understand why I can't put a QVariant parameter in this signal.
Any help is welcome :)
Change your slot to receive his parameter by value, rather than by reference. Like this:
void cppSlot(QVariant msg);
I think it has something to do with QML refusing to pass Window's property by reference. Anyway, Qt often sends signal by-value even if your signal/slot signature[s] declare arguments by-reference. For more on the topic see this and that
Don't use references between QML and C++, they won't work.
void cppSlot(QVariant & msg);
You can also create Q_INVOKABLE function in C++ and call it directly from QML. Here is how this looks like:
main.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlProperty>
#include <QQmlComponent>
#include <QDebug>
#include "myclass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
auto myobject = new MyClass(&app);
engine.rootContext()->setContextProperty(QStringLiteral("myobject"), myobject);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
{
return -1;
}
return app.exec();
}
mycass.h:
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QDebug>
#include <QString>
#include <QList>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass(QObject *parent = 0);
Q_INVOKABLE void cppSlot(QVariant msg);
};
#endif // MYCLASS_H
myclass.cpp:
#include "myclass.h"
MyClass::MyClass(QObject *parent):QObject(parent){}
void MyClass::cppSlot(QVariant msg)
{
qDebug() << "c++: bien reçu chef " << msg;
}
main.qml:
import QtQuick 2.4
import QtQuick.Layouts 1.1
import Material.ListItems 0.1 as ListItem
import Material.Extras 0.1
import QtQuick.Controls 1.3 as QuickControls
import QtQuick.Controls 1.4
import Material 0.2
Window
{
visible: true
property var listCloud: ["nuage1", "nuage2", "nuage3", "nuage4"]
id: item
width: 100; height: 100
Rectangle
{
id:sousRect
color:"red"
width:100; height:100
Button
{
id:buttonTest
onClicked: myobject.cppSlot(listCloud)
}
}
}
But if you like to use SIGNAL SLOTS there is Connections Object in QML. Your main.qml will look then like that:
Window
{
visible: true
property var listCloud: ["nuage1", "nuage2", "nuage3", "nuage4"]
id: item
width: 100; height: 100
Connections
{
target: buttonTest
onClicked: myobject.cppSlot(listCloud)
}
Rectangle
{
id:sousRect
color:"red"
width:100; height:100
Button
{
id:buttonTest
}
}
}
Q_INVOKABLE is nothing else as an public SLOT, it will be called through meta-object system.