Subclassed QSqlRelationalDelegate not working properly with SQlite view



  • I have table view with a QSortFilterProxy model. The source of the proxy is a QSqlRelationalTableModel. I have subclassed QSqlRelationalDelegate for the QSortFilterProxyModel to edit and display column with foreign key support (ComboBox) and set the table view delegate to it.

    SQlite table 1:
    | ID1 | Name | foreign_key_table2 | foreign_key_table3 |
    
    
    SQlite table 2:
    | ID2 | Value 1 | Value 2 | Value 3 |
    
    SQlite view (join of table1 and table2):
    | ID1 | Name | Value 1 | Value 2 | Value 3 | foreign_key_table3 |
    

    If I set the SQlite table1 as source for the QSqlRelationalTableModel with the correspondend relations, I can edit and display all data properly. The Data gets written to the database.
    If I set the SQlite view as source for for the QSqlRelationalTableModel, I can only edit the cell I first double click on. The createEditor methode of the subclassed delegate is only called on this cell. All other cells are not editable anymore. No data is written to the database.

    I don't fully understand the delegate mechanics. Why does it makes any difference if I use a database view as source and not a table. I noticed that the column header name changes if I set the view as source.

    QTableview headers
    |view.ID1 | view.Name | view.value1 | view.value2 | view.value3 | relTblAl_7.table4ColumnName |
    

    Subclassed delegate

    class SqlProxyRelationalDelegate : public QSqlRelationalDelegate
    {
        Q_OBJECT
    public:
        static int fieldIndex(const QSqlTableModel *const model,
                              const QSqlDriver *const driver,
                              const QString &fieldName)
        {
            const QString stripped = driver->isIdentifierEscaped(fieldName, QSqlDriver::FieldName)
                    ? driver->stripDelimiters(fieldName, QSqlDriver::FieldName)
                    : fieldName;
            return model->fieldIndex(stripped);
        }
        explicit SqlProxyRelationalDelegate(QObject *parent = nullptr);
        virtual QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
        virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
        virtual void setEditorData(QWidget *editor, const QModelIndex &index) const;
    };
    

    Delegate implementation

    QWidget * SqlProxyRelationalDelegate::createEditor(QWidget *aParent, const QStyleOptionViewItem &option, const QModelIndex &index) const{
        const QSqlRelationalTableModel* sqlModel = qobject_cast<const QSqlRelationalTableModel*>(index.model());
        QSqlTableModel* childModel = sqlModel ? sqlModel->relationModel(index.column()) : nullptr;
        if (!childModel)
        {
            const QSortFilterProxyModel* proxyModel = qobject_cast<const QSortFilterProxyModel*>(index.model());
            if (proxyModel)
            {
                sqlModel = qobject_cast<const QSqlRelationalTableModel*>(proxyModel->sourceModel());
                childModel = sqlModel ? sqlModel->relationModel(index.column()) : nullptr;
            }
        }
        if (!childModel)
        {
            return QItemDelegate::createEditor(aParent, option, index);
        }
    
        const QSqlDriver *const driver = childModel->database().driver();
        QComboBox* combo = new QComboBox(aParent);
    
        combo->setModel(childModel);
        int u = fieldIndex(childModel,driver,sqlModel->relation(index.column()).displayColumn());
        combo->setModelColumn(u);
        //combo->setFocusPolicy(Qt::StrongFocus);
        combo->installEventFilter(const_cast<SqlProxyRelationalDelegate*>(this));
    
        return combo;
    }
    
    void SqlProxyRelationalDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const{
    
        QString strVal = "";
        const QSqlRelationalTableModel* sqlModel = qobject_cast<const QSqlRelationalTableModel*>(index.model());
        if (!sqlModel )
        {
            const QSortFilterProxyModel* proxyModel = qobject_cast<const QSortFilterProxyModel*>(index.model());
            if (proxyModel) {
                strVal = proxyModel->data(index).toString();
            }
        } else {
            strVal = sqlModel->data(index).toString();
        }
    
        QComboBox* combo = qobject_cast<QComboBox*>(editor);
    
        if (strVal.isEmpty() || !combo) {
            QItemDelegate::setEditorData(editor, index);
            return;
        }
    
        combo->setCurrentIndex(combo->findText(strVal));
    }
    
    void SqlProxyRelationalDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const{
        if (!index.isValid())
            return;
    
        QSqlRelationalTableModel* sqlModel = qobject_cast<QSqlRelationalTableModel*>(model);
    
        QSortFilterProxyModel* proxyModel = NULL;
        if (!sqlModel )
        {
            proxyModel = qobject_cast<QSortFilterProxyModel*>(model);
            if (proxyModel)
                sqlModel = qobject_cast<QSqlRelationalTableModel*>(proxyModel->sourceModel());
        }
    
        QSqlTableModel *childModel = sqlModel ? sqlModel->relationModel(index.column()) : nullptr;
    
        QComboBox* combo = qobject_cast<QComboBox*>(editor);
        if (!childModel || !combo) {
            QItemDelegate::setModelData(editor, model, index);
            return;
        }
    
        int currentItem = combo->currentIndex();
    
        int childColIndex = childModel->fieldIndex(sqlModel->relation(index.column()).displayColumn());
        int childEditIndex = childModel->fieldIndex(sqlModel->relation(index.column()).indexColumn());
    
        if (proxyModel) {
            proxyModel->setData(index, childModel->data(childModel->index(currentItem, childColIndex), Qt::DisplayRole), Qt::DisplayRole);
            proxyModel->setData(index, childModel->data(childModel->index(currentItem, childEditIndex), Qt::EditRole), Qt::EditRole);
        } else {
            sqlModel->setData(index, childModel->data(childModel->index(currentItem, childColIndex), Qt::DisplayRole), Qt::DisplayRole);
            sqlModel->setData(index, childModel->data(childModel->index(currentItem, childEditIndex), Qt::EditRole), Qt::EditRole);
        }
    }
    

  • Lifetime Qt Champion

    Hi,

    SQLite views are read-only..



  • That should not be a problem. I set triggers to implement the missing write feature for views.
    I did some further testing.

    1. QSqlRelationalTableModel with Sqlite view as source but no relation set.
    2. QSqlRelationalTableModel with Sqlite view as source with relations set.
    3. QSortFilterProxyModel with 2) as source

    With 1) I can edit data in the view and it gets written to the DB.
    With 2) and 3) Data edits are not commited to the db.

    It seams there is already a problem with QSqlRelationalTableModel and views.


  • Lifetime Qt Champion

    Did you already checked the bug report system to see if there was something related ?

    If not, please consider opening a new report providing a minimal compilable example showing the behaviour.



Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.