in the past i resolved a problem with QQmlListProperty for expose c++ qlist to qml, and the problem was resolved, now i try use their solution again, but i can't.
the first time the list exposed to qml every time are different, this time no, i have a Qlist in a main class, and many data from her are loaded from db, and others changed in runtime.
When the application start, the load of data and exposed to Qml is ok, i need change of page(gui) and keep the data stored in program and probably used many of their data in other guis (as text), and every time i change of page (with loader element) the program crashes, i believe a directions of pointers are changed in qml when the data from c++ class is copied to gui in qml.
My Code from QmlListproperty class and method of Load data in qml, and copy from the main qml to page.
definition of class ListBombsUi.h"
#include "StructBombUi.h"
class ListBombsUi: public QObject{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<CStructBombUi> bombsUi READ bombsUiList NOTIFY bombsUiChanged);
Q_CLASSINFO("DefaultProperty", "bombsUi");
public:
ListBombsUi(QObject *parent=0);
QQmlListProperty<CStructBombUi> bombsUiList();
static void appendBombUi(QQmlListProperty<CStructBombUi> *list, CStructBombUi *pdt);
static void clear(QQmlListProperty<CStructBombUi> *property);
static int listSize(QQmlListProperty<CStructBombUi> *property);
static CStructBombUi *bombUiAt(QQmlListProperty<CStructBombUi> *property, int index);
void addBomb(CStructBombUi *bombUi);
Q_INVOKABLE int size();
void Q_INVOKABLE getMemory();
Q_INVOKABLE void clearList();
CStructBombUi* getValue(int index) const;
QList<CStructBombUi *> copyList();
signals:
void bombsUiChanged();
public:
int lastAdded;
private:
CStructBombUi *item;
QList<CStructBombUi*> m_BombsUi;
i register how qmltype
qmlRegisterType<ListBombsUi>("BombsListUiModel", 1, 0, "ListBombsUi");
definition of main methods of ListBombsUi
ListBombsUi::ListBombsUi(QObject *parent):QObject(parent)
{}
QQmlListProperty<CStructBombUi> ListBombsUi::bombsUiList()
{
return QQmlListProperty<CStructBombUi>(this, &m_BombsUi, &ListBombsUi::appendBombUi,
&ListBombsUi::listSize,
&ListBombsUi::bombUiAt,
&ListBombsUi::clear);
emit bombsUiChanged();
}
void ListBombsUi::appendBombUi(QQmlListProperty<CStructBombUi> *list, CStructBombUi *pdt)
{
ListBombsUi *bombsList= qobject_cast<ListBombsUi *>(list->object);
if(bombsList) {
pdt->setParent(bombsList);
bombsList->m_BombsUi.append(pdt);
bombsList->bombsUiChanged();
}
}
void ListBombsUi::clearList()
{
m_BombsUi.clear();
emit bombsUiChanged();
}
void ListBombsUi::addBomb(CStructBombUi *bombUi)
{
m_BombsUi.append(bombUi);
emit bombsUiChanged();
}
definition in main class for two data, i use the first as aux, but is possible use the second directly(their are my idea originallly)
QList<CStructBombUi * > listBombs;
ListBombsUi *listBufferBombs;
i assign a listBufferBombs to element in qml
listBufferBombs = mainObject->findChild<ListBombsUi*>("listGralBombs");
method for expose data to qml
void EgasWindowWork::setDataOfBombsInModels()//, const QList <CEstrucBombUi> *const dataArrayBombs)
{
if( (mainObject) ) {
CStructBombUi e,k,j;
e.initializing(QStringList()<<"text1"<<"text2"<<"text3");
e.update(QString("15"), QString("1"), QString("1"), QString("1"),QString("1"));
k.initializing(QStringList()<<"text1"<<"text2");
k.update(QString("2"), QString("2"), QString("2"), QString("2"),QString("2"));
listBombs.append(&e);
listBombs.append(&k);
for(qint16 i=0; i<listBombs.size() ; i++)
{ listBufferBombs->addBomb( listBombs.value(i) ); }
QMetaObject::invokeMethod(mainObject, "setDataOfModelBombs"); //this method copy the list located in main qml for page
}
the metod in main.qml file
function setDataOfModelBombs()
{
if(itemOfPageLoaded) {
itemOfPageLoaded.listBombs=listed
itemOfPageLoaded.setDataOfBombs() //this function put every data inside the every field of qml gui element
}
}
declaration of loader element in main.qml file
Loader{
id: pageLoader
focus: true
//anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
height: parent.height-50
anchors.top: headerIcons.bottom
objectName: "switchPages"
z: 2
source: "BombsUi.qml"
property bool valid: item !== null
Binding {
target: pageLoader.item
property: "numberBombs"
value: numberOfBombs
when: pageLoader.status == Loader.Ready
}
}
method in qml file loaded as page
function setDataOfBombs()
{
var size=newList.size()
arrayBombs.model=size //i use a repater container, and model is the number of elements loaded
if(size>0) {
for(var i=0; i<size; i++) {
//exactly in the next line the error happens
arrayBombs.itemAt(i).priceText=newList.bombsUi[i].ultimateProductPrice
arrayBombs.itemAt(i).volumeText = newList.bombsUi[i].ultimateProductVolume
//exist mor date, but with this are sufficient
}
}
}
and here is declaration of element repeater-grid
Item{
width: parent.width; height: parent.height
Item{
id: bombsArea
height: parent.height; width: parent.width*0.7
anchors.leftMargin: 10
y: 3
x: 2
Grid{
id: gridOfBombsModel
width: parent.width; height: parent.height
spacing: 7
rows: 6; columns: Math.round((parent.width/165))
Repeater{
id: arrayBombs
BombModel{
numberBomb: (index + 1)
MouseArea{
anchors.fill: parent
onClicked:{
parent.borderProperties.color="red"
parent.borderProperties.width=1.5
}
}
}
}
}
}
the program load the page 1 ok, but i change a page "n" and return to page 1, crashes. i accept alternatives to, Thanks for your help.
Well, i detected two things, fail in constructor of CStructBombsUi and i descart use of auxiliar list, and the use of CStructBombsUi* exclusively, and change the function in qml file, i show the changes.
Changes in main.qml
function setDataOfModelBombs()
{
if(itemOfPageLoaded) {
itemOfPageLoaded.numberBombs=listBombs.size()
itemOfPageLoaded.setDataOfBombs(listBombs.bombsUi)
}
}
changes in detailsbombs.qml (file loaded as page), and here i don't instance a ListBombs
function setDataOfBombs(bombsUi)
{
if(numberBombs>0) {
for(var i=0; i<numberBombs; i++)
{
arrayBombs.itemAt(i).priceText=bombsUi[i].ultimateProductPrice
arrayBombs.itemAt(i).volumeText = bombsUi[i].ultimateProductVolume
arrayBombs.itemAt(i).amountText= bombsUi[i].getUltimateProductMount()
arrayBombs.itemAt(i).fuelText= bombsUi[i].getUltimateProductName()
if(bombsUi[i].getFuels())
{arrayBombs.itemAt(i).setListOfFuelsInBomb( bombsUi[i].getFuels() ) }
}
}
}
The next line don't exist more
QList<CStructBombUi* > listBombs;
And every is a pointer
CStructBombUi *e=new CStructBombUi();
i don't use a list of values, with this adjusts the program don't crashes
Related
From QML:
AudioInfoCpp is the C++ class's object. Aim to to get data from C++ and fill it in the model of the combobox.
property ListModel comboModel: cbItems
AudioInfoCpp
{
id: audioInfoCpp
Component.onCompleted:
{
fetchInputDevices()
comboModel.append(inputDevices1)
console.log(inputDevices1)
}
}
ComboBox
{
id: comboBoxM;
width: 100
model: audioInfoCpp.inputDevices1;
textRole: "langDescription";
}
From .h
#ifndef AUDIOINFO_H
#define AUDIOINFO_H
#include <QAudioDeviceInfo>
#include <QList>
#include <QVariantList>
class AudioInfo : public QObject
{
Q_OBJECT
protected:
QAudioDeviceInfo objQAudioDeviceInfo;
public:
AudioInfo();
Q_PROPERTY(QVariantList inputDevices1 READ inputDevices1 WRITE setInputDevices1 NOTIFY inputDevices1Changed)
Q_INVOKABLE void fetchInputDevices();
QVariantList inputDevices1() const
{
return m_inputDevices1;
}
public slots:
void setInputDevices1(QVariantList inputDevices1)
{
if (m_inputDevices1 == inputDevices1)
return;
m_inputDevices1 = inputDevices1;
emit inputDevices1Changed(m_inputDevices1);
}
signals:
void inputDevices1Changed(QVariantList inputDevices1);
private:
QVariantList m_inputDevices1;
};
#endif // AUDIOINFO_H
.cpp :
void AudioInfo::fetchInputDevices()
{
QList<QAudioDeviceInfo> devices1 = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
if (devices1.empty())
{
qDebug() << "No audio input devices";
}
QVariantList tt;
for (const QAudioDeviceInfo& device : devices1)
{
tt.append(device.deviceName());
}
setInputDevices1( tt );
}
In QML as well as CPP I can see the data printed from the list but I am not able to populate the combobox. There are no errors.
Please guide.
The problem comes from this line:
textRole: "langDescription"
That means it will look through each element in your model for the langDescription field. But you are populating your QVariantList with QStrings:
QVariantList tt;
for (const QAudioDeviceInfo& device : devices1)
{
tt.append(device.deviceName());
}
QStrings don't have a langDescription field, so nothing gets displayed.
To solve this try completely removing the textRole line and allow QML to automatically figure out how to display the names correctly.
I'm using Qt 5.15.0 on Manjaro.
I have a table I created with TableView in qml.
I can delete a row with right click on the Id field like this:
The deletion etc works but I get these errors in qml:
The method for delete rows looks like this in main.qml:
function deleteRowFromDatabase(row) {
console.log("before" + model.countOfRows())
if (!model.removeEntry(row)) {
console.log(qsTr("remove row %1 failed").arg(row))
}
model = QuestionsProxyModel
console.log("after" + model.countOfRows())
}
The error point to the delegate row of id in main.qml
DelegateChoice {
column: 0
delegate: QuestionIdDelegate {
id: questionIdDelegate
width: tableView.columnWidthProvider(column)
text: model.id /// this is undefined
row: model.row
Component.onCompleted: {
questionIdDelegate.markForDelete.connect(
tableView.deleteRowFromDatabase)
}
}
}
Removing of the rows is implemented from C++ In a class derrived from QIdentityProxyModel in questionsproxmodel.h:
bool QuestionsProxyModel::removeEntry(int row)
{
return removeRows(row, 1);
}
This model takes a class QuestionSqlTableModel derrived from QSqlTableModel as a source model
The remove rows is implemented like this in questionssqltablemodel.qml:
bool QuestionSqlTableModel::removeRows(int row, int count,
const QModelIndex &parent)
{
auto result = QSqlTableModel::removeRows(row, count, parent);
if (result) {
select(); // row is not deleted from sql database until select is called
}
return result;
}
From my understanding the countOfRows of the model gets updated only after the select() is called so I assume between QSqlTableModel::removeRows and select the TableView reads one more time with the non existing row and causes these errors in QML. How can that be prevented?
Full Source code to try it out:
main.cpp:
#include <QDebug>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>
#include <QFile>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include "questionsproxymodel.h"
#include "questionsqltablemodel.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QUrl dbUrl{"file:///home/sandro/Desktop/test.db"};
auto exists = QFile::exists(dbUrl.toLocalFile());
auto db = QSqlDatabase::addDatabase("QSQLITE", "DBConnection");
db.setDatabaseName(dbUrl.toLocalFile());
db.open();
if (!exists) {
const QString questionTableName = "questions";
QSqlQuery query{db};
query.exec("CREATE TABLE " + questionTableName +
" ("
"id INTEGER PRIMARY KEY AUTOINCREMENT)");
}
QScopedPointer<QuestionSqlTableModel> questionSqlTableModel(
new QuestionSqlTableModel(nullptr, db));
QScopedPointer<QuestionsProxyModel> questionsProxyModel{
new QuestionsProxyModel};
questionsProxyModel->setSourceModel(questionSqlTableModel.get());
if (!exists) {
for (int i = 0; i < 10; ++i) {
questionsProxyModel->addNewEntry();
}
}
QQmlApplicationEngine engine;
qmlRegisterSingletonInstance<QuestionsProxyModel>(
"QuestionsProxyModels", 1, 0, "QuestionsProxyModel",
questionsProxyModel.get());
const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
engine.load(url);
return app.exec();
}
questionssqltablemodel.h
#include <QSqlTableModel>
class QuestionSqlTableModel : public QSqlTableModel {
Q_OBJECT
public:
explicit QuestionSqlTableModel(QObject *parent = nullptr,
const QSqlDatabase &db = QSqlDatabase());
bool removeRows(int row, int count, const QModelIndex &parent) override;
};
questionssqltablemodel.cpp
#include "questionsqltablemodel.h"
#include <QBuffer>
#include <QDebug>
#include <QPixmap>
#include <QSqlError>
#include <QSqlField>
#include <QSqlRecord>
#include <QSqlRelationalDelegate>
QuestionSqlTableModel::QuestionSqlTableModel(QObject *parent,
const QSqlDatabase &db)
: QSqlTableModel{parent, db}
{
setTable("questions");
setSort(0, Qt::AscendingOrder);
if (!select()) {
qDebug() << "QuestionSqlTableModel: Select table questions failed";
}
setEditStrategy(EditStrategy::OnFieldChange);
}
bool QuestionSqlTableModel::removeRows(int row, int count,
const QModelIndex &parent)
{
auto result = QSqlTableModel::removeRows(row, count, parent);
if (result) {
select(); // row is not deleted from sql database until select is called
}
return result;
}
questionsproxymodel.h:
#include <QIdentityProxyModel>
#include <QObject>
class QuestionsProxyModel : public QIdentityProxyModel {
Q_OBJECT
enum questionRoles {
idRole = Qt::UserRole + 1,
};
public:
QuestionsProxyModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
bool addNewEntry();
Q_INVOKABLE bool removeEntry(int row);
Q_INVOKABLE int countOfRows() const;
private:
QModelIndex mapIndex(const QModelIndex &source, int role) const;
};
questionsproxymodel.h:
#include "questionsproxymodel.h"
#include <QBuffer>
#include <QDebug>
#include <QPixmap>
#include <QByteArray>
#include <QSqlError>
#include <QSqlTableModel>
QuestionsProxyModel::QuestionsProxyModel(QObject *parent)
: QIdentityProxyModel(parent)
{
}
QHash<int, QByteArray> QuestionsProxyModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[idRole] = "id";
return roles;
}
QVariant QuestionsProxyModel::data(const QModelIndex &index, int role) const
{
QModelIndex newIndex = mapIndex(index, role);
if (role == idRole) {
return QIdentityProxyModel::data(newIndex, Qt::DisplayRole);
}
return QIdentityProxyModel::data(newIndex, role);
}
bool QuestionsProxyModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
QModelIndex newIndex = mapIndex(index, role);
if (role == idRole) {
return QIdentityProxyModel::setData(newIndex, value, Qt::EditRole);
}
return QIdentityProxyModel::setData(newIndex, value, role);
}
bool QuestionsProxyModel::addNewEntry()
{
auto newRow = rowCount();
if (!insertRows(newRow, 1)) {
return false;
}
if (!setData(createIndex(newRow, 0), newRow + 1)) {
removeRows(newRow, 1);
return false;
}
auto sqlModel = qobject_cast<QSqlTableModel *>(sourceModel());
return sqlModel->submit();
}
bool QuestionsProxyModel::removeEntry(int row)
{
return removeRows(row, 1);
}
int QuestionsProxyModel::countOfRows() const
{
return rowCount();
}
QModelIndex QuestionsProxyModel::mapIndex(const QModelIndex &source,
int role) const
{
switch (role) {
case idRole:
return createIndex(source.row(), 0);
}
return source;
}
main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Window 2.15
import Qt.labs.qmlmodels 1.0
import QtQuick.Controls.Material 2.15
import QuestionsProxyModels 1.0
ApplicationWindow {
id: root
visible: true
width: 1460
height: 800
TableView {
id: tableView
width: parent.width
anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
reuseItems: true
clip: true
property var columnWidths: [60]
columnWidthProvider: function (column) {
return columnWidths[column]
}
model: QuestionsProxyModel
delegate: DelegateChooser {
id: chooser
DelegateChoice {
column: 0
delegate: QuestionIdDelegate {
id: questionIdDelegate
width: tableView.columnWidthProvider(column)
text: model.id
row: model.row
Component.onCompleted: {
questionIdDelegate.markForDelete.connect(
tableView.deleteRowFromDatabase)
}
}
}
}
ScrollBar.vertical: ScrollBar {}
function deleteRowFromDatabase(row) {
console.log("before" + model.countOfRows())
if (!model.removeEntry(row)) {
console.log(qsTr("remove row %1 failed").arg(row))
}
model = QuestionsProxyModel
console.log("after" + model.countOfRows())
}
}
}
QuestionIdDelegate.qml:
import QtQuick 2.15
import QtQuick.Controls 2.15
TextField {
property int row
signal markForDelete(int row)
id: root
implicitHeight: 100
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
readOnly: true
background: Frame {}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
eraseContextMenu.popup(root, 0, mouseArea.mouseY + 10)
}
}
Menu {
id: eraseContextMenu
y: root.y
MenuItem {
text: qsTr("Delete entry")
onTriggered: {
eraseDialog.open()
eraseContextMenu.close()
}
}
MenuItem {
text: qsTr("Cancel")
onTriggered: {
eraseContextMenu.close()
}
}
}
Dialog {
id: eraseDialog
title: qsTr("Delete database entry")
modal: true
focus: true
contentItem: Label {
id: label
text: qsTr("Do you really want to erase the entry with id %1?").arg(
root.text)
}
onAccepted: {
markForDelete(root.row)
}
standardButtons: Dialog.Ok | Dialog.Cancel
}
}
.pro:
QT += quick
QT += quickcontrols2
QT += sql
CONFIG += c++17
DEFINES += QT_DEPRECATED_WARNINGS
HEADERS += \
questionsproxymodel.h \
questionsqltablemodel.h
SOURCES += \
main.cpp \
questionsproxymodel.cpp \
questionsqltablemodel.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
edit:
The answer solves the error stated above. However I detected another problem with the code.
If I delete the row 1 and then the row 2 my output looks like this:
This binding loop error points Dialog in QuestionsIDDelegate:
Dialog {
id: eraseDialog
title: qsTr("Delete database entry")
modal: true
focus: true
contentItem: Label {
id: label
text: qsTr("Do you really want to erase the entry with id %1?").arg(
root.text)
}
onAccepted: {
markForDelete(root.row)
}
standardButtons: Dialog.Ok | Dialog.Cancel
}
One way to prevent this is to check that the property is not undefined:
text: model.id === undefined ? "" : model.id
I'm not going to fix the code, but I am going to try to explain why you are having trouble. I feel the why of things will help more people.
This image may not be 100% accurate but it is how one must visualize things in their mind. You have three distinct lanes an object lives in when you introduce QML: C++, QML Engine, and JavaScript engine. Each and every one of those lanes believes beyond a shadow of a doubt that they control the life and death of the object.
When you are just passing integers around that are passed by value, this is no issue. When you are passing QStrings around, because of the copy-on-write charter of Qt, this is only a minor issue. When you are passing real objects around, worse yet, complex containers of real objects, you have to understand this completely. The answer that "fixed" your problem really only masked it. You will find there is a lot of QML and JavaScript code out there that exists only to mask an application not respecting the lanes.
Had this application used an actual database there would be a fourth lane. Yes, SQLite provides an SQL interface and allows many things, but an actual database has an external engine providing shared access and controlling the life of cursors. SQLite files tend to be single user. Yes, multiple threads within your application can access it, but while your application is running you cannot open a terminal window and use command line tools to examine the database. Think of it more as a really nice indexed file system without sharing.
So, you create an object in C++ and then expose it to QML. The QML engine now believes beyond a doubt that it controls the life and death of that object despite not having its own copy.
QML is really feeble. It can't actually do much, so it has to hand any significant object off to JavaScript. The JavaScript engine now believes beyond a shadow of a doubt that it now controls the life and death of that object.
You need to also envision these lanes as independent threads. There will most likely be many threads within each, but, in general, any signal or communication between these will go on the event loop of the target as a queued event. That means it will only be processed when it finally bubbles to the top of the queue for that event loop.
This, btw, is why you can never use Smart Pointers when also using QML/JavaScript. Especially the kind that do reference counting and delete the object when there are no more references. There is no way for the C++ lane to know that QML or JavaScript are still using the object.
The answer telling you to check for undefined property is masking the problem that your code is drunk driving across all lanes. Eventually, on a faster (or sometimes slower) processor garbage collection for one of the lanes will run at a most inopportune moment and you will be greeted with a stack dump. (The drunk driving code will hit a tree that does not yield.)
Correct Solution #1:
Never use QML or JavaScript. Just use C++ and Widgets. Stay entirely within the C++ lane. If that is a route open to you it's a good way to go. There is an awful lot of production code out there doing just that. You can obtain a copy of this book (or just download the source code from the page) and muddle through building it.
Correct Solution #2:
Never actually do anything in QML or JavaScript. This is an all together different solution than #1. You can use QML for UI only, leaving all logic in C++. Your code is failing because you are trying to actually do something. I haven't built or tried your code. I simply saw
function deleteRowFromDatabase(row)
which shouldn't exist at all. C++ holds your model. You emit a signal from your QML/JavaScript lanes when user action requires deletion. This signal becomes a queued event in the C++ lane. When it is processed the row will be deleted and the model updated. If you have properly exposed your model to QML it will emit some form of "model changed" signal and the UI will update accordingly. One of the main points of MVC (Model-View-Controler) is communication to/from the model. When the data changes it notifies the view(s).
Correct Solution #3:
Never use C++. Have your C++ be a "Hello World!" shell that just launches your QML. Never create and share an object between C++ and the other two lanes. Do everything inside of JavaScript and QML.
Binding loop errors, in general, happen when code drunk drives across all three lanes. They also happen when code doesn't view each lane as at least one distinct thread with its own event loop. The C++ lane hasn't finished (perhaps not even started) the delete operation but your code is already trying to use the result.
I don't know why, but some people edit these things and delete the correct answers. The full information was too long for SO and is contained here,
https://www.logikalsolutions.com/wordpress/uncategorized/so-you-cant-get-your-qt-models-to-work-with-qml/
with source even.
I'm using QQuickwidget in my QT5 gui. I've loaded an openstreet map on it by adding qml files accordingly.
By adding a function in the qml file, I call it through the cpp file by using QMetaObject::invokeMethod. This function adds markers at certain coordinates. This is because I want to control my markers through c++ code itself.
My problem: I am able to load the map and the markers successfully, but I'm not able to remove the markers. My aim is to show the path of a moving object on a map. Therefore, using a timer, I want to update it's position periodically. Every t_samp seconds, I want to remove the marker and add it somewhere else.
the 'item' created in the addMarker code is added to the map by using map.addMapItem(item). By appending map.removeMapItem(item) to the function, the marker disappears. But, the problem is, I can't seem to be able to access 'item' outside the function. Therefore, I cant use map.removeMapItem since I cant input the marker.
I also tried making the function output 'item', so that I can then use it to add/remove the marker. Unfortunately, I dont know the data type of item, and hence cant receive it.
mapview.qml:
import QtQuick 2.12
import QtLocation 5.12
import QtPositioning 5.12
Item {
id: window
Plugin
{
id: mapPlugin
name:"osm"
}
function addMarker(latitude, longitude)
{
var component= Qt.createComponent("qrc:///qml/marker.qml")
var item= component.createObject(window, {coordinate: QtPositioning.coordinate(latitude,longitude)})
map.addMapItem(item)
}
Map
{
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(15.4561,73.8021);
zoomLevel: 14
}
}
marker.qml:
import QtQuick 2.12
import QtLocation 5.12
import QtPositioning 5.12
MapQuickItem
{
id: marker
anchorPoint.x: marker.width / 4
anchorPoint.y: marker.height
sourceItem: Image
{
id: icon
source: "qrc:///images/mapmark.png"
sourceSize.width: 50
sourceSize.height: 50
}
}
mainwindow.cpp: (only relevant snippet)
QObject* target= qobject_cast<QObject*>(ui->quickWidget->rootObject());
QString functionName= "addMarker";
QMetaObject::invokeMethod(target,functionName.toUtf8().constData(), Qt::AutoConnection, Q_ARG(QVariant, 15.4561), Q_ARG(QVariant,73.8021));
Instead of exporting the QML marker to C++, it is best to export a QObject from C++ to QML, and since you want to handle several markers you must use a model.
Explanation of the approach:
The MVC pattern is Qt's natural way of handling a lot of information, and for this it implements views such as MapItemView, and models that can be created based on QAbstractXXXModel. So only the responsibility is to specialize the classes for the objective, such as implementing the logic of just keeping n elements and if there is a new element, remove the oldest one.
Why is it better to export a QObject to QML? The life cycle of the objects in QML are handled by QML, so in your case you could access the markers at a given time QML could delete it so that the pointer in C ++ would append non-reserved memory. Another advantage is that the Q_PROPERTY are recognized in QML and the data type is known by QML and C ++, unlike if you export a QML object to C ++ since only the properties of QObject or QQuickItem will be used. Also when exported using setContextProperty the QObject is global. The disadvantage is that more code is added. For more details read Interacting with QML from C++.
markermodel.h
#ifndef MARKERMODEL_H
#define MARKERMODEL_H
#include <QAbstractListModel>
#include <QGeoCoordinate>
class MarkerModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QGeoCoordinate current READ current NOTIFY currentChanged)
public:
enum MarkerRoles{
PositionRole = Qt::UserRole + 1000,
};
explicit MarkerModel(QObject *parent = nullptr);
void moveMarker(const QGeoCoordinate & coordinate);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
int maxMarkers() const;
void setMaxMarkers(int maxMarkers=0);
QGeoCoordinate current() const;
signals:
void currentChanged();
private:
void insert(int row, const QGeoCoordinate & coordinate);
void removeLastMarker();
QList<QGeoCoordinate> m_markers;
QGeoCoordinate m_current;
int m_maxMarkers;
};
#endif // MARKERMODEL_H
markermodel.cpp
#include "markermodel.h"
MarkerModel::MarkerModel(QObject *parent)
: QAbstractListModel(parent),
m_maxMarkers(0)
{
}
void MarkerModel::moveMarker(const QGeoCoordinate &coordinate)
{
QGeoCoordinate last = m_current;
m_current = coordinate;
Q_EMIT currentChanged();
if(!last.isValid())
return;
if(m_maxMarkers == 0){
insert(0, last);
return;
}
if(rowCount() >= m_maxMarkers){
while (rowCount() >= m_maxMarkers)
removeLastMarker();
removeLastMarker();
}
insert(0, last);
}
int MarkerModel::maxMarkers() const
{
return m_maxMarkers;
}
void MarkerModel::setMaxMarkers(int maxMarkers)
{
m_maxMarkers = maxMarkers > 1 ? maxMarkers: 0;
}
QGeoCoordinate MarkerModel::current() const
{
return m_current;
}
int MarkerModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_markers.count();
}
QVariant MarkerModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if(role == PositionRole)
return QVariant::fromValue(m_markers[index.row()]);
return QVariant();
}
QHash<int, QByteArray> MarkerModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[PositionRole] = "position";
return roles;
}
void MarkerModel::insert(int row, const QGeoCoordinate & coordinate)
{
beginInsertRows(QModelIndex(), row, row);
m_markers.insert(row, coordinate);
endInsertRows();
}
void MarkerModel::removeLastMarker()
{
if(m_markers.isEmpty())
return;
beginRemoveRows(QModelIndex(), rowCount()-1, rowCount()-1);
m_markers.removeLast();
endRemoveRows();
}
mainwindow.h
// ...
MarkerModel marker_model;
// ...
mainwindow.cpp
// ...
ui->quickWidget->rootContext()->setContextProperty("marker_model", &marker_model);
ui->quickWidget->setSource(QUrl("qrc:/mapview.qml"));
// ...
main.qml
// ...
Map{
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(15.4561,73.8021);
zoomLevel: 14
Marker{
coordinate: marker_model.current
}
MapItemView{
model: marker_model
delegate: Marker{
coordinate: model.position
}
}
}
// ...
The complete example is here
My Qt Quick application on Windows is experiencing a very weird crash.
I have a QObject derived c++ class called QObjectVector that allows QML side to access QObject pointers in a container.
On the QML side, I have a bunch of buttons displaying names of objects in that container. when I scroll the mouse wheel, I manually update each button's currentItem property.
Strangely, and only sometimes, after an unpredictable number of fast scrolling, the program crashes (stopped working). After debugging, I found that the crashing happens when the at function access m_data[i]. I have made sure that i is within the valid range [0, m_data.size()), so it is not an index-out-of-range error. My guess is that, somehow, the QML engine deletes my objects on the heap when trying to manage memory.
QObjectVector.h
#pragma once
#include <QObject>
#include <QVector>
#include "Types.h"
namespace LPP
{
class QObjectVector : public QObject
{
Q_OBJECT
Q_PROPERTY(Int size READ size NOTIFY sizeChanged)
public:
explicit QObjectVector();
virtual ~QObjectVector();
Int size();
Q_INVOKABLE QObject* at(Int);
void push(QObject*);
void remove(Int);
void remove(QObject*);
QVector<QObject*> &getData();
bool deleteChildrenOnDestroy;
private:
QVector<QObject*> m_data;
signals:
void sizeChanged();
public slots:
};
}
QObjectVector.cpp
...
QObject* QObjectVector::at(Int i)
{
qDebug() << "size: " << this->m_data.size();
for (int i = 0; i < this->m_data.size(); i++){
qDebug() << "i: " << i << " item: ";
//when I scroll rapidly, crash happens here when I trace objects in vector
qDebug() << this->m_data[i];
}
qDebug() << "returning.";
return (i >= 0 && i < this->m_data.size()) ? this->m_data[i] : nullptr;
}
....
ButtonContainer.qml
....
//this function is called when I scroll mouse wheel.
//itemList contains list of Buttons
function refreshDisplay() {
var i, j = 0;
for (i = 0; i < itemList.length; i++){
itemList[i].visible = itemList[i].enabled = false;
}
var start = Utils.clamp(Math.floor(scrollView.flickableItem.contentY / (itemHeight + itemVSpacing)), 0, currentFolder.size);
var end = Utils.clamp(Math.ceil((scrollView.flickableItem.contentY + scrollView.flickableItem.height) / (itemHeight + itemVSpacing)), 0, currentFolder.size);
var item;
for (i = start; i < end; i++){
if (j >= itemList.length){
itemList.push(tableItem_comp.createObject(tableArea));
}
itemList[j].visible = itemList[j].enabled = true;
item = currentFolder.at(i);
itemList[j].currentItem = item;
j++;
}
}
....
Button.qml
....
property var currentItem: null;
anchors.left: parent.left
anchors.right: parent.right
SimpleButton {
id: button
width: 100
height: 50
text: currentItem.name;
onClicked: {
viewer.select(currentItem);
}
}
....
This problem is very weird and unpredictable, and solving it is very crucial to the development of my app, so please please please, any help will be appreciated.
Thank you!
Tommy
This is because the objects you're returning are owned by the QML engine, and can therefore be garbage collected at any time.
Try calling
QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership)
on each object before it is returned to QML (e.g. when it's constructed).
See this section of the documentation:
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.
I am trying to set a QColor property of a custom QQuickPaintedItem by passing a QColor from C++ to QML. I have tried the following:
Converting QColor to QVariant. In the JS debugger, the color object was empty.
Converting QColor to a color string "#RRGGBB". This still throws the type error.
QML Code:
m_DisplayScreens[m_DisplayScreens.length].backgroundColor = m_Model.getBackgroundColor(i_Timer);
m_DisplayScreens is a list of my custom QML widget. I can set the backgroundColor property just fine by doing something like.
DisplayScreen
{
backgroundColor: "Red"
}
The "m_Model" object is simply a QObject which is the 'backend' of the QML form. The code for getBackgroundColor is as follows:
Q_INVOKABLE QString getBackgroundColor(int index);
QString CountDownPanelModel::getSegmentColor(int index)
{
return "#003300";
}
The specific error is: xxx.js:19: TypeError: Type error
Any help would be appreciated. I've been banging my head against this for a few hours now.
Thanks,
Jec
1st Edit:
Ok folks, here is my attempt when returning a QColor;
class CountDownPanelModel : public QObject
{
Q_OBJECT
public:
explicit CountDownPanelModel(QObject *parent = 0);
~CountDownPanelModel() = default;
Q_INVOKABLE QColor getBackgroundColor(int index);
Q_INVOKABLE QColor getSegmentColor(int index);
};
QColor CountDownPanelModel::getBackgroundColor(int index)
{
return QColor(44, 44, 44);
//return m_TimerList->at(index)->getTimerData()->getBackgroundColor();
}
QColor CountDownPanelModel::getSegmentColor(int index)
{
return QColor(200, 200, 200);
//return m_TimerList->at(index)->getTimerData()->getSegmentColor();
}
The result of using a QColor is the same as using the QString. I get "Type Error" at the line I assign the color at. For example:
var m_DisplayScreens = [];
function createDisplays()
{
m_DisplayScreens = [];
var timerCount = m_Model.getTimerCount();
var bg = m_Model.getBackgroundColor(0);
var fg = m_Model.getSegmentColor(0)
for (var i_Timer = 0;
i_Timer < timerCount;
++i_Timer)
{
var component = Qt.createComponent("DynamicSevenSegmentDisplay.qml");
var display = component.createObject(m_Panel);
display.initialize(m_Model.getSevenSegmentDisplayInitializer())
display.y = 100 * (i_Timer);
m_DisplayScreens[m_DisplayScreens.length] = display;
m_DisplayScreens[m_DisplayScreens.length].backgroundColor = m_Model.getBackgroundColor(i_Timer);
m_DisplayScreens[m_DisplayScreens.length].segmentColor = m_Model.getSegmentColor(i_Timer)
}
m_Panel.height = 100 * timerCount;
}
Just for completeness, here is DynamicSevenSegmentDisplay.qml
SevenSegmentDisplayScreen
{
y: 0
height: 100
width: parent.width - x
backgroundColor: "Black"
borderPercentage: 10
displayCount: 20
text: "1234567890"
anchors.left: m_SettingsButton.right
anchors.leftMargin: 8
}
I'm completely confused as to why I can't assignthe QColor to backgroundColor. The debugger just shows nothing in the 'value' column. I take it this is the JS version of 'null'.
This is not a definitive answer but I found several issues. I was previously developing using Linux Mint. I recently switched to Ubuntu and found several issues with passing data between C++ and QML. I got a message about a CRC miss match.
"the debug information found in "/usr/lib/x86_64-linux-gnu/dri/i915_dri.so" does not match "/usr/lib/x86_64-linux-gnu/dri/i965_dri.so" (CRC mismatch)."
So this also could be part of the problem. But for the most part it looks like its my development machine.