How to get the old value when handling QAbstractItemModel::dataChanged() signal?

2.8k views Asked by At

I have a QTableView which have set a QStandardItemModel. The user edits data in some index in the view and then the model emits the dataChanged() signal. In the SLOT where I am handling the SIGNAL I have the QModelIndex range of the user changes and thus I can get the new values the user have entered. How can I obtain the old values at that point?

4

There are 4 answers

0
Nejat On

Since QStandardItemModel is a simple model, it does not have a signal with this feature. If you want such a feature you can subclass QAbstractItemModel and have your own custom class and implement setData and emit a custom signal which contains both the old and the new values.

As a workaround you can connect the itemChanged signal of QStandardItemModel to some slot :

connect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));

And store the new value as a Qt::UserRole in model to use it as the old value when the slot is called next time :

void MyClass::onModelChanged(QStandardItem *item)
{

   disconnect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));

   QVariant oldValue = item->data(Qt::UserRole);

   item->setData(item->data(Qt::DisplayRole), Qt::UserRole); //Store the new value for next use

   connect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));

}
0
Jablonski On

User can change data with delegate so possible solution is:

#ifndef ITEMDELEGATE_H
#define ITEMDELEGATE_H

#include <QItemDelegate>

class ItemDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    explicit ItemDelegate(QObject *parent = 0);

protected:
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void setEditorData(QWidget * editor, const QModelIndex & index) const;
    void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const;
    void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const;

signals:
    void dataChanged(QString oldValue,QString newValue) const;

public slots:
private:
     mutable QString old;//we want change member data in const method

};

#endif // ITEMDELEGATE_H

As you can see many methods are const by default so I did some tricks (such as mutable) to avoid problems. Also in my edited answer I doesn't store old data in UserRole+1 , all done with old mutable variable.

cpp:

#include "itemdelegate.h"
#include <QLineEdit>
#include <QDebug>

ItemDelegate::ItemDelegate(QObject *parent) :
    QItemDelegate(parent)
{
}

QWidget *ItemDelegate::createEditor(QWidget *parent,
                                    const QStyleOptionViewItem &option,
                                    const QModelIndex &index) const
{
    QLineEdit *editor = new QLineEdit(parent);
    return editor;
}


void ItemDelegate::setEditorData(QWidget *editor,
                                 const QModelIndex &index) const
{
    old = index.model()->data(index, Qt::EditRole).toString();//store old data
    QLineEdit *line = qobject_cast<QLineEdit*>(editor);
    line->setText(old);
}


void ItemDelegate::setModelData(QWidget *editor,
                                QAbstractItemModel *model,
                                const QModelIndex &index)const
{
    QLineEdit *line = static_cast<QLineEdit*>(editor);
    QString data = line->text();

    emit dataChanged(old, line->text());
    model->setData(index, data);
}


void ItemDelegate::updateEditorGeometry(QWidget *editor,
                                        const QStyleOptionViewItem &option,
                                        const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}

Usage:

  ItemDelegate * del = new ItemDelegate;
   connect(del,&ItemDelegate::dataChanged,[=](QString oldValue,QString newValue) {
        qDebug() << "old" << oldValue<< "new" <<newValue ;
   });
    ui->tableView->setItemDelegate(del);

I tested it and it works. It will work with different models. QTableView uses lineEdit as delegate by default, so user will not see any changes in view.

I used here C++11 (CONFIG += c++11 to .pro file) and new syntax of signals and slots, but of course you can use old syntax if you want.

0
Uga Buga On

After some research I figured out that there is no standard way to achive this behaviour. To solve the problem I had to inherit QStandardItemModel and reimplement setData() like this:

class RecallModel : public QStandardItemModel
{
public:
    RecallModel (QObject * parent = 0) : QStandardItemModel(parent) {}

    // Reimplemented
    bool setData(const QModelIndex &index, const QVariant &value, int role= Qt::EditRole)
    {
        // backup the previous model data
        if (role == Qt::EditRole || role == Qt::DisplayRole)
            QStandardItemModel::setData(index, data(index), Qt::UserRole + 1);

        return QStandardItemModel::setData(index, value, role);
    }
};

And after that I can access the old data in the slot handling the dataChanged() signal:

void SomeObject::handleDataChange(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    ...
    const QVariant &vOldData = index.data(Qt::UserRole + 1); // here is the old data
    const QVariant &vNewData = index.data(Qt::DisplayRole); // here is the new data
    ...
}
0
Andrei Shikalev On

All solutions before this answer rely on Qt-specific features. But you can move INSERT, UPDATE, DELETE (not SQL, but common) functionality outside Qt framework. Look at Unit Of Work Design Pattern, and related Domain Object pattern from "Patterns of Enterprise Application Architecture".

You can modify this patterns to hold old values and get it when you modify during setData().