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
Related
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 derived a model from QAbstractTableModel and now I want to notify, that the data of a whole row has been changed. If for example the data of a row with index 5 is changed (4 columns), than using the following code works as expected.
emit dataChanged(index(5,0), index(5, 0));
emit dataChanged(index(5,1), index(5, 1));
emit dataChanged(index(5,2), index(5, 2));
emit dataChanged(index(5,3), index(5, 3));
But if I try to achieve the same with only one emit, ALL columns of ALL rows in the view are updated.
emit dataChanged(index(5, 0), index(5, 3));
What I am doing wrong here?
Minimal example (C++11, QTCreator 4.7.1, Windows 10 (1803), 64 Bit)
demo.h
#pragma once
#include <QAbstractTableModel>
#include <QTime>
#include <QTimer>
class Demo : public QAbstractTableModel
{
Q_OBJECT
QTimer * t;
public:
Demo()
{
t = new QTimer(this);
t->setInterval(1000);
connect(t, SIGNAL(timeout()) , this, SLOT(timerHit()));
t->start();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
int c = index.column();
if (role == Qt::DisplayRole)
{
QString strTime = QTime::currentTime().toString();
if (c == 0) return "A" + strTime;
if (c == 1) return "B" + strTime;
if (c == 2) return "C" + strTime;
if (c == 3) return "D" + strTime;
}
return QVariant();
}
int rowCount(const QModelIndex &) const override { return 10; }
int columnCount(const QModelIndex &) const override { return 4; }
private slots:
void timerHit()
{
//Works
emit dataChanged(index(5,0), index(5, 0));
emit dataChanged(index(5,1), index(5, 1));
emit dataChanged(index(5,2), index(5, 2));
emit dataChanged(index(5,3), index(5, 3));
//emit dataChanged(index(5,0), index(5, 3)); // <-- Doesn't work
}
};
main.cpp
#include "demo.h"
#include <QApplication>
#include <QTreeView>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView dataView;
Demo dataModel{};
dataView.setModel( &dataModel );
dataView.show();
return a.exec();
}
I think the problem lies with certain assumptions you're making with regard the behaviour of QTreeView when the QAbstractItemModel::dataChanged signal is emitted.
Specifically, you assume that the view will only invoke QAbstractItemModel::data on those indexes that are specified in the signal. That's not necessarily the case.
Looking at the source for QAbstractItemView::dataChanged (Qt 5.11.2) you'll see...
void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
Q_UNUSED(roles);
// Single item changed
Q_D(QAbstractItemView);
if (topLeft == bottomRight && topLeft.isValid()) {
const QEditorInfo &editorInfo = d->editorForIndex(topLeft);
//we don't update the edit data if it is static
if (!editorInfo.isStatic && editorInfo.widget) {
QAbstractItemDelegate *delegate = d->delegateForIndex(topLeft);
if (delegate) {
delegate->setEditorData(editorInfo.widget.data(), topLeft);
}
}
if (isVisible() && !d->delayedPendingLayout) {
// otherwise the items will be update later anyway
update(topLeft);
}
} else {
d->updateEditorData(topLeft, bottomRight);
if (isVisible() && !d->delayedPendingLayout)
d->viewport->update();
}
#ifndef QT_NO_ACCESSIBILITY
if (QAccessible::isActive()) {
QAccessibleTableModelChangeEvent accessibleEvent(this, QAccessibleTableModelChangeEvent::DataChanged);
accessibleEvent.setFirstRow(topLeft.row());
accessibleEvent.setFirstColumn(topLeft.column());
accessibleEvent.setLastRow(bottomRight.row());
accessibleEvent.setLastColumn(bottomRight.column());
QAccessible::updateAccessibility(&accessibleEvent);
}
#endif
d->updateGeometry();
}
The important point is that this code behaves differently depending on whether or not the signal specifies a single QModelIndex -- e.g. topLeft is the same as bottomRight. If they are the same then the view tries to ensure that only that model index is updated. However, if multiple model indexes are specified then it will invoke...
d->viewport->update();
which will, presumably, result in the data for all visible model indexes being queried.
Since your implementation of Demo::data always returns new data based on the current time you will see the entire visible part of the view update giving the impression that the dataChanged signal was emitted for all rows and columns.
So the fix is really to make your data model more ``stateful'' -- it needs to keep track of values rather than simply generating them on demand.
Not sure whether this is what you're looking for but I'll put it up anyways.
Even using emit dataChanged(...), you would still see that clicks/selection on rows will cause them to self-update (doing this from a Mac, so might be different).
Instead of using the QAbstractItemModel::dataChanged signal, I will be using the QAbstractItemModel::setData() function.
This is my implementation of demo.h
#pragma once
#include <QAbstractTableModel>
#include <QTime>
#include <QTimer>
class Demo : public QAbstractTableModel
{
Q_OBJECT
public:
Demo()
{
int cCount = columnCount(index(0, 0));
int rCount = rowCount(index(0, 0));
// populate model data with *static* values
QString strTime = QTime::currentTime().toString();
QStringList temp;
for (int j = 0; j < cCount; j++)
temp.append(strTime);
for (int i = 0; i < rCount; i++)
demoModelData.append(temp);
// nothing new here
t = new QTimer(this);
t->setInterval(1000);
connect(t, SIGNAL(timeout()) , this, SLOT(timerHit()));
t->start();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
// tells the *view* what to display
// if this was dynamic (e.g. like your original strTime implementation)
// then the view (QTreeView in main.cpp) will constantly update
if (role == Qt::DisplayRole)
return demoModelData.at(index.row()).at(index.column()); // retrieve data from model
return QVariant();
}
// reimplemented from QAbstractTableModel
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override
{
if (role == Qt::DisplayRole)
{
demoModelData[index.row()][index.column()] = value.toString(); // set the new data
emit dataChanged(index, index); // explicitly emit dataChanged signal, notifies TreeView to update by
// calling this->data(index, Qt::DisplayRole)
}
return true;
}
int rowCount(const QModelIndex &) const override { return 10; }
int columnCount(const QModelIndex &) const override { return 4; }
private slots:
void timerHit()
{
QString strTime = QTime::currentTime().toString();
setData(index(5, 0), QVariant(strTime), Qt::DisplayRole); // only changes index at (row = 5, col = 0)
}
private:
QTimer *t;
QList<QStringList> demoModelData; // stores the table model's data
};
Since the class is a "model", there should be some way of storing/retrieving data for display. Here, I've used a QList<QStringList>, but you can store data in other ways that suit you as well (e.g. a tree, QVector, QMap).
This is a genuine efficiency bug in Qt.
The Qt project is no longer accepting changes to Qt 5, so I made the change and pushed it to my fork on GitHub. You can see a fix for the issue you've encountered here.
If you want to build your own copy of 5.15.2, you may be interested in my other fixes.
A project uses custom frame widget that implements a kind of "cover" that hides widgets (think of it as of security cover that prevents pressing buttons).This is a required visual design. Qt version is 4.6
#ifndef CQFRAME_H
#define CQFRAME_H
#include <QFrame>
#include <QtDesigner/QDesignerExportWidget>
//! [0] //! [1]
class CQFrame : public QFrame
// our agreement about "custom" widgets is to start them with CQ
{
Q_OBJECT
Q_ENUMS(FrameColorStyle)
Q_PROPERTY(FrameColorStyle colorStyle READ getColorStyle WRITE setColorStyle)
Q_PROPERTY(bool border READ border WRITE setBorder)
//! [0]
private:
bool curCover;
QFrame *frameCover;
public:
enum FrameColorStyle {fcDark, fcLight, fcTransparent, fcSystemDefault, fcRed, fcGreen, fcBlue};
CQFrame(QWidget *parent = 0);
void setCoverPropertie();
void setCover(bool state);
protected:
void resizeEvent(QResizeEvent *event);
FrameColorStyle m_colorStyle;
QString pstylebord;
bool m_border;
//! [2]
};
//! [1] //! [2]
#endif
I omitted getters and setters that are irrelevant to the problem. Here is implementation:
void CQFrame::setCoverPropertie()
{
QString str, strAlpha, gradient1, gradient2;
strAlpha.setNum(200);
gradient1 = "rgba("+str.setNum(cwDisableColor.red())+", "
+str.setNum(cwDisableColor.green())
+", "+str.setNum(cwDisableColor.blue())
+" ," +strAlpha+ " )";
gradient2 = "rgba("+str.setNum(cwLbColor.red())+", "
+str.setNum(cwLbColor.green())+", "
+str.setNum(cwLbColor.blue())+" ," +strAlpha+ " )";
QStackedLayout *stackedLayout = new QStackedLayout(this);
frameCover = new QFrame(this);
frameCover->setGeometry(rect());
frameCover->setStyleSheet("QFrame{border:5px solid "+strLbColor+"; "
"border-radius: 10px; background-color: "
"qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.5, stop: 0 "
+gradient1+" , stop: 1 "+gradient2+"); }");
stackedLayout->addWidget(frameCover);
stackedLayout->setStackingMode(QStackedLayout::StackAll);
}
void CQFrame::setCover(bool state)
{
frameCover->setVisible(curCover = state);
}
void CQFrame::resizeEvent(QResizeEvent *event)
{
if (curCover)
frameCover->setGeometry(rect());
}
The design isn't mine, I was asked to fix strange visual glitches it experiences. This "frame" is used in Qt designer as one of widgets. After a while suddenly everything resizes, which prompted question "what is wrong with this code". Qt fires warning about attempt to add layout while one already exist: I suppose that may cause a problem, because a frame must have only one layout at time? Code generated by Qt Creator looks something like
void setupUi(CQFrame *CQWnd1T2SeparateONForm)
{
if (CQWnd1T2SeparateONForm->objectName().isEmpty())
CQWnd1T2SeparateONForm->setObjectName(QString::fromUtf8("CQWnd1T2SeparateONForm"));
CQWnd1T2SeparateONForm->resize(735, 241);
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(CQWnd1T2SeparateONForm->sizePolicy().hasHeightForWidth());
CQWnd1T2SeparateONForm->setSizePolicy(sizePolicy);
gridLayout = new QGridLayout(CQWnd1T2SeparateONForm); // warning here
}
There is similar problem with standard QMainWindow which always got own "special" layout, which Qt Creator solves automatically, by adding a central widget to the layout and everything else is added to that widget. What I don't know that is how to simulate same behavior with a custom widget with Qt Creator plugin.
Or what alternative design for CQFrame can be used. CQFrame reused in dozen project, in about 30+ panels, so reuse of code for them all is a strict requirement.
Current plugin is very basic:
class QDESIGNER_WIDGET_EXPORT CQFramePlugin : public QObject,
public QDesignerCustomWidgetInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInterface)
public:
CQFramePlugin(QObject *parent = 0);
bool isContainer() const;
bool isInitialized() const;
QIcon icon() const;
QString domXml() const;
QString group() const;
QString includeFile() const;
QString name() const;
QString toolTip() const;
QString whatsThis() const;
QWidget *createWidget(QWidget *parent);
void initialize(QDesignerFormEditorInterface *core);
private:
bool initialized;
};
.cpp for it:
#include "cqframe.h"
#include "cqframeplugin.h"
#include <QtPlugin>
CQFramePlugin::CQFramePlugin(QObject *parent)
: QObject(parent)
{
initialized = false;
}
void CQFramePlugin::initialize(QDesignerFormEditorInterface * /* core */)
{
if (initialized)
return;
initialized = true;
}
bool CQFramePlugin::isInitialized() const
{
return initialized;
}
QWidget *CQFramePlugin::createWidget(QWidget *parent)
{
return new CQFrame(parent);
}
QString CQFramePlugin::name() const
{
return "CQFrame";
}
QString CQFramePlugin::group() const
{
return "CustomWidgets";
}
QIcon CQFramePlugin::icon() const
{
return QIcon(":/Resources/frame_icon.png");
}
QString CQFramePlugin::toolTip() const
{
return "";
}
QString CQFramePlugin::whatsThis() const
{
return "";
}
bool CQFramePlugin::isContainer() const
{
return true;
}
QString CQFramePlugin::domXml() const
{
return "<ui language=\"c++\">\n"
" <widget class=\"CQFrame\" name=\"Frame\">\n"
" <property name=\"geometry\">\n"
" <rect>\n"
" <x>0</x>\n"
" <y>0</y>\n"
" <width>120</width>\n"
" <height>80</height>\n"
" </rect>\n"
" </property>\n"
" </widget>\n"
"</ui>";
}
QString CQFramePlugin::includeFile() const
{
return "cqframe.h";
}
So I haven't worked with QT yet, but I'm going to gie it a try. First thing, I think, is that you should use the QStackedLayout to cover the widgets (source and QT manual). But you need to have a single stack.
So the stack should be a private member of CQFrame. E.g.
class CQFrame : public QFrame
{
[...]
private:
QWidget* _frame; // Frame to cover.
QFrame* _frameCover;
QStackedLayout* _stackedLayout;
[...]
And probably:
CQFrame::~CQFrame()
{
delete _stackedLayout;
delete _frameCover;
}
Then you could already initialize everything in the constructor
CQFrame::CQFrame(QWidget* parent = 0, QWidget* frame)
: QFrame(parent)
, _frame(frame)
, _frameCover(new QFrame(this))
, _stackedLayout(new QStackedLayout(this))
{
_frameCover->setGeometry(rect());
[...]
_stackedLayout->addWidget(frame);
_stackedLayout->addWidget(frameCover);
//default:_stackedLayout->setStackingMode(QStackedLayout::StackOne);
}
You could then switch between widgets in the stack using
void CQFrame::SetCover(bool state)
{
if (state) _stackedLayout->setCurrentWidget(_frameCover);
else _stackedLayout->setCurrentWidget(_frame);
}
Maybe this helps you.
edit2:
I removed this code, as it was incorrect, both in coding format, as in idea
So I checked the QT sources QStackedLayout.cpp and QLayout and it seems a QWidget can have only one layout. If you add another layout, you get an error.
Furthermore, a QStackedLayout is a QLayout, is QObject. That could indicate it is automatically removed?
I'm not sure of the cover is implemented in QT as it should. It seems like you have a QWidget you want to cover, on top of which you put a QStackedLayout that is not used as designed i.e. switching stack objects, on top of which you put a new QWidget that is made visible or not.
And maybe that bottom QWidget is already in a (stacked)layout, etc.
I am trying to write a QML Gui for a large dynamic C/Fortran simulation. The data I want to display is stored in Fortran Common blocks and updated on fixed time steps. My problem is that QML ListView does not refresh when the dataChanged signal is emitted after each time step, although the signal is received by the Gui (test is in the code below).
I am probably missing out something really obvious because when I flick my ListView down and up again, the displayed data is updated and correct (I guess because the QML engine re-renders elements when they get "out of sight" and back in again). So the only thing that does not work is that the ListView gets updated every time the dataChanged signal is received and not only when it is re-rendered. Below is a more detailed description of my approach and the relevant code parts.
Each simulation entity has several attributes (alive, position...), so I decided to create a ListModel containing a DataObject for each entity. This is the corresponding header file (the actual simulation data is declared as extern structs in "interface.h", so I can access it via pointer):
"acdata.h"
#include <QtCore>
#include <QObject>
#include <QtGui>
extern "C" {
#include "interface.h"
}
class AcDataObject : public QObject
{
Q_OBJECT
public:
explicit AcDataObject(int id_, int *pac_live, double *pac_pos_x, QObject *parent = 0) :
QObject(parent)
{
entity_id = id_;
ac_live = pac_live;
ac_pos_x = pac_pos_x;
}
int entity_id;
int *ac_live;
double *ac_pos_x;
};
class AcDataModel : public QAbstractListModel
{
Q_OBJECT
public:
enum RoleNames {
IdRole = Qt::UserRole,
LiveRole = Qt::UserRole + 1,
PosXRole = Qt::UserRole + 2
};
explicit AcDataModel(QObject *parent = 0);
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
void do_update();
protected:
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<AcDataObject*> data_list;
QHash<int, QByteArray> m_roleNames;
QModelIndex start_index;
QModelIndex end_index;
signals:
void dataChanged(const QModelIndex &start_index, const QModelIndex &end_index);
};
Like the header, the .cpp file is also adapted from what you can find in the Qt5 Cadaques Book here, except that my constructor iterates over all simulation entities to set the pointers. Additionally, there is the do_update function that emits the dataChanged signal for the whole list.
"acdata.cpp"
#include "acdata.h"
AcDataModel::AcDataModel(QObject *parent) :
QAbstractListModel(parent)
{
m_roleNames[IdRole] = "entity_id";
m_roleNames[LiveRole] = "ac_live";
m_roleNames[PosXRole] = "ac_pos_x";
for (int i = 0; i < MAX_ENTITIES; i++) // MAX_ENTITIES is defined in interface.h
{
AcDataObject *data_object = new AcDataObject( i,
&fdata_ac_.ac_live[i], // fdata_ac_ is the C struct/Fortran common block defined in interface.h
&fdata_ac_.ac_pos_x[i] );
data_list.append(data_object);
}
}
int AcDataModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return data_list.count();
}
QVariant AcDataModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if(row < 0 || row >= data_list.count()) {
return QVariant();
}
const AcDataObject *data_object = data_list.at(row);
switch(role) {
case IdRole: return data_object->entity_id;
case LiveRole: return *(data_object->ac_live);
case PosXRole: return *(data_object->ac_pos_x);
}
return QVariant();
}
QHash<int, QByteArray> AcDataModel::roleNames() const
{
return m_roleNames;
}
void AcDataModel::do_update() {
start_index = createIndex(0, 0);
end_index = createIndex((data_list.count() - 1), 0);
dataChanged(start_index, end_index);
}
Qt::ItemFlags AcDataModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {return 0;}
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
When the simulation is running, do_update() is called every second. I have created a test Gui with a ListView and exposed my model to it with:
Excerpt from "threadcontrol.cpp"
acdata = new AcDataModel();
viewer = new QtQuick2ApplicationViewer();
viewer->rootContext()->setContextProperty("acdata", acdata);
viewer->setMainQmlFile(QStringLiteral("../lib/qml_gui/main.qml"));
viewer->showExpanded();
(This code is part of a larger file that controls the different threads. I am quite sure the rest is not relevant to the actual problem and this question is getting really long...)
So finally there is main.qml. It contains a list with MAX_ENTITIES elements and each elements holds text fields to display my data. I have also added a Connections element to check if the dataChanged signal is received by the Gui.
"main.qml"
ListView {
id: listviewer
model: acdata
delegate: Rectangle {
/* ... some formatting stuff like height etc ... */
Row {
anchors.fill: parent
Text {
/* ... formatting stuff ... */
text: model.entity_id
}
Text {
/* ... formatting stuff ... */
text: model.ac_live
}
Text {
/* ... formatting stuff ... */
text: model.ac_pos_x
}
}
}
Connections {
target: listviewer.model // EDIT: I drew the wrong conclusions here, see text below!
onDataChanged: {
console.log("DataChanged received")
}
}
}
When running the simulation, the "DataChanged received" message is printed every second.
Edit: I was connecting to the ListModel and not to the ListView here, although the ListView has to receive the dataChanged signal. As the console log does not work when connecting to listviewer, I am probably missing the connection between listView and dataChanged signal. However, I think this should work automatically when implementing the dataChanged signal?
Additional information: I have found a similar problem here with Qt Map and it actually seemed to be a bug that was fixed in Qt 5.6. However, running qmake with Qt 5.7 did not fix my problem.
You mustn't declare the dataChanged() signal in your class, because you want to emit the signal AbstractItemModel::dataChanged(). If you re-declare it you add a comleptely new and different Signal that is not connected anywhere. If you remove the declaration in acdata.h everything should work fine.
My application consists of many Lists that I display in QML-ListViews by using QAbstractListModel derived model-classes. It's always the same, with the difference of the Item-Type. That's why I want to know how to build a class-template for this approach.
I figured out, that it is not possible to use the Q_OBJECT-Macro in a class-template. That's why my GenericListModel consists of two part.
1. GenericListModelData
The first part is the Model itself that derives from QAbstractListModel and implements the basic functions data(), rowCount() and roleNames().
2. GenericListModel
The second part is the class-template that is used as a wrapper to provide functions similar to a QListView.
If you have any suggestions or questions please let me know. It would be really nice to improve this solution.
I uploaded the full sourcecode here:
https://github.com/sebabebibobu/QGenericListModel/
1. GenericListModelData
QVariant GenericListModelData::data(const QModelIndex &index, int role) const
{
QObject *item = m_itemList.at(index.row());
return item->property(item->metaObject()->property(role).name());
}
/*
* Returns the number of items attached to the list.
*/
int GenericListModelData::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_itemList.size();
}
/*
* Generates a hash out of QMetaObject property-index and property-name.
*/
QHash<int, QByteArray> GenericListModelData::roleNames() const
{
QHash<int, QByteArray> roles;
if (!m_itemList.isEmpty()) {
for(int i = 0; i < m_itemList.at(0)->metaObject()->propertyCount(); i++) {
roles[i] = m_itemList.at(0)->metaObject()->property(i).name();
}
}
return roles;
}
/*
* Append Item to List.
*/
void GenericListModelData::appendItem(QObject *item)
{
/* map the notify()-signal-index with the property-index when the first item get's inserted */
if (m_itemList.isEmpty()) {
for(int i = 0; i < item->metaObject()->propertyCount(); i++) {
m_propertySignalIndexHash.insert(item->metaObject()->property(i).notifySignalIndex(), i);
}
}
/* connect each notify()-signals to the onDataChanged()-slot which call's the dataChanged()-signal */
for(int i = 0; i < item->metaObject()->propertyCount(); i++) {
connect(item, "2" + item->metaObject()->property(i).notifySignal().methodSignature(), this, SLOT(onDataChanged()));
}
/* finally append the item the list */
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_itemList.append(item);
endInsertRows();
}
/*
* Helper-Slot that emit's the dataChanged()-signal of QAbstractListModel.
*/
void GenericListModelData::onDataChanged()
{
QModelIndex index = createIndex(m_itemList.indexOf(sender()),0);
QVector<int> roles;
roles.append(m_propertySignalIndexHash.value(senderSignalIndex()));
emit dataChanged(index, index, roles);
}
2. GenericListModel
template <typename T>
class GenericListModel : public GenericListModelData
{
public:
explicit GenericListModel(QObject *parent) : GenericListModelData(parent) {
}
void append(T *item) {
appendItem(item);
}
T *at(int i) {
return qobject_cast<T *>(m_itemList.at(i));
}
};
Update 01.05.2016
GrecKo posted in the comments, that a project like mine already exists. That's why I decided to share the link of this project here too:
http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models