自定义QItemDelegate获取编辑前后数据遇重复调用问题求助
我有一个基于QStandardItemModel的QTableView,支持编辑功能。其中某列是可编辑的QString,需要在数据变更时通过Qt信号获取旧值与新值(无需处理编辑取消场景)。
研究了QAbstractItemModel::dataChanged和QStandardItemModel::itemChanged信号,但它们无法同时提供新旧数据。于是打算实现自定义编辑委托,重写setEditorData和setModelData虚函数:
- 用户双击单元格时,
setEditorData会被调用,尝试用可变成员变量mOldValue捕获旧数据; - 编辑完成(回车或失去焦点)时,
setModelData把编辑器数据写回模型,此时想发射自定义信号传递新旧数据。
但实际运行发现setEditorData会在setModelData前被调用两次:第一次传入旧数据,第二次传入新数据,导致发射信号时新旧数据一致。临时加判断忽略第二次调用能生效,但没处理编辑取消的情况,求合理解决方案。
自定义委托代码
头文件
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: mutable QVariant mOldValue; mutable std::map<std::filesystem::path, bool> mPathStatuses; Q_DISABLE_COPY(PathItemDelegate) };
实现代码
void PathItemDelegate::setEditorData( QWidget* editor, const QModelIndex& index) const { QItemDelegate::setEditorData(editor, index); if (!mOldValue.isValid()) { mOldValue = index.model()->data(index, Qt::EditRole); qDebug() << "setEditorData index=" << index << ", oldValue=" << mOldValue; } } void PathItemDelegate::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { QItemDelegate::setModelData(editor, model, index); const auto& newValue = index.model()->data(index, Qt::EditRole); qDebug() << "setModelData index=" << index << ", newValue=" << newValue << ", oldValue=" << mOldValue; Q_EMIT dataChanged(newValue, mOldValue); mOldValue.clear(); }
解决方案
核心思路:在编辑开始时一次性捕获旧值
问题根源在于setEditorData可能因样式刷新、编辑器复用等原因被多次调用,不能仅靠mOldValue是否有效来判断是否记录旧值。正确的做法是在编辑启动瞬间捕获旧值,而非依赖setEditorData的调用时机。
方案1:重写createEditor捕获初始值
编辑器创建时对应编辑开始的时机,此时记录旧值最准确,因为每个编辑会话只会创建一次编辑器:
修改委托,添加createEditor重写:
QWidget* PathItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { QWidget* editor = QItemDelegate::createEditor(parent, option, index); // 编辑启动时记录旧值 mOldValue = index.model()->data(index, Qt::EditRole); qDebug() << "编辑启动,旧值:" << mOldValue; return editor; }
同时修改setEditorData,移除旧值判断逻辑:
void PathItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { QItemDelegate::setEditorData(editor, index); // 不再在此处记录旧值 }
这样无论setEditorData被调用多少次,mOldValue始终是编辑前的原始值,确保信号发射时新旧值正确。
方案2:处理编辑取消场景(可选)
如果需要处理用户按Esc取消编辑的情况,可以在createEditor中监听编辑器的editingFinished信号,判断是否需要重置旧值:
QWidget* PathItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { QWidget* editor = QItemDelegate::createEditor(parent, option, index); mOldValue = index.model()->data(index, Qt::EditRole); // 针对QLineEditor(字符串编辑常用编辑器)监听结束信号 if (QLineEdit* lineEdit = qobject_cast<QLineEdit*>(editor)) { connect(lineEdit, &QLineEdit::editingFinished, this, [this, lineEdit, index]() { QString editorContent = lineEdit->text(); QVariant currentModelValue = index.model()->data(index, Qt::EditRole); // 编辑器内容与旧值一致,且模型数据未变更,说明是取消编辑 if (editorContent == mOldValue.toString() && currentModelValue == mOldValue) { mOldValue.clear(); } }); } return editor; }
方案3:模型层重写setData(更简洁的替代方案)
如果不需要定制编辑器,直接继承QStandardItemModel重写setData是更简单的方式,在数据变更前后直接获取新旧值:
class CustomStandardItemModel : public QStandardItemModel { Q_OBJECT public: using QStandardItemModel::QStandardItemModel; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { if (role == Qt::EditRole) { QVariant oldValue = data(index, Qt::EditRole); bool updateResult = QStandardItemModel::setData(index, value, role); // 仅当数据确实变更时发射信号 if (updateResult && oldValue != value) { Q_EMIT dataChangedWithOldValue(index, value, oldValue); } return updateResult; } return QStandardItemModel::setData(index, value, role); } Q_SIGNALS: void dataChangedWithOldValue(const QModelIndex& index, const QVariant& newValue, const QVariant& oldValue); };
这种方式无需修改委托,直接在模型层完成新旧值的捕获与信号发射,逻辑更清晰。
内容的提问来源于stack exchange,提问作者johnco3

