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_HThe 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_TYPEtext.When I create the editor I add the items in the
QComboBoxusing thenameas text andidas 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 theDisplayRolethat should printed in the cell of theQTableView. My current test implementation above should always print "foo". Instead it shows theEditRole, i.e. theidvalue.QSqlRelationalDelegatedoes not reimplement thepaintfunction, hence I checked the source code for theQStyledItemDelegateand 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
paintfunction: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
QStyledItemDelegatelevel. But the debugger told me the actual value for theDisplayRolewas 3 (theidin 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
DisplayRolevalue. Why?Unfortunately I cannot debug the
setDatafunction: placing a breakpoint there will hang the whole screen, nothing reacts anymore. I had to killgdbfrom 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 thelastErrorfor queries). -
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_HThe 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_TYPEtext.When I create the editor I add the items in the
QComboBoxusing thenameas text andidas 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 theDisplayRolethat should printed in the cell of theQTableView. My current test implementation above should always print "foo". Instead it shows theEditRole, i.e. theidvalue.QSqlRelationalDelegatedoes not reimplement thepaintfunction, hence I checked the source code for theQStyledItemDelegateand 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
paintfunction: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
QStyledItemDelegatelevel. But the debugger told me the actual value for theDisplayRolewas 3 (theidin 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
DisplayRolevalue. Why?Unfortunately I cannot debug the
setDatafunction: placing a breakpoint there will hang the whole screen, nothing reacts anymore. I had to killgdbfrom 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 thelastErrorfor queries).@Mark81
I am not sure I follow all that you have done/said. However, the way things work with aQSqlRelationis that the data table stores the desired value (like an integer id or similar) as itsEditRoleand the delegate looks that up in the foreign table to find the display value from there. Presumably ignoring anyDisplayRoleit would normally use for a data table and doing its own look up instead. -
@Mark81
I am not sure I follow all that you have done/said. However, the way things work with aQSqlRelationis that the data table stores the desired value (like an integer id or similar) as itsEditRoleand the delegate looks that up in the foreign table to find the display value from there. Presumably ignoring anyDisplayRoleit 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
QStyledItemDelegatebut 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. -
@JonB ok, I changed my class so now it is a subclass of
QStyledItemDelegatebut 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
QSqlRelationalTableModelis 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 theDisplayRolevalue in the source table instead of its own. AndQSqlRelationalDelegateis 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. -
@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
QSqlRelationalTableModelis 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 theDisplayRolevalue in the source table instead of its own. AndQSqlRelationalDelegateis 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
protocolsthere is a column (idEvent) that is the foreign key towards another table (sayalarms). If it was so simple, the standardQSqlRelationalTableModelbehavior was just fine.My problem is the
protocolstable 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 thealarmstable, if the value is 1 it must lookup into thesessionstable and so on.Of course this very specific (and awkward...) behavior is not provided by the
QSqlRelationalTableModelandQSqlRelationalDelegate.My attempt was to manually select the right table in function of the
eventTypevalue and add theidand thenameof 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
idstored in the database as foreign key in theprotocolstable) 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::EditRoleshould be theidof the related row (and this is ok) but since I would show in theQTableViewthenameof the linked row (exactly as the standard lookup would do) my approach was to set this string to theQt::DisplayRolehoping it will be painted to the cell. But I cannot set theQt::DisplayRoleto 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.
-
@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
protocolsthere is a column (idEvent) that is the foreign key towards another table (sayalarms). If it was so simple, the standardQSqlRelationalTableModelbehavior was just fine.My problem is the
protocolstable 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 thealarmstable, if the value is 1 it must lookup into thesessionstable and so on.Of course this very specific (and awkward...) behavior is not provided by the
QSqlRelationalTableModelandQSqlRelationalDelegate.My attempt was to manually select the right table in function of the
eventTypevalue and add theidand thenameof 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
idstored in the database as foreign key in theprotocolstable) 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::EditRoleshould be theidof the related row (and this is ok) but since I would show in theQTableViewthenameof the linked row (exactly as the standard lookup would do) my approach was to set this string to theQt::DisplayRolehoping it will be painted to the cell. But I cannot set theQt::DisplayRoleto 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).
-
@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? -
@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
protocolsthere is a column (idEvent) that is the foreign key towards another table (sayalarms). If it was so simple, the standardQSqlRelationalTableModelbehavior was just fine.My problem is the
protocolstable 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 thealarmstable, if the value is 1 it must lookup into thesessionstable and so on.Of course this very specific (and awkward...) behavior is not provided by the
QSqlRelationalTableModelandQSqlRelationalDelegate.My attempt was to manually select the right table in function of the
eventTypevalue and add theidand thenameof 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
idstored in the database as foreign key in theprotocolstable) 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::EditRoleshould be theidof the related row (and this is ok) but since I would show in theQTableViewthenameof the linked row (exactly as the standard lookup would do) my approach was to set this string to theQt::DisplayRolehoping it will be painted to the cell. But I cannot set theQt::DisplayRoleto 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:
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
QSqlRelationalTableModelis 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 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
QSqlRelationalTableModelis 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 whatQSqlRelationalTableModelrequires. 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 normalQSqlTableModels, as well as the original "source" table, and implement your desired logic yourself in client code as required for your desired behaviour.