QAbstractTableModel и emit dataChanged для одной строки - PullRequest
0 голосов
/ 04 ноября 2018

Я вывел модель из QAbstractTableModel и теперь хочу сообщить, что данные целой строки были изменены. Если, например, данные строки с индексом 5 изменены (4 столбца), то использование следующего кода работает как положено.

emit dataChanged(index(5,0), index(5, 0));
emit dataChanged(index(5,1), index(5, 1));
emit dataChanged(index(5,2), index(5, 2));
emit dataChanged(index(5,3), index(5, 3));

Но если я попытаюсь добиться того же только с одним излучением, ВСЕ столбцы ВСЕХ строк в представлении обновятся.

emit dataChanged(index(5, 0), index(5, 3));

Что я здесь не так делаю?

Минимальный пример (C ++ 11, QTCreator 4.7.1, Windows 10 (1803), 64-разрядная версия)

demo.h

#pragma once
#include <QAbstractTableModel>
#include <QTime>
#include <QTimer>

class Demo : public QAbstractTableModel
{
  Q_OBJECT
  QTimer * t;
public:
  Demo()
  {
    t = new QTimer(this);
    t->setInterval(1000);
    connect(t, SIGNAL(timeout()) , this, SLOT(timerHit()));
    t->start();
  }

  QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
  {
    int c = index.column();
    if (role == Qt::DisplayRole)
    {
      QString strTime = QTime::currentTime().toString();
      if (c == 0) return "A" + strTime;
      if (c == 1) return "B" + strTime;
      if (c == 2) return "C" + strTime;
      if (c == 3) return "D" + strTime;
    }
    return QVariant();
  }

  int rowCount(const QModelIndex &) const override { return 10; }
  int columnCount(const QModelIndex &) const override { return 4; }
private slots:
  void timerHit()
  {
    //Works
    emit dataChanged(index(5,0), index(5, 0));
    emit dataChanged(index(5,1), index(5, 1));
    emit dataChanged(index(5,2), index(5, 2));
    emit dataChanged(index(5,3), index(5, 3));

    //emit dataChanged(index(5,0), index(5, 3)); // <-- Doesn't work
  }
};

main.cpp

#include "demo.h"
#include <QApplication>
#include <QTreeView>

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  QTreeView dataView;
  Demo dataModel{};
  dataView.setModel( &dataModel );
  dataView.show();
  return a.exec();
}

Ответы [ 2 ]

0 голосов
/ 04 ноября 2018

Я думаю, что проблема заключается в определенных допущениях, которые вы делаете относительно поведения QTreeView, когда испускается сигнал QAbstractItemModel::dataChanged.

В частности, вы предполагаете, что представление будет вызывать QAbstractItemModel::data только для тех индексов, которые указаны в сигнале. Это не обязательно так.

Глядя на источник для QAbstractItemView::dataChanged (Qt 5.11.2), вы увидите ...

void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
    Q_UNUSED(roles);
    // Single item changed
    Q_D(QAbstractItemView);
    if (topLeft == bottomRight && topLeft.isValid()) {
        const QEditorInfo &editorInfo = d->editorForIndex(topLeft);
        //we don't update the edit data if it is static
        if (!editorInfo.isStatic && editorInfo.widget) {
            QAbstractItemDelegate *delegate = d->delegateForIndex(topLeft);
            if (delegate) {
                delegate->setEditorData(editorInfo.widget.data(), topLeft);
            }
        }
        if (isVisible() && !d->delayedPendingLayout) {
            // otherwise the items will be update later anyway
            update(topLeft);
        }
    } else {
        d->updateEditorData(topLeft, bottomRight);
        if (isVisible() && !d->delayedPendingLayout)
            d->viewport->update();
    }

#ifndef QT_NO_ACCESSIBILITY
    if (QAccessible::isActive()) {
        QAccessibleTableModelChangeEvent accessibleEvent(this, QAccessibleTableModelChangeEvent::DataChanged);
        accessibleEvent.setFirstRow(topLeft.row());
        accessibleEvent.setFirstColumn(topLeft.column());
        accessibleEvent.setLastRow(bottomRight.row());
        accessibleEvent.setLastColumn(bottomRight.column());
        QAccessible::updateAccessibility(&accessibleEvent);
    }
#endif
    d->updateGeometry();
}

Важным моментом является то, что этот код ведет себя по-разному, в зависимости от того, указывает ли сигнал один QModelIndex - например, topLeft совпадает с bottomRight. Если они равны , то представление пытается обеспечить обновление только этого модельного индекса. Однако, если указано несколько модельных индексов, это вызовет ...

d->viewport->update();

, что, предположительно, приведет к получению данных для всех видимых модельных индексов.

Поскольку ваша реализация Demo::data всегда возвращает новые данные, основанные на текущем времени, вы увидите всю видимую часть обновления представления, создавая впечатление, что сигнал dataChanged был излучен для всех строк и столбцов.

Таким образом, исправление состоит в том, чтобы сделать вашу модель данных более «состоящей из состояний» - она ​​должна отслеживать значения, а не просто генерировать их по требованию.

0 голосов
/ 04 ноября 2018

Не уверен, что это то, что вы ищете, но я все равно это подниму.


Даже при использовании emit dataChanged(...) вы все равно увидите, что щелчки / выборки по строкам приведут к их самообновлению (при этом на Mac это может отличаться).

Вместо использования сигнала QAbstractItemModel::dataChanged я буду использовать функцию QAbstractItemModel::setData().

Это моя реализация demo.h

#pragma once
#include <QAbstractTableModel>
#include <QTime>
#include <QTimer>

class Demo : public QAbstractTableModel
{
    Q_OBJECT

public:
    Demo()
    {
        int cCount = columnCount(index(0, 0));
        int rCount = rowCount(index(0, 0));

        //  populate model data with *static* values
        QString strTime = QTime::currentTime().toString();

        QStringList temp;
        for (int j = 0; j < cCount; j++)
            temp.append(strTime);
        for (int i = 0; i < rCount; i++)
            demoModelData.append(temp);

        //  nothing new here
        t = new QTimer(this);
        t->setInterval(1000);
        connect(t, SIGNAL(timeout()) , this, SLOT(timerHit()));
        t->start();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {

        //  tells the *view* what to display
        //      if this was dynamic (e.g. like your original strTime implementation)
        //      then the view (QTreeView in main.cpp) will constantly update
        if (role == Qt::DisplayRole)
            return demoModelData.at(index.row()).at(index.column());    //  retrieve data from model

        return QVariant();
    }

    //  reimplemented from QAbstractTableModel
    virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override
    {
        if (role == Qt::DisplayRole)
        {
            demoModelData[index.row()][index.column()] = value.toString();  //  set the new data

            emit dataChanged(index, index);     //  explicitly emit dataChanged signal, notifies TreeView to update by 
                                                //  calling this->data(index, Qt::DisplayRole)
        }

        return true;
    }

    int rowCount(const QModelIndex &) const override { return 10; }
    int columnCount(const QModelIndex &) const override { return 4; }

private slots:
    void timerHit()
    {
        QString strTime = QTime::currentTime().toString();
        setData(index(5, 0), QVariant(strTime), Qt::DisplayRole);   //  only changes index at (row = 5, col = 0)
    }

private:
    QTimer *t;

    QList<QStringList> demoModelData;       //  stores the table model's data

};

Поскольку класс является «моделью», должен быть какой-то способ хранения / извлечения данных для отображения. Здесь я использовал QList<QStringList>, но вы можете хранить данные и другими подходящими для вас способами (например, дерево, QVector, QMap).

...