Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 552
Uga Buga Avatar asked Dec 01 '14 11:12

Uga Buga


3 Answers

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
    ...
}
like image 136
Uga Buga Avatar answered Oct 19 '22 09:10

Uga Buga


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*)));

}
like image 30
Nejat Avatar answered Oct 19 '22 09:10

Nejat


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.

like image 22
Kosovan Avatar answered Oct 19 '22 10:10

Kosovan