You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义QItemDelegate获取编辑前后数据遇重复调用问题求助

问题:QTableView编辑时获取数据新旧值的困境

我有一个基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 17:24:58