как работает setData

Итак, что надо сразу понимать. Что все изменения данных в модели данных QSqlTableModel идут только через setData.

Функция setData принадлежит классу QSqlTableModel, в классе предке QSqlQueryModel setData нет. См.QSqlQueryModel

Установка значений происходит непосредственно уже в базу данных через функцию updateRowInTable.или insertRowIntoTable или deleteRowFromTable, тут тоже больше вариантов нет.Это все в модели данных QSqlTableModel.

Посмотрим QSqlTableModel::setData(..).


bool QSqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    Q_D(QSqlTableModel);
    if (role != Qt::EditRole)
        return QSqlQueryModel::setData(index, value, role);

    if (!index.isValid() || index.column() >= d->rec.count() || index.row() >= rowCount())
        return false;

    bool isOk = true;
    switch (d->strategy) {
    case OnFieldChange: {
        if (index.row() == d->insertIndex) {
            QSqlTableModelPrivate::setGeneratedValue(d->editBuffer, index.column(), value);
            return true;
        }
        d->clearEditBuffer();
        QSqlTableModelPrivate::setGeneratedValue(d->editBuffer, index.column(), value);
        isOk = updateRowInTable(index.row(), d->editBuffer);
        if (isOk)
            select();
        emit dataChanged(index, index);
        break; }
    case OnRowChange:
        if (index.row() == d->insertIndex) {
            QSqlTableModelPrivate::setGeneratedValue(d->editBuffer, index.column(), value);
            return true;
        }
        if (d->editIndex != index.row()) {
            if (d->editIndex != -1)
                submit();
            d->clearEditBuffer();
        }
        QSqlTableModelPrivate::setGeneratedValue(d->editBuffer, index.column(), value);
        d->editIndex = index.row();
        emit dataChanged(index, index);
        break;
    case OnManualSubmit: {
        QSqlTableModelPrivate::ModifiedRow &row = d->cache[index.row()];
        if (row.op == QSqlTableModelPrivate::None) {
            row.op = QSqlTableModelPrivate::Update;
            row.rec = d->rec;
            QSqlTableModelPrivate::clearGenerated(row.rec);
            row.primaryValues = d->primaryValues(indexInQuery(index).row());
        }
        QSqlTableModelPrivate::setGeneratedValue(row.rec, index.column(), value);
        emit dataChanged(index, index);
        break; }
    }
    return isOk;
}

submit при OnRowChange

Очень важным нюансом является наличие вызова функции submit в коде функции setData.это при варианте модели редактирование OnRowChange. В чём тут смысл: если вы меняете поле уже другой строки надо сохранить изменения ранее изменённой строки. Идея так себе.

Это как раз тот случай, когда возможно произойдет вызов нового select к базе данных. OnRowChange.

И все бы ничего, но нюанс в том, что после  submit слетают все текщие присвоенные индексы. То есть, если вы планировали вызвать еще какой-то  второй  setData, то в результате первого setData может произойти submit, потом произойдет select и как следствие все ранее установленные индексы (QModelIndex) протухнут. Буквально их row/column станут -1/-1.

Кстати именно это и будет являться признаком, что произошел select. И это можно использовать в своих целях.

 Теперь немного анализа кода setData

В зависимости от выбранной стратегии редактирования setData пишет данные в разные хранилища.

OnFieldChange

Может показаться, что при стратегии OnFieldChange данные сразу записываются в базу данных. И это правильно с одной оговоркой: если это не вставка новой строки.

При вставке новой строки и потом редактировании поля этой строки запись в базу сразу может и не произойти. Это например зависит от клавиши, которой вы завершаете редактирование.

Например если вы жмете Enter после редактирования - вызывается submit.

А если вы жмете tab после редактирования и переходите к другому полю этой вставляемой строки, то в функции setData функция submit не вызывется. А данные сохранятся в editBuffer. Тут речь была о визуальных делегатах для модели представления типа QTableView, о его делегатах. Дело в том, что делегаты всегда существуют по умолчанию.

OnRowChange

При стратегии OnRowChange данные сохраняются во временном буфере editBuffer (обычный QSqlRecord).

OnManualSubmit

При использовании стратегии OnManualSubmit данные пишутся в кэш модели данных (контейнер cache). То есть данные в базу данных не пишутся, все копится в cache.Для записи в базу данных надо в конце всех изменений вызывать submitAĺl.

Может показаться , что можно записывать данные от роли отличной от EditRole. Но изначально это не так, смотрите роли в модели данных.

Логгируем работу модели данных QSqlRelationalTableModel и ее представления QTableView.Смотрите логирование запросов.

setData вызывается обычно интерактивным элементом управления в QTableView при установке нового значения в поле таблицы, но естественно можно и программно вызывать из кода, надо только указать индекс ячейки.

Например что делает ComboBoxDelegate в setModelData индекс [1x6] newValue : 0. То есть тут и вызывается setData модели данных с целью установить новое значение.

В модели данных выпускается сигнал dataChanged (конкретно в QSqlTableModel) , который можно связать со своим слотом, чтобы проверять устанавливаемое значение на валидность. Но на самом деле проверять что-то уже поздно. Это только для информирования, что ячейка изменилась.

Может показаться, что обрабатывая dataChanged можно как сделать submit, так и revert установки нового значения, но по нашему мнению это вредное решение. 

Не надо лишний раз без четкого понимания зачем вы это делаете вызывать submit.


ComboBoxDelegate::createEditor [1x6] parent objName qt_scrollarea_viewport option  " QStyleOption( SO_ViewItem , LeftToRight , QStyle::State( "Active | Enabled | HasFocus" ) , QRect(439,30 90x29) ) 
"ComboBoxDelegate::updateEditorGeometry [1x6] option "   option :  QStyleOption( SO_ViewItem , LeftToRight , QStyle::State( "Active | Enabled | HasFocus" ) , QRect(439,30 90x29) ) 
ComboBoxDelegate::slot_currentIndexChanged commitData currentIndex() :  -1 
ComboBoxDelegate::slot_currentIndexChanged commitData currentIndex() :  0 
"ComboBoxDelegate::setModelData [1x6]  newValue : 0" 
   ComboBoxDelegate::setModelData model->setData(index, newValue, Qt::EditRole) 
my_TableView_Purchases::dataChanged return 
testCheck::purch_dataChanged 
     "taxCode"  :  "0" 
testCheck::slot_beforeUpdatePurch  field :  "taxCode"   isGenerated :  true  newVal  QVariant(int, 0) 
table_QSqlRelationalTableModel::updateRowInTable row  1   true 

testCheck::purch_dataChanged submitAll  true 
 ComboBoxDelegate::setModelData model->setData(index, newValue, Qt::EditRole) :  true 
table_QSqlRelationalTableModel::updateRowInTable row  -1   false 
 ComboBoxDelegate::setModelData model->submit()  false   newValue  0

Примечание: setData и далее submit в итоге используют те же sql команды типа updateRowInTableinsertRowIntoTable.

Теперь пару слов OnRowChange.

Может случится такая ситуация при стратегии редактирования OnRowChange, что вы открываете QTableView изменяете значение ячейки таблицы, закрываете таблицу и изменения пропадают. Что это значит: submit не был вызван, потому вы не перешли к редактированию ячейки другой строки. И это правда, но что делать? К сожалению модель данных QSqlTableModel предлагает только обходить все ячейки таблицы и проверять их через isDirty и это не удобно, но вы можете унаследоваться от QSqlTableModel и при setData помечать какая строка осталась последняя изменённая и при закрытии таблицы проверять это и если надо вызывать submit

А можете поступить ещё так: взять наш класс QpSqlTableModel , унаследованный от QSqlTableModel и взять нашу таблицу QpTableViewWrapper, унаследованную от QTableView, там эта проблема уже решена и ещё есть очень много нового полезного функционала, смотрите здесь: Развиваем класс QSqlTableModel.