Unsolved How to properly subclass QSqlRelationalTableModel to set background color of cells in QTableView
-
I took "Books" example from Qt examples with minimal changes. Need to be able to set background color, text color and border width for arbitrary cells. Wrote on the Internet that, probably, the simplest way is to subclass the table model class. So, I tried to subclass QSqlRelationalTableModel class in tablemodel.h and tablemodel.cpp and redefine
data(const QModelIndex &index, int role = Qt::DisplayRole) const override
class method.
I wrotemodel = new TableModel(ui.bookTable);
instead of
model = new QSqlRelationalTableModel(ui.bookTable);
in bookwindow.cpp, where TableModel is the subclass of QSqlRelationalTableModel.
But, as soon as I did it, the code began failing with a segmentation fault at the call ofQSqlRelationalDelegate::paint(painter, opt, index);
in bookdelegate.cpp on line 17, which is the subclass of QSqlRelationalDelegate.
The reproducing error code is at: https://github.com/AndStorm/QtQuestion.git .
I can not understand what causes this error, could You, please, explain in detail?
How to properly subclass classes like QSqlRelationalTableModel and decorate cells of QTableView?
If there are any examples on the Internet, couldn't You give a link? I didn't find any helpful myself. -
@And92 said in How to properly subclass QSqlRelationalTableModel to set background color of cells in QTableView:
But, as soon as I did it, the code began failing with a segmentation fault at the call of
Two big problems in your
QVariant TableModel::data()
override intablemodel.cpp
, basic C++:case Qt::DisplayRole: { //return actual content for row here, i. e. text, numbers } case Qt::TextAlignmentRole:
You can't leave the
case Qt::DisplayRole:
empty of any code and expect it to work/anything but disaster. Your code forDisplayRole
will drop into your code forTextAlignmentRole
and return the result from there, which is quite unsuitable for any displaying.Your whole
data()
method only returns any result in a couple of cases, in most cases it doesn't even hit anyreturn
statement. Does this really compile, and without warnings? I am surprised if it does.... You mustreturn QSqlRelationalTableModel::data(index, role)
where you don't have your own thing to return. -
Thanks. I corrected the code:
QVariant TableModel::data(const QModelIndex &index, int role) const { if(!index.isValid()) return QVariant(); QVariant value = QSqlQueryModel::data(index, role); switch(role) { case Qt::BackgroundRole: { if(index.column() == 2) { if(index.row()%3 == 0) return QVariant::fromValue(QColor(Qt::red)); else if(index.row()%3 == 1) return QVariant::fromValue(QColor(Qt::green)); else return QVariant::fromValue(QColor(Qt::blue)); } break; } case Qt::ForegroundRole: { if(index.column() == 1) return QVariant::fromValue(QColor(Qt::blue)); break; } case Qt::DisplayRole: { if(value.isValid()) { if(index.column() == 0) return value.toString().prepend('#'); else if(index.column() == 2) return value.toString(); } break; } case Qt::TextAlignmentRole: { if(1==index.column()) return QVariant (Qt::AlignVCenter | Qt::AlignLeft); if(2==index.column()) return QVariant (Qt::AlignVCenter | Qt::AlignTrailing); return QVariant ( Qt::AlignVCenter | Qt::AlignHCenter ); break; } default: { return value; break; } } return value; }
Now the QTableWidget is displayed properly, but i cannot modify anything. After a double click on any modifiable field, the program crashes with a seg fault. The debugger says the crash is in the function QSqlRelationalDelegate::setModelData of qsqlrelationaldelegate.h. I guess that it is because setData method is not implemented.
Tried to implement setData in tablemodel.cpp to be able to modify the column with the number 1 ("Title"):bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (index.column() < 1 || index.column() > 4) return false; QModelIndex primaryKeyIndex = QSqlQueryModel::index(index.row(), 0); int id = data(primaryKeyIndex).toInt(); clear(); bool ok = true; if(index.column() == 1) ok = setTitle(id, value.toString()); refresh(); return ok; }
where
bool TableModel::setTitle(int id, const QString &title) { QSqlQuery query; query.prepare("update books set title = ? where id = ?"); query.addBindValue(title); query.addBindValue(id); return query.exec(); }
and
void TableModel::refresh() { QSqlQuery q; q.exec("select * from books"); setHeaderData(fieldIndex("author"), Qt::Horizontal, tr("Author Name")); setHeaderData(fieldIndex("genre"), Qt::Horizontal, tr("Genre")); setHeaderData(fieldIndex("title"), Qt::Horizontal, tr("Title")); setHeaderData(fieldIndex("year"), Qt::Horizontal, tr("Year")); setHeaderData(fieldIndex("rating"), Qt::Horizontal, tr("Rating")); }
But this does not work. Unfortunately, I have no idea how to implement it yet. If You know, please, be so kind to give me any hints how to implement it.
-
Hi,
You can also use a QIdentityProxyModel and do your changes there.
-
Thank You for Your answer. But i would like to know how to get to work this actual code. I'm a beginner in Qt and would like to understand and learn some rules and examples I can use in everyday work.
Thanks to @JonB, I understood how to make a custom visualization of the model on a QtableWidget. But the elements of the widget do not react on being edited. My question was how to properly perform the ability of editing items, so how to subclass QSqlRelationalTableModel and implement setData and flags methods properly. If there are any examples on the Internet, could You, please, give me a link. -
What exact minimal changes did you do with regard to the original example ?
-
@And92
First, always please answer @SGaist questions first, as he always knows what he is talking about!I have a feeling your code may be a bit over the place now, as you've tried to introduce
QSqlRealtionalTableModel
, and we're not sure what you've changed/added etc.But your
TableModel::setData()
does not look good to me, and that has to work for any editing to work.Just like my earlier points about your
data()
implementation. It must handle all cases, correctly:- You need to be looking at
role
, which you are not - You need to call base
QSqlQueryModel::setData()
in cases you don't handle, which you are not. - You call
clear()
in the middle of setting data, that doesn't look right, it will throw everything away. - You need to respect the
column()
being changed, which you do not.
- You need to be looking at
-
Thank You, @SGaist and @JonB, for answering my questions.
Excuse me for not answering Your latest posts. I have been away from the town and did not have access to the Internet.
Dear @SGaist, I took the "Books" example from Qt Creator examples and the only change I did with regard to the original example is that I created TableModel class inheriting QSqlRelationalTableModel, because I wanted to paint some rows of the QTableWidget in different colors and wanted to do this by redefining the method data() in TableModel. So, the only change is that i created TableModel class and used it in bookwindow.h/cpp instead of QSqlRelationalTableModel. Now the rows are painted in necessary colors, but now I can not modify the values in the cells (text, spinBoxes, comboBoxes) and in textEdit fields below the QTableWidget. The changes are not applied. the actual code is at https://github.com/AndStorm/QtQuestion.git .@JonB, I removed the call of clear() from setData(). I do not know how to properly handle cases (spinBox/comboBox ValueChanged signals) that I do not handle with QSqlQueryModel::setData(). I try to respect the column==1 in setData(), where the title is, but is does not change.
Lastly, unfortunately, I do not understand what You are speaking about "role". My thought is that with switch, Qt::BackgroundRole, Qt::BackgroundRole, case Qt::ForegroundRole:, case Qt::DisplayRole:, case Qt::TextAlignmentRole: and return QSqlQueryModel::data(index, role); in default: I handle all the cases of all possible values of "role", don't I?I have searched on the Internet, but all I found yet is "Query Model Example" in Qt Creator. But it uses QSqlQueryModel instead of QSqlRelationalTableModel, it is much simpler, and I undestand everything there, it does not help in work on this code.
-
@And92 said in How to properly subclass QSqlRelationalTableModel to set background color of cells in QTableView:
Lastly, unfortunately, I do not understand what You are speaking about "role". My thought is that with switch, Qt::BackgroundRole, Qt::BackgroundRole, case Qt::ForegroundRole:, case Qt::DisplayRole:, case Qt::TextAlignmentRole: and return QSqlQueryModel::data(index, role); in default: I handle all the cases of all possible values of "role", don't I?
You do indeed in your
data()
override method. But you also have to deal with these insetData()
!setData()
has arole
parameter just likedata()
does, and you need to act on it. And call the baseQSqlQueryModel::setData()
for those cases you do not handle, just like you callQVariant value = QSqlQueryModel::data(index, role)
duringdata()
. -
I rewrote setData as:
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(index.column() < 1 || index.column() > 4) return false; QModelIndex primaryKeyIndex = QSqlQueryModel::index(index.row(), 0); int id = data(primaryKeyIndex).toInt(); bool ok = true; switch (role) { case Qt::EditRole: switch(index.column()) { case 1: ok = setTitle(index.row()+1, value.toString()); emit dataChanged(index, index); break; case 2: break; case 3: break; case 4: break; default: ok = QSqlRelationalTableModel::setData(index, value, role); break; } default: QSqlRelationalTableModel::setData(index, value, role); break; } refresh(); return ok; }
where
void TableModel::refresh() { QSqlQuery q; q.exec("select * from books"); setHeaderData(fieldIndex("author"), Qt::Horizontal, tr("Author Name")); setHeaderData(fieldIndex("genre"), Qt::Horizontal, tr("Genre")); setHeaderData(fieldIndex("title"), Qt::Horizontal, tr("Title")); setHeaderData(fieldIndex("year"), Qt::Horizontal, tr("Year")); setHeaderData(fieldIndex("rating"), Qt::Horizontal, tr("Rating")); }
and added
TableModel::TableModel(QObject *parent) : QSqlRelationalTableModel(parent) { connect(this, &QSqlRelationalTableModel::dataChanged, this, &QSqlRelationalTableModel::select); }
in the constructor of TableModel class. Now the titles are modified (and they are also modified in the database) and the values in QTableWidget widget 1st column are updated immediately at run-time.
But I don't know how to handle and modify QSpinBox and QComboBox (2-4 columns) fields, that is why I left blank case 2-4: in the switch. -
@And92
For efficiency, why do you callrefresh()
on everysetData()
call?? That executes an actual SQL query (not to mention, which returns the whole of the table), and there could be many calls! I can only say this setting of header data should only need doing once, and not to do withsetData()
anyway.