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();
}