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.