0

I have a QTableView backed by a QStandardItemModel. The table also supports editing.

One of the columns in this table contains a user editable QString. If the data changes, I need to know both the old and new QVariant values through a Qt signal. I do not need to know if the user cancelled the edit.

I looked into the QAbstractItemModel::dataChanged and QStandardItemModel::itemChanged signals, however these do not have mechanism of giving both the old & new data.

One possibility which came to mind was to implement a custom editing delegate - Overriding the setEditorData & setModelData virtual functions.

The idea is that when a user double clicks on the item in the table, setEditorData be called with the current item data from the cell (I capture this as old data in the mOldData QVariant fiekd).

One thing to note is that though this the setEditorData method is const, however using a mutable QVariant member mOldData allows me to still capture the old data.

Once the editing has been completed, and the user presses enter, or looses focus, the updated data is copied from the editor widget back into the model via setModelData method. I added a hook to this method to emit a custom signal.

void dataChanged(const QVariant& newValue, const QVariant& oldValue) const;

I implemented this and unfortunately it does not quite work: The setEditorData gets called 2 times before the setModelData (the first time it is called with the old data and the second time it is called with the new data), then after editing setModelData is called. This means that by the time I emit the dataChanged signal the old and new data are identical (and set to the new editor value). Why is it being called 2 times. I think I am doing the correct implementation in my subclass - effectively calling the base class. I put in a nasty hack below to make sure that the second call to setEditorData is ignored and this works - however it doesn't consider what happens when the user presses escape or aborts the edit - help there would be useful. I thing I need some sort of event filter.

Using a QItemDelegate subclass (QWAMDelegate is a KDAB framework subclass of QItemDelegate)) - See the tutorials here & here.

class PathItemDelegate final : public QWAMDelegate {
    Q_OBJECT
public:
    using QWAMDelegate::QWAMDelegate;
    explicit PathItemDelegate(QObject* parent = nullptr);
    void setEditorData(QWidget* editor, const QModelIndex& index) const override;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
Q_SIGNALS:
    void dataChanged(const QVariant& newValue, const QVariant& oldValue) const;
protected:
    void paintInternal(
        QPainter* painter,
        const QStyleOptionViewItem& option,
        const QModelIndex& index) const override;
private:
    // trick to capture missing old value when editing finished.
    mutable QVariant mOldValue;
    // crude cache to prevent checking filesystem
    // each time the view changes.
    mutable std::map<std::filesystem::path, bool> mPathStatuses;
    Q_DISABLE_COPY(PathItemDelegate)
};

The impmementation is shown below:

void
PathItemDelegate::setEditorData(
    QWidget* editor,
    const QModelIndex& index) const
{
    // copy the model data into the editor widget
    QItemDelegate::setEditorData(editor, index);
    // capture the old value
    if (!mOldValue.isValid()) {
        // hack to prevent 2nd update from overwriting the old data
        mOldValue = index.model()->data(index, Qt::EditRole);
        qDebug() << "setEditorData index=" << index << ", oldValue=" << mOldValue;
    }
}

void
PathItemDelegate::setModelData(
    QWidget* editor,
    QAbstractItemModel* model,
    const QModelIndex& index) const
{
    // save the updated data from the editor into the
    // model & notify the views of this change via the base class
    QItemDelegate::setModelData(editor, model, index);
    // get the updated value from the model
    const auto& newValue = index.model()->data(index, Qt::EditRole);
    qDebug() << "setModelData index=" << index << ", newValue=" << newValue << ", oldValue=" << mOldValue;
    Q_EMIT dataChanged(newValue, mOldValue);
    // rest the
    mOldValue.clear();
}
johnco3
  • 2,401
  • 4
  • 35
  • 67
  • If [*then after editing `setModelData` is called*] is true; that means `setModelData` gets called before the 2nd time `setEditorData` is called, does it not? In which case, it can emit the signal just like you intended. If all of the above is correct, I would change your code to remove your "hack" (`if (!mOldValue.isValid())`) and instead do `if (newValue != mOldValue) Q_EMIT dataChanged(newValue, mOldValue);` No point emitting any signal is the value is not changed after all. – Atmo Aug 21 '23 at 04:35
  • 1
    Thinking again, you could even initalize `mOldValue` in `setModelData`, i.e. as late as possible although still early enough to know when to send the signal. – Atmo Aug 21 '23 at 13:21
  • @Atmo - great suggestion, Sorry I meant to get back to you about your first post above - I was doing a little research and for some reason I get 2 calls to setEditor data before the first call to setModelData - really wierd - the first call contains the old data the second one contains the new data and then when the setModelData is called, the mOldData (due to the second call) matches the value in the editor so it does not work. I did notice 3 errors in the console indicating "QAbstractItemView::closeEditor called with an editor that does not belong to this view"I have no idea why this occurs – johnco3 Aug 21 '23 at 20:12
  • This message is super easy to find in the source code; it is after that `if` in qabstractitemview.cpp: `QModelIndex index = d->indexForEditor(editor); if (!index.isValid()) {` but I do not see what you did wrong. Have you tried to implement my 2nd suggestion? That would greatly simplify your code/logic. – Atmo Aug 22 '23 at 04:33
  • @Atmo - thanks I finally figured it out by simplifying everything and only overriding the setModelData - the 2nd unexpected call to setEditorData (which overwrote my 'mOldValue) was caused by calling the base class QItemDelegate::setModelData within my PathItemDelegate::setModelData. So my code works with these pesky editor warnings – johnco3 Aug 22 '23 at 09:11

0 Answers0