Turn a flat table into a tree with QIdentityProxyModel - c++

I want to write a proxy model that groups a flat table into a tree by the first (n) columns, similar to this UI library for React.
I am struggling to get this to work, even in its simplest form:
First question is: do i need to implement all four of the following or only mapToSource and mapFromSource?
https://doc.qt.io/qt-6/qidentityproxymodel.html#parent
https://doc.qt.io/qt-6/qidentityproxymodel.html#index
https://doc.qt.io/qt-6/qidentityproxymodel.html#mapToSource
https://doc.qt.io/qt-6/qidentityproxymodel.html#mapFromSource
If so, how can I achieve the behavior with the following code? I am trying around but I always come up with deadlocks or weird behavior.
#include <QApplication>
#include <QSqlDatabase>
#include <QSqlTableModel>
#include <QSqlError>
#include <QSqlQuery>
#include <QTreeView>
#include <QIdentityProxyModel>
class GroupByFirstColumnProxyModel : public QIdentityProxyModel {
Q_OBJECT
public:
GroupByFirstColumnProxyModel(QObject *parent = nullptr)
: QIdentityProxyModel{parent} {}
~GroupByFirstColumnProxyModel() {}
void setSourceModel(QAbstractItemModel* sourceModel) override {
return QIdentityProxyModel::setSourceModel(sourceModel);
}
int columnCount (const QModelIndex &parent=QModelIndex()) const override {
auto cols = QIdentityProxyModel::columnCount(parent);
qDebug() << "columnCount" << parent << cols << parent.isValid();
return cols;
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override {
qDebug() << "index" << row << column << parent;
return QIdentityProxyModel::index(row, column, parent);
}
QModelIndex parent(const QModelIndex &child) const override {
if (!child.isValid()) {
return QModelIndex();
}
qDebug() << "parent" << child;
return QIdentityProxyModel::parent(child);
}
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override {
qDebug() << "mapFromSource" << sourceIndex;
return QIdentityProxyModel::mapFromSource(sourceIndex);
}
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override {
if (!proxyIndex.isValid()) {
return QModelIndex();
}
qDebug() << "mapToSource" << proxyIndex;
return QIdentityProxyModel::mapToSource(proxyIndex);
}
};
static bool createConnection()
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(":memory:");
if (!db.open()) {
return false;
}
QSqlQuery query;
query.exec("create table simple (A int, B int)");
query.exec("insert into simple values(1, 2)");
return true;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
if (!createConnection())
return EXIT_FAILURE;
QSqlTableModel *model = new QSqlTableModel;
model->setTable("simple");
model->select();
QTreeView *view = new QTreeView;
GroupByFirstColumnProxyModel *proxy = new GroupByFirstColumnProxyModel;
proxy->setSourceModel(model);
view->setModel(proxy);
view->show();
return a.exec();
}
#include "main.moc"

Related

How to keep row selected after adding new one in the begining of table?

My model designed to add row in the begining of table. Problem is that if I select second row (for example) selection still will be on this row (second) after adding new row, but original row moved down.
I figured out that QStandardItemModel provides propely behaviour. How to provide same one in my inherited QAbstractTableModel?
Now there is exapmle code for QStandardItemModel. You can select row and see what's going on.
#include <QApplication>
#include <QMainWindow>
#include <QStandardItemModel>
#include <QTableView>
#include <QTimer>
void timeout(QTimer &timer, QStandardItemModel &tblModel)
{
static int step = 0;
tblModel.insertRow(0);
QStandardItem *pItem = new QStandardItem(QString("row %0").arg(step));
tblModel.setItem(0, 1, pItem);
step++;
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QMainWindow win;
QStandardItemModel tblModel(0, 1);
QTableView tblView;
tblView.setModel(&tblModel);
win.setCentralWidget(&tblView);
win.show();
QTimer timer;
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout,
[&timer, &tblModel]() { timeout(timer, tblModel); });
timer.start();
return app.exec();
}
And simplified version of my model. You can see that in my version selected row doesen't move.
#include <QApplication>
#include <QMainWindow>
#include <QStandardItemModel>
#include <QTableView>
#include <QTimer>
#include <QString>
class MyModel : public QAbstractTableModel
{
public:
MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void insertRow(int data);
private:
QVector<int> table;
};
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent) {}
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
return table.count();
}
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return 1;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
return table[table.count() - 1 - index.row()];
}
return QVariant();
}
void MyModel::insertRow(int data) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
table.push_back(data);
endInsertRows();
}
void timeout(QTimer &timer, MyModel * tblModel)
{
static int step = 0;
tblModel->insertRow(step);
step++;
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QMainWindow win;
MyModel * model = new MyModel();
QTableView tblView;
tblView.setModel(model);
win.setCentralWidget(&tblView);
win.show();
QTimer timer;
timer.setInterval(1000);
QObject::connect(&timer, &QTimer::timeout,
[&timer, model]() { timeout(timer, model); });
timer.start();
return app.exec();
}
I didn't find any information in QAbstractTableModel documentation. After that I tried to read QStandardItemModel source code but it's really difficult for understanding.

How to force a model to update QComboBox when data changed?

I created model for QComboBox:
#ifndef QCOMBOBOXMODEL_H
#define QCOMBOBOXMODEL_H
#include <QModelIndex>
class QComboBoxModel : public QAbstractListModel
{
public:
QComboBoxModel(QObject *parent=nullptr);
int rowCount(const QModelIndex &) const;
QVariant data(const QModelIndex &index, int role) const;
void populate(const QList<QPair<int,QString>> &values);
private:
QList<QPair<int,QString>> values;
};
#endif // QCOMBOBOXMODEL_H
code
#include "qcomboboxmodel.h"
#include <QModelIndex>
QComboBoxModel::QComboBoxModel(QObject *parent)
:QAbstractListModel(parent)
{
}
int QComboBoxModel::rowCount(const QModelIndex &) const
{
return values.count();
}
QVariant QComboBoxModel::data( const QModelIndex &index, int role ) const
{
QVariant value;
switch ( role )
{
case Qt::DisplayRole: //string
{
value = this->values.value(index.row()).second;
}
break;
case Qt::UserRole: //data
{
value = this->values.value(index.row()).first;
}
break;
default:
break;
}
return value;
}
void QComboBoxModel::populate(const QList<QPair<int,QString>> &values)
{
this->values = values;
}
Now i use it
values.append(QPair<int,QString>(-1,"Select item"));
values.append(QPair<int,QString>(10,"item1(0)"));
values.append(QPair<int,QString>(11,"item1(1)"));
values.append(QPair<int,QString>(21,"item1(2)"));
values.append(QPair<int,QString>(32,"item1(3)"));
values.append(QPair<int,QString>(44,"item1(4)"));
newidx = 50;
model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);
and on button click i add new item to combobox
newidx++;
QString strIdx = QString().number(newidx);
values.append(QPair<int,QString>(newidx,"New item("+strIdx+")"));
model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);
Its all seems works just fine, but problem here that i need to recreate model every time i add new item to combobox data
model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);
Is that a proper way to do so? Or there are another way to force model update combobox when data updated?
According to "Model Subclassing Reference" in the documentation you have to do many more things to make an editable model. Is there a reason why you don't use a ready made model like QStandardItemModel?
comboModel = new QStandardItemModel(0, 2, this);
ui->comboBox1->setModel(comboModel);
comboModel->insertRow(0);
comboModel->setData(comboModel->index(0, 0), -1);
comboModel->setData(comboModel->index(0, 1), "Select item");
//and so on
//and the data is available as
int number = comboModel->data(comboModel->index(0, 0)).toInt();
QString itemtext = comboModel->data(comboModel->index(0, 1)).toString();
I find solution!
First i add new method to model
void QComboBoxModel::append(int index, QString value)
{
int newRow = this->values.count();
this->beginInsertRows(QModelIndex(), newRow, newRow);
values.append(QPair<int,QString>(index,value));
endInsertRows();
}
Now on button click method changed to this
void MainWindow::on_pushButton_clicked()
{
qDebug() << "Clicked!";
newidx++;
QString strIdx = QString().number(newidx);
model->append(newidx,"new item " + strIdx );
}
Point is to use beginInsertRows and endInsertRows to notify model that data actually changed!
Now all worked as expected!
Now you also can modify append method to batch add rows to it, but i think if you add many rows it better just recreate model and reassign it to combobox.
Update 1:
Also keep in mind, that you update values QList inside model, so if you add
qDebug() << values;
in on_pushButton_clicked() method, you always see
(QPair(-1,"Select item"), QPair(10,"item1(0)"), QPair(11,"item1(1)"), QPair(21,"item1(2)"), QPair(32,"item1(3)"), QPair(44,"item1(4)"))
Update 2:
Also i update populate method
void QComboBoxModel::populate(const QList<QPair<int,QString>> &newValues)
{
int oldIdx = this->values.count();
int newIdx = newValues.count();
this->beginInsertRows(QModelIndex(), oldIdx, newIdx);
this->values = newValues;
endInsertRows();
}
Now you can just work with values list
void MainWindow::on_pushButton_clicked()
{
qDebug() << "Clicked!";
newidx++;
QString strIdx = QString().number(newidx);
values.append(QPair<int,QString>(newidx,"new item " + strIdx));
model->populate(values);
qDebug() << values;
}
Update 3:
Now, i find one big problem - i did not use pointer inside model, so when i pass QList to model it just create copy instead use already created, so i rewrite model and other code:
Model
#ifndef QCOMBOBOXMODEL_H
#define QCOMBOBOXMODEL_H
#include <QModelIndex>
class QComboBoxModel : public QAbstractListModel
{
public:
QComboBoxModel(QObject *parent=nullptr);
int rowCount(const QModelIndex &) const;
QVariant data(const QModelIndex &index, int role) const;
void populate(QList<QPair<int,QString>> *newValues);
void append(int index, QString value);
private:
QList<QPair<int,QString>> *values;
};
#endif // QCOMBOBOXMODEL_H
#include "qcomboboxmodel.h"
#include <QModelIndex>
#include <QDebug>
QComboBoxModel::QComboBoxModel(QObject *parent)
:QAbstractListModel(parent)
{
values = new QList<QPair<int,QString>>();
}
int QComboBoxModel::rowCount(const QModelIndex &) const
{
return values->count();
}
QVariant QComboBoxModel::data( const QModelIndex &index, int role ) const
{
QVariant value;
switch ( role )
{
case Qt::DisplayRole: //string
{
value = this->values->value(index.row()).second;
}
break;
case Qt::UserRole: //data
{
value = this->values->value(index.row()).first;
}
break;
default:
break;
}
return value;
}
void QComboBoxModel::populate(QList<QPair<int,QString>> *newValues)
{
int oldIdx = this->values->count();
int newIdx = newValues->count();
this->beginInsertRows(QModelIndex(), oldIdx, newIdx);
this->values = newValues;
endInsertRows();
}
void QComboBoxModel::append(int index, QString value)
{
int newRow = this->values->count();
this->beginInsertRows(QModelIndex(), newRow, newRow);
values->append(QPair<int,QString>(index,value));
endInsertRows();
}
Main form
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "qcomboboxmodel.h"
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_comboBox_currentIndexChanged(int index);
void on_comboBox_currentIndexChanged(const QString &arg1);
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
int newidx;
QList<QPair<int,QString>> *values;
QComboBoxModel *model;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "qcomboboxmodel.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
values = new QList<QPair<int,QString>>();
values->append(QPair<int,QString>(-1,"Select item"));
values->append(QPair<int,QString>(10,"item1(0)"));
values->append(QPair<int,QString>(11,"item1(1)"));
values->append(QPair<int,QString>(21,"item1(2)"));
values->append(QPair<int,QString>(32,"item1(3)"));
values->append(QPair<int,QString>(44,"item1(4)"));
newidx = 50;
model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_comboBox_currentIndexChanged(int index)
{
qDebug() << ui->comboBox->itemData(index).value<int>();
}
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
qDebug() << arg1;
}
void MainWindow::on_pushButton_clicked()
{
qDebug() << "Clicked!";
newidx++;
QString strIdx = QString().number(newidx);
values->append(QPair<int,QString>(newidx,"new item " + strIdx));
model->populate(values);
qDebug() << values->toStdList();
}
Now all looks just fine and works as intended!
Why don't you request the current model via "QAbstractItemModel * model() const" from the QComboBox; alter it and assign it again (via "void QComboBox::setModel(QAbstractItemModel *model)")?

QSqlRelationalTable How to display value from other table in a column with foreign key?

I have a SQLite database with three tables:
graph(ID int primary key, name varchar(64));
vertex(ID int primary key, graphID int references graph(ID), name varchar(64), x int default 0, y int default 0);
edge(ID int primary key, graphID int references graph(ID), sourceID int references vertex(ID), targetID int references vertex(ID), weight real default 1);
In my desktop app I'm using custom classes for model/view
MyTableView : public QTableView
VertexTableModel : public QSqlTableModel
EdgeTableModel : public QSqlRelationalTableModel
I'm setting them up like this:
GraphyEditor::GraphyEditor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::GraphyEditor),
vertexModel(new VertexTableModel(parent)),
edgeModel(new EdgeTableModel(parent)) {
ui->setupUi(this);
vertexModel->setTable("vertex");
ui->vertices->setModel(vertexModel); // ui->vertices is *MyTableView
edgeModel->setTable("edge");
//TODO find fix to the issue
// edgeModel->setRelation(2, QSqlRelation("vertex", "ID", "name"));
// edgeModel->setRelation(3, QSqlRelation("vertex", "ID", "name"));
ui->edges->setModel(edgeModel); // ui->egdes is *MyTableView
}
This code works and displays the data correctly, but I would like to substitute columns 2 and 3 (sourceID and targetID) in edgeModel from vertex.ID to vertex.name
I did some searching and found the setRelation method (the same I commented out in my code), but when I use it the edgeModel table shows no edges.
Is it because of my tables schemas or is there something wrong in my code?
How do I achieve this?
EDIT:
Here are implementations of classes I'm using:
MyTableModel.h/cpp
#include <QtSql/QSqlTableModel>
class MyTableModel : public QSqlTableModel {
Q_OBJECT
public:
explicit MyTableModel(QObject *parent = nullptr);
void refresh();
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override = 0;
bool setData(const QModelIndex &index, const QVariant &value, int role) override = 0;
signals:
void databaseUpdated();
};
#endif
#include "MyTableModel.h"
#include <QDebug>
#include <utility>
#include <database/DBManager.h>
MyTableModel::MyTableModel(QObject *parent) :
QSqlTableModel(parent) {}
void MyTableModel::refresh() { select(); }
VertexTableModel.h/cpp
#include <model/MyTableModel.h>
class VertexTableModel : public MyTableModel {
Q_OBJECT
public:
explicit VertexTableModel(QObject *parent = nullptr);
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
};
#endif
bool VertexTableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
// checks if value is valid and updates database
}
Qt::ItemFlags VertexTableModel::flags(const QModelIndex &index) const {
auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if (index.column() == 2) flags |= Qt::ItemIsEditable;
return flags;
}
VertexTableModel::VertexTableModel(QObject *parent) : MyTableModel(parent) {}
EdgeTableModel.h/cpp
class EdgeTableModel : public QSqlRelationalTableModel {
Q_OBJECT
public:
explicit EdgeTableModel(QObject *parent = nullptr);
void refresh();
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
signals:
void databaseUpdated();
};
#endif
EdgeTableModel::EdgeTableModel(QObject *parent) : QSqlRelationalTableModel(parent) {
refresh();
}
void EdgeTableModel::refresh() { select(); }
bool EdgeTableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
// checks if value is valid and updates the database
}
Qt::ItemFlags EdgeTableModel::flags(const QModelIndex &index) const {
auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if (index.column() == 4) flags |= Qt::ItemIsEditable;
return flags;
}
GraphyEditor.h/cpp
#ifndef GRAPHY_EDITOR_H
#define GRAPHY_EDITOR_H
#include <QMainWindow>
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>
#include <QtGui/QRegExpValidator>
#include <QtSql/QSqlRelationalDelegate>
QT_BEGIN_NAMESPACE
namespace Ui { class GraphyEditor; }
QT_END_NAMESPACE
class GraphyEditor : public QMainWindow {
Q_OBJECT
Ui::GraphyEditor *ui;
QSqlTableModel *vertexModel;
QSqlRelationalTableModel *edgeModel;
QSqlRelationalDelegate *delegate;
QString graphID = "";
public:
explicit GraphyEditor(QWidget *parent = nullptr);
void setGraphID(const QString &newGraphID);
~GraphyEditor() override;
};
#endif
#include <QtWidgets/QWidget>
#include "GraphyEditor.h"
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>
#include <QtSql/QSqlRelationalDelegate>
GraphyEditor::GraphyEditor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::GraphyEditor),
// vertexModel(new VertexTableModel(parent)),
vertexModel(new QSqlTableModel(parent)),
// edgeModel(new EdgeTableModel(parent)) {
edgeModel(new QSqlRelationalTableModel(parent)) {
ui->setupUi(this);
vertexModel->setTable("vertex");
vertexModel->setHeaderData(1, Qt::Horizontal, "Vertex ID");
vertexModel->setHeaderData(2, Qt::Horizontal, "Vertex Name");
ui->vertices->setModel(vertexModel);
// ui->vertices->hideColumn(0);
ui->vertices->hideColumn(1);
ui->vertices->hideColumn(3);
ui->vertices->hideColumn(4);
edgeModel->setTable("edge");
//TODO find fix to the issue
// edgeModel->setRelation(2, QSqlRelation("vertex", "ID", "name"));
// edgeModel->setRelation(3, QSqlRelation("vertex", "ID", "name as targetName"));
edgeModel->setHeaderData(2, Qt::Horizontal, "Source Vertex", Qt::DisplayRole);
edgeModel->setHeaderData(3, Qt::Horizontal, "Target Vertex", Qt::DisplayRole);
edgeModel->setHeaderData(4, Qt::Horizontal, "Weight");
delegate = new QSqlRelationalDelegate(this);
ui->edges->setModel(edgeModel);
ui->edges->setItemDelegate(delegate);
// ui->edges->setItemDelegateForColumn(2, delegate);
// ui->edges->setItemDelegateForColumn(3, delegate);
ui->edges->hideColumn(0);
ui->edges->hideColumn(1);
ui->canvas->setVertices(vertexModel);
ui->canvas->setEdges(edgeModel);
}
GraphyEditor::~GraphyEditor() {
delete ui;
delete vertexModel;
delete edgeModel;
delete delegate;
}
void GraphyEditor::setGraphID(const QString &newGraphID) {
GraphyEditor::graphID = newGraphID;
vertexModel->setFilter("graphID = " + newGraphID);
edgeModel->setFilter("graphID = " + newGraphID);
ui->canvas->setGraphID(newGraphID);
ui->canvas->refresh();
}
GraphyCanvas.h/cpp
#ifndef GRAPHY_CANVAS_H
#define GRAPHY_CANVAS_H
#include <QWidget>
#include <model/vertex/VertexTableModel.h>
#include <model/edge/EdgeTableModel.h>
class GraphyCanvas : public QWidget {
Q_OBJECT
QSqlTableModel *vertexModel = nullptr;
QSqlRelationalTableModel *edgeModel = nullptr;
QString graphID = "";
public:
void setGraphID(const QString &newGraphID);
explicit GraphyCanvas(QWidget *parent = nullptr);
void setVertices(QSqlTableModel *vertexTableModel);
void setEdges(QSqlRelationalTableModel *edgeTableModel);
public slots:
void refresh();
};
#endif
#include <QPainter>
#include <QPen>
#include <QDebug>
#include <QtWidgets/QtWidgets>
#include <cmath>
#include "GraphyCanvas.h"
GraphyCanvas::GraphyCanvas(QWidget *parent) : QWidget(parent) {
QPalette newPalette = palette();
newPalette.setColor(QPalette::Window, Qt::white);
setPalette(newPalette);
}
void GraphyCanvas::refresh() {
vertexModel->select();
edgeModel->select();
for (auto child : children()) child->deleteLater();
int edgesCount = edgeModel->rowCount();
for (int e = 0; e < edgesCount; ++e) {
//paints edge objects
}
int verticesCount = vertexModel->rowCount();
for (int v = 0; v < verticesCount; ++v) {
//paints vertex objects
}
}
void GraphyCanvas::setVertices(QSqlTableModel *vertexTableModel) {
GraphyCanvas::vertexModel = vertexTableModel;
connect(vertexModel, SIGNAL(databaseUpdated()), this, SLOT(refresh()));
}
void GraphyCanvas::setEdges(QSqlRelationalTableModel *edgeTableModel) {
GraphyCanvas::edgeModel = edgeTableModel;
connect(edgeModel, SIGNAL(databaseUpdated()), this, SLOT(refresh()));
}
void GraphyCanvas::setGraphID(const QString &newGraphID) { GraphyCanvas::graphID = newGraphID; }
main.cpp
#include "setup/GraphySetup.h"
#include <QApplication>
#include <database/DBManager.h>
int main(int argc, char *argv[]) {
QApplication application(argc, argv);
DBManager::initialize();
GraphyEditor editor;
editor.setGraphID("1");
editor.showMaximized();
return QApplication::exec();
}
DBManager is a helper class that's responsible for initializing and accessing the database
In GraphyEditor.cpp, function GraphyEditor::setGraphID, this line
edgeModel->setFilter("graphID = " + newGraphID);
should be
edgeModel->setFilter("edge.graphID = " + newGraphID);
The underlying query is a join, where the field name graphID belongs to more than one table, so the table name has to be specified along with the field name.
Your problem is probably hidden in some other place in your project that you don't provide in your question, but not in the code using QSqlRelationalTableModel.
There is only a minimal problem in your (commented) code: the two replaced columns would be having the same name: "name", but you can rename both columns while defining the QSqlRelation.
Here is a m.r.e. to illustrate how to deal with your two tables and a QSqlRelationalTableModel, just in case someone else comes to stackoverflow asking a similar question.
SQLite database dump:
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE `vertex` (
`ID` INTEGER,
`name` TEXT,
PRIMARY KEY(`ID`)
);
INSERT INTO vertex VALUES(1,'one');
INSERT INTO vertex VALUES(2,'two');
INSERT INTO vertex VALUES(3,'three');
INSERT INTO vertex VALUES(4,'four');
INSERT INTO vertex VALUES(5,'five');
INSERT INTO vertex VALUES(6,'six');
INSERT INTO vertex VALUES(7,'seven');
INSERT INTO vertex VALUES(8,'eight');
INSERT INTO vertex VALUES(9,'nine');
CREATE TABLE IF NOT EXISTS "edge" (
"ID" INTEGER,
"sourceID" INTEGER,
"targetID" INTEGER,
FOREIGN KEY("targetID") REFERENCES "vertex"("ID"),
PRIMARY KEY("ID"),
FOREIGN KEY("sourceID") REFERENCES "vertex"("ID")
);
INSERT INTO edge VALUES(1,1,4);
INSERT INTO edge VALUES(2,2,5);
INSERT INTO edge VALUES(3,3,6);
INSERT INTO edge VALUES(4,4,7);
INSERT INTO edge VALUES(5,5,8);
INSERT INTO edge VALUES(6,6,9);
COMMIT;
test.pro
QT = core sql
CONFIG += c++11 console
SOURCES += main.cpp
main.cpp
#include <QCoreApplication>
#include <QTextStream>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlRelationalTableModel>
#include <QSqlRecord>
#include <QSqlField>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTextStream cout(stdout, QIODevice::WriteOnly);
QTextStream cerr(stderr, QIODevice::WriteOnly);
auto db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("test.db");
if (!db.open()) {
cerr << db.lastError().text() << endl;
return 1;
}
QSqlRelationalTableModel model;
model.setTable("edge");
model.setRelation(1, QSqlRelation("vertex", "ID", "name as sourceName"));
model.setRelation(2, QSqlRelation("vertex", "ID", "name as targetName"));
model.select();
auto rec = model.record();
// headers output
cout << qSetFieldWidth(15);
for(int i=0; i<rec.count(); ++i) {
cout << rec.field(i).name();
}
cout << endl;
// rows output
for(int i=0; i<model.rowCount(); ++i) {
rec = model.record(i);
cout << rec.value("ID").toInt() << rec.value("sourceName").toString() << rec.value("targetName").toString() << endl;
}
}
And this is the output of the program:
ID sourceName targetName
1 one four
2 two five
3 three six
4 four seven
5 five eight
6 six nine
Maybe modify selectStatement:
QString QSqlRelationalTableModel::selectStatement() const
......
//!!! my
fList.append(QLatin1String(", "));
fList.append(relTableAlias);
fList.append(QLatin1String("."));
fList.append(relation.indexColumn());
fList.append(QLatin1String(" as "));
fList.append(relation.tableName());
fList.append(QLatin1String("_"));
fList.append(relation.indexColumn());
Maybe you can look our open Qt project:
[github][1]
This project contains a wrapper above QSqlTableModel + QTableView and realize PblTableDlg class with basic table functionaliy.
We use a new variant of QSqlRelationalTableModel = PblSqkRelationalTableModel.
There is PblTableView (inherited by QTableView) and PblTableDlg that contains a db table view with all controls.
[1]:https://github.com/PavelDorofeev/Fork-Sql-Qt-4.8.1--SQLite-3--relations--calc-fields

Qt background color of row in ListStringModel

i try set color of row with this code and it doesnt work.
QColor dataColor =Qt::red;
row = seznamChyb->rowCount();
seznamChyb->insertRows(row,1);
QModelIndex index = seznamChyb->index(row);
ui->listView->setCurrentIndex(index);
seznamChyb->setData(index,dataColor, Qt::BackgroundRole);
seznamChyb->setData(index,data);
I'm not sure what really type of seznamChyb and ListStringModel. But Changing the background color of row required the implementation of method QAbstractItemModel::data().
I write a example for this question, and it does work well:
#include <QApplication>
#include <QTableView>
#include <QAbstractListModel>
class StringListModel : public QAbstractListModel
{
public:
StringListModel(const QStringList &strings, QObject *parent = 0)
: QAbstractListModel(parent), stringList(strings) {}
int rowCount(const QModelIndex &) const
{
return stringList.count();
}
QVariant data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole)
return stringList.at(index.row());
if (role == Qt::BackgroundColorRole && index.row() == 0) // Look this.
return QBrush(Qt::red);
else
return QVariant();
}
private:
QStringList stringList;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableView tableView;
QStringList list;
list << "a" << "b" << "c";
StringListModel model(list);
tableView.setModel(&model);
tableView.show();
return a.exec();
}
Please check your program again.

QObject Model is empty in QT

I was working on the code which in simple word displays a list of items.
i am able to populate the QlistView but its a empty model.The model i have created is empty.
I mean i have added some four Items.but i cannot see it in the output.Please help.
Main.cpp
#include <QtGui/QApplication>
#include <QDeclarativeContext>
#include <QKeyEvent>
#include <QAbstractItemModel>
#include <QListView>
#include <QDebug>
#include "qmlapplicationviewer.h"
#include "listmodel.h"
#include "songitem.h"
#include "mainwindow.h"
#include "song.h"
#include "songs.h"
#include "songitem.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
ListModel *model = new ListModel(new SongItem, qApp);
model->appendRow(new SongItem( "Michel Telo","Ai Se Eu Te Pego","Ai Se Eu Te Pego", model));
SongItem test = new SongItem( "Michel Telo","Ai Se Eu Te Pego","Ai Se Eu Te Pego", model);
qDebug() << test.data(NameRole);
QModelIndex index = model->index(0, 2, QModelIndex());
qDebug() << model->data(index);
Songs songz;
Song s;
vector<Song> songCollection;
songCollection = songz.all(3);
int ii;
for(ii=0; ii < songCollection.size(); ii++)
{
QString qstr = QString::fromStdString(songCollection[ii].album);
model->appendRow(new SongItem( qstr , qstr, qstr, model));
}
QListView *view = new QListView;
view->setWindowTitle("Model Data");
view->setModel(model);
view->setStyleSheet("color: black");
view->show();
return app.exec();
}
ListModel.cpp
#include <QDebug>
#include "listmodel.h"
int i=0;
ListModel::ListModel(ListItem* prototype, QObject *parent) :
QAbstractListModel(parent), m_prototype(prototype)
{
setRoleNames(m_prototype->roleNames());
}
int ListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_list.size();
}
QVariant ListModel::data(const QModelIndex &index, int role) const
{
if(index.row() < 0 || index.row() >= m_list.size())
return QVariant();
return m_list.at(index.row())->data(role);
}
ListModel::~ListModel() {
delete m_prototype;
clear();
}
void ListModel::appendRow(ListItem *item)
{
appendRows(QList<ListItem*>() << item);
//qDebug() << "Test";
//qDebug() << item;
}
void ListModel::appendRows(const QList<ListItem *> &items)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount()+items.size()-1);
foreach(ListItem *item, items) {
connect(item, SIGNAL(dataChanged()), SLOT(handleItemChange()));
m_list.append(item);
}
endInsertRows();
}
void ListModel::insertRow(int row, ListItem *item)
{
beginInsertRows(QModelIndex(), row, row);
connect(item, SIGNAL(dataChanged()), SLOT(handleItemChange()));
m_list.insert(row, item);
endInsertRows();
}
void ListModel::handleItemChange()
{
ListItem* item = static_cast<ListItem*>(sender());
QModelIndex index = indexFromItem(item);
if(index.isValid())
emit dataChanged(index, index);
}
ListItem * ListModel::find(const QString &id) const
{
foreach(ListItem* item, m_list) {
if(item->id() == id) return item;
}
return 0;
}
QModelIndex ListModel::indexFromItem(const ListItem *item) const
{
Q_ASSERT(item);
for(int row=0; row<m_list.size(); ++row) {
if(m_list.at(row) == item) return index(row);
}
return QModelIndex();
}
void ListModel::clear()
{
qDeleteAll(m_list);
m_list.clear();
}
bool ListModel::removeRow(int row, const QModelIndex &parent)
{
Q_UNUSED(parent);
if(row < 0 || row >= m_list.size()) return false;
beginRemoveRows(QModelIndex(), row, row);
delete m_list.takeAt(row);
endRemoveRows();
return true;
}
bool ListModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(parent);
if(row < 0 || (row+count) >= m_list.size()) return false;
beginRemoveRows(QModelIndex(), row, row+count-1);
for(int i=0; i<count; ++i)
{
delete m_list.takeAt(row);
}
endRemoveRows();
return true;
}
ListItem * ListModel::takeRow(int row)
{
beginRemoveRows(QModelIndex(), row, row);
ListItem* item = m_list.takeAt(row);
endRemoveRows();
return item;
}
SongItem.h
#ifndef SONGITEM_H
#define SONGITEM_H
#include "listmodel.h"
class SongItem : public ListItem
{
Q_OBJECT
public:
enum Roles {
NameRole = Qt::UserRole+1,
ArtistRole,
TrackRole
};
public:
SongItem(QObject *parent = 0): ListItem(parent){}
explicit SongItem(const QString &name, const QString &artist, const QString &track, QObject *parent = 0);
QVariant data(int role) const;
QHash<int, QByteArray> roleNames() const;
inline QString id() const { return m_name; }
inline QString name() const { return m_name; }
inline QString artist() const { return m_artist; }
inline QString track() const { return m_track; }
private:
QString m_name;
QString m_artist;
QString m_track;
};
#endif // SONGITEM_H
SongItem.cpp
#include "songitem.h"
#include <QDebug>
SongItem::SongItem(const QString &name, const QString &artist, const QString &track, QObject *parent) :
ListItem(parent), m_name(name), m_artist(artist), m_track(track)
{
}
QHash<int, QByteArray> SongItem::roleNames() const
{
QHash<int, QByteArray> names;
names[NameRole] = "name";
names[ArtistRole] = "artist";
names[TrackRole] = "track";
return names;
}
QVariant SongItem::data(int role) const
{
switch(role) {
case NameRole:
return name();
case ArtistRole:
return artist();
case TrackRole:
return track();
default:
return QVariant();
}
}
A QListView will fetch data with the Qt::DisplayRole role for display, so you have to adapt SongItem::data to return something for this role, e.g.
QVariant SongItem::data(int role) const
{
switch(role) {
case Qt::DisplayRole:
return name();
case NameRole:
return name();
case ArtistRole:
return artist();
case TrackRole:
return track();
default:
return QVariant();
}
}
It might be that you took some examples related to QML, where it works quite differently.