How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole
-
I wrote this delegate in Qt 6.8.2:
#ifndef DELEGATEEVENTS_H #define DELEGATEEVENTS_H #include <QSqlRelationalDelegate> #include <QComboBox> #include <QSqlDatabase> #include <QSqlTableModel> #include <QSqlQuery> #include <QSqlError> #include <QDebug> #include "mainwindow.h" #include "formprotocols.h" class DelegateEvents : public QSqlRelationalDelegate { Q_OBJECT public: DelegateEvents(QObject *parent = nullptr) : QSqlRelationalDelegate(parent) { } QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override { const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel *>(index.model()); QModelIndex idx = sqlModel->index(index.row(), static_cast<int>(FormProtocols::Columns::MODEL_COL_TYPE)); QString text = sqlModel->data(idx, Qt::EditRole).toString(); if (text.isEmpty()) return nullptr; MainWindow::EventTypes eventType; bool ok; int i = text.toInt(&ok); if (ok) eventType = static_cast<MainWindow::EventTypes>(i); else { if (text == "Alarm") eventType = MainWindow::EventTypes::Alarm; else if (text == "Session") eventType = MainWindow::EventTypes::Session; else return nullptr; } QComboBox *editor = new QComboBox(parent); editor->setFrame(false); QSqlDatabase db = QSqlDatabase::database("mydb"); QSqlQuery query(db); switch (eventType) { case MainWindow::EventTypes::Alarm: query.exec("SELECT id, gb FROM alarms;"); while (query.next()) { int id = query.value("id").toInt(); QString name = query.value("gb").toString(); editor->addItem(name, id); } break; case MainWindow::EventTypes::Session: query.exec("SELECT id, gb FROM sessions;"); while (query.next()) { int id = query.value("id").toInt(); QString name = query.value("gb").toString(); editor->addItem(name, id); } break; } return editor; } void setEditorData(QWidget *editor, const QModelIndex &index) const override { int value = index.model()->data(index, Qt::EditRole).toInt(); QComboBox *w = static_cast<QComboBox*>(editor); w->setCurrentIndex(w->findData(value)); } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { QComboBox *w = static_cast<QComboBox*>(editor); int value = w->currentData().toInt(); model->setData(index, "foo", Qt::DisplayRole); // <-- this is not shown model->setData(index, value, Qt::EditRole); } }; #endif // DELEGATEEVENTS_H
The goal is to fit a specific request: basically the column where the delegate is installed will show the values from another table according to a specific value of the current
FormProtocols::Columns::MODEL_COL_TYPE
text.When I create the editor I add the items in the
QComboBox
using thename
as text andid
as user-data. In this way when I have to set the editor value I can retrieve the correct item using the data property.On the other side, when I need to write back the values to the model I set both the
EditRole
(the actual integer value that is committed to the database) and theDisplayRole
that should printed in the cell of theQTableView
. My current test implementation above should always print "foo". Instead it shows theEditRole
, i.e. theid
value.QSqlRelationalDelegate
does not reimplement thepaint
function, hence I checked the source code for theQStyledItemDelegate
and it seems it should print theDisplayRole
:value = modelRoleDataSpan.dataForRole(Qt::DisplayRole); if (value->isValid() && !value->isNull()) { option->features |= QStyleOptionViewItem::HasDisplay; option->text = displayText(*value, option->locale); }
In order to debug what's happening I added a dummy
paint
function:void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { QSqlRelationalDelegate::paint(painter, option, index); }
so I was able to place a breakpoint and go deeper to reach the
QStyledItemDelegate
level. But the debugger told me the actual value for theDisplayRole
was 3 (theid
in my test case).Hence I tried to understand why and I changed the function like this:
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { QComboBox *w = static_cast<QComboBox*>(editor); int value = w->currentData().toInt(); model->setData(index, "foo", Qt::DisplayRole); model->setData(index, value, Qt::EditRole); qDebug() << model->data(index, Qt::EditRole) << model->data(index, Qt::DisplayRole); }
and the output was:
QVariant(qlonglong, 3) QVariant(qlonglong, 3)
so it does not store the
DisplayRole
value. Why?Unfortunately I cannot debug the
setData
function: placing a breakpoint there will hang the whole screen, nothing reacts anymore. I had to killgdb
from SSH. Tried few times.I checked the return value of the first
setData()
and it wasfalse
, but in the documentation I cannot find how to find out the reasons (like thelastError
for queries). -
@Mark81
I am not sure I follow all that you have done/said. However, the way things work with aQSqlRelation
is that the data table stores the desired value (like an integer id or similar) as itsEditRole
and the delegate looks that up in the foreign table to find the display value from there. Presumably ignoring anyDisplayRole
it would normally use for a data table and doing its own look up instead. -
@JonB ok, I changed my class so now it is a subclass of
QStyledItemDelegate
but nothing has changed. The behavior is the same.By the way, I tried to reimplement
displayText
, but since it does not provide an index (just the text to be printed) I cannot retrieve the associated data from the model. -
@Mark81
I have to confess I don't know what you are trying to "fix". I don't know what you expect to "change". It may (well) be that I don't understand what you are saying, but I don't think you can change the behaviour ofDisplayRole
/display text.Nor do I understand why you are trying to do so. The whole point of
QSqlRelationalTableModel
is just to allow columns of a source table to hold a foreign key into a different table, so that the value (typically an integer "id", and stored inEditRole
) is looked up in a foreign table and another column there (typically some text) is used as theDisplayRole
value in the source table instead of its own. AndQSqlRelationalDelegate
is just for editing the source table in this situation, so that e.g. a combobox of the text values from the foreign table can be offered to the user when editing the source table instead of dealing with the underlying id number. -
@JonB thanks for your effort and patience, surely my English is not helping here.
My post was quite long because I provided a MRE, my attempts and my researches.Anyway I try to explain it in another way. Please, feel free to ask what is not clear.
In table
protocols
there is a column (idEvent
) that is the foreign key towards another table (sayalarms
). If it was so simple, the standardQSqlRelationalTableModel
behavior was just fine.My problem is the
protocols
table has another column (eventType
) that is basically an enum: its value will change the destination table of the lookup. For example if the value is 0, the foreign key must lookup into thealarms
table, if the value is 1 it must lookup into thesessions
table and so on.Of course this very specific (and awkward...) behavior is not provided by the
QSqlRelationalTableModel
andQSqlRelationalDelegate
.My attempt was to manually select the right table in function of the
eventType
value and add theid
and thename
of the rows to theQComboBox
(as text and user-data). And this is done and working in thecreateEditor()
function.When the user open the editor I select the correct text finding out the current user-data (that is the
id
stored in the database as foreign key in theprotocols
table) and also this seems to work.My issue is when the user close the editor and I need to write to the model the new value: the
Qt::EditRole
should be theid
of the related row (and this is ok) but since I would show in theQTableView
thename
of the linked row (exactly as the standard lookup would do) my approach was to set this string to theQt::DisplayRole
hoping it will be painted to the cell. But I cannot set theQt::DisplayRole
to the model: thesetData()
fails.If you might suggest another approach (that does not require to change the db structure, since its not mine) I will be glad to try it.
-
@Mark81 said in How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole:
But I cannot set the Qt::DisplayRole to the model: the setData() fails.
How should those work? The model is not made for this as already explained. You have to implement this by yourself if you want and really need it (which I doubt).
-
@Christian-Ehrlicher ok, I didn't find an explanation in the docs why I cannot set the
Qt::DisplayRole
, but here I learnt I can't. So, can you kindly propose another approach to reach my goal please? -
@Mark81 said in How to change the displayed text in a QSqlRelationalDelegate subclass using the Qt::DisplayRole:
My problem is the protocols table has another column (eventType) that is basically an enum: its value will change the destination table of the lookup. For example if the value is 0, the foreign key must lookup into the alarms table, if the value is 1 it must lookup into the sessions table and so on.
I would just confirm that this means you cannot use what SQL offers for a "foreign key", and hence should mean that a
QSqlRelationalTableModel
is not applicable here and presumably should not be used. I think you would need to implement what you want from this entirely in your own code. And may well be problematic. -
@Mark81
It is "difficult" because you do not have a proper foreign key relation, which is whatQSqlRelationalTableModel
requires. Having some other column (eventType
) whose value dictates which of multiple tables (alarms
,sessions
, ...) is the foreign one in which to look up the key column's value is supported neither in SQL nor Qt'sQSqlRelationalTableModel
--- it is not allowed in the definition of a foreign key.Either think about redefining/rearchitecting your intended relationships which fits better with SQL or (in my opinion) give up trying to use
QSqlRelationalTableModel
. Instead read in the contents of all required/linked tables into their own normalQSqlTableModel
s, as well as the original "source" table, and implement your desired logic yourself in client code as required for your desired behaviour.