QTableView и модель QSqlRelationalTableModel

Замечания по стандартной реализации представления QTableView и модели QSqlRelationalTableModel.

Поработает отладчиком , позаходим в функции и посмотрим , что там делается. (возможно это только при сборке qt из исходников):

Первый кандидат на изучение QSqlTableModel::data(..)

Тут прикол для разума в том , что никакие данные кроме как для ролей DisplayRole и EditRole НЕ ХРАНЯТСЯ в модели данных!

QVariant QSqlTableModel::data(const QModelIndex &index, int role) const
{
    Q_D(const QSqlTableModel);
    if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::EditRole))
        return QVariant();

То есть все другие роли типа CheckStateRole, UserRole, BackgroundRole и т.д. - мы должны реализовывать сами! То есть где-то сами должны сохранять такие данные в нашем наследуемом от 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;
}

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

И правильно QSqlQueryModel::setData(index, value, role) всегда возвращает false.

QTableView

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

А творится там много чего и это не для слабонервных...

Сначала возникает какое либо событие от мышки ,клавиатуры и т.д. Значит надо искать входы этих событий в QTableView.

Но начинать надо с начала (не правда ли?..). А что есть начало?...

Допустим открыли мы QTableView , можно переопределить event() и посмотреть какие события приходят в QTableView (от ОС).

17:06:00 Debug: my_QTableView::event  "ChildPolished" 
17:06:00 Debug: my_QTableView::event  "ChildPolished" 
17:06:00 Debug: my_QTableView::event  "ChildPolished" 
17:06:00 Debug: my_QTableView::event  "ChildAdded" 
17:06:00 Debug: my_QTableView::event  "Polish" 
17:06:00 Debug: my_QTableView::event  "DynamicPropertyChange" 
17:06:00 Debug: my_QTableView::event  "FontChange" 
17:06:00 Debug: my_QTableView::event  "ChildPolished" 
17:06:00 Debug: my_QTableView::event  "ChildPolished" 
17:06:00 Debug: my_QTableView::event  "ChildPolished" 
17:06:00 Debug: my_QTableView::event  "Move" 
17:06:00 Debug: my_QTableView::event  "Resize" 
17:06:00 Debug: my_QTableView::event  "Show" 
17:06:00 Debug: my_QTableView::event  "ShowToParent" 
17:06:00 Debug: my_QTableView::event  "WindowActivate" 
17:06:00 Debug: my_QTableView::event  "PolishRequest" 
17:06:00 Debug: my_QTableView::event  "MetaCall" 
17:06:00 Debug: my_QTableView::event  "MetaCall" 
17:06:00 Debug: my_QTableView::event  "Timer" 
17:06:00 Debug: my_QTableView::event  "MetaCall" 
17:06:00 Debug: my_QTableView::event  "MetaCall" 
17:06:00 Debug: my_QTableView::event  "LayoutRequest" 
17:06:00 Debug: my_QTableView::event  "Paint" 
17:06:00 Debug: my_QTableView::event  "Timer" 
17:06:01 Debug: my_QTableView::event  "WindowDeactivate" 
17:06:01 Debug: my_QTableView::event  "Paint" 
17:06:01 Debug: my_QTableView::event  "Paint"

Далее как происходит интерактивное взаимодейтсвие? Щелкаем мышкой (левой кнопкой) на числовом поле, вбиваем цифру и нажимаем Enter, происходит выход из режима редактирования:

При перемещении мыши над QTableView будут возникать события Enter. Но сам обработчик Enter ничего не делает , только зачем-то посылает событие QStatusTipEvent в случае :

    case QEvent::Enter:
#ifndef QT_NO_STATUSTIP
        if (d->statusTip.size()) {
            QStatusTipEvent tip(d->statusTip);
            QApplication::sendEvent(const_cast<QWidget *>(this), &tip);
        }
#endif
        enterEvent(event);
        break;

Далее прилетает FocusIn и попадаем мы после этого в обработчик:

Bool QAbstractItemView::event(QEvent *event)

Он проверяем свои типы событий и перекидывает в
bool QAbstractScrollArea::event(QEvent *e)
тут тоде проверяются свои типы событий и перекидывают дальше в :
bool QFrame::event(QEvent *e)
Далее в
bool QWidget::event(QEvent *event)
и наконец заходим в :
focusInEvent((QFocusEvent*)event);
но это уже почему-то в :
void QAbstractItemView::focusInEvent(QFocusEvent *event)

void QAbstractItemView::focusInEvent(QFocusEvent *event)
{
    Q_D(QAbstractItemView);
    QAbstractScrollArea::focusInEvent(event);

    const QItemSelectionModel* model = selectionModel();
    const bool currentIndexValid = currentIndex().isValid();

    if (model
        && !d->currentIndexSet
        && !currentIndexValid) {
        bool autoScroll = d->autoScroll;
        d->autoScroll = false;
        QModelIndex index = moveCursor(MoveNext, Qt::NoModifier); // first visible index
        if (index.isValid() && d->isIndexEnabled(index) && event->reason() != Qt::MouseFocusReason)
            selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
        d->autoScroll = autoScroll;
    }

    if (model && currentIndexValid) {
        if (currentIndex().flags() != Qt::ItemIsEditable)
            setAttribute(Qt::WA_InputMethodEnabled, false);
        else
            setAttribute(Qt::WA_InputMethodEnabled);
    }

    if (!currentIndexValid)
        setAttribute(Qt::WA_InputMethodEnabled, false);

    d->viewport->update();
}

QModelIndex index = moveCursor(MoveNext, Qt::NoModifier) - тут определяется текущий индекс.

Далее проверяется его Qt::ItemIsEditable и как результат - для виджета устанавливаем аттрибут Qt::WA_InputMethodEnabled:
setAttribute(Qt::WA_InputMethodEnabled, true);

QApplicationPrivate::setFocusWidget
void QWidget::setFocus(Qt::FocusReason reason)
bool QApplication::notify(QObject *receiver, QEvent *e)
res = d->notify_helper(w, w == receiver ? mouse : &me);
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
// send to all receiver event filters
if (sendThroughObjectEventFilters(receiver, e)) !!!! вот здесь срабатывает indexAt
return true;
bool QAbstractItemView::viewportEvent(QEvent *event)
bool QAbstractScrollArea::viewportEvent(QEvent *e)
void QAbstractItemView::mousePressEvent(QMouseEvent *event)
....
QPersistentModelIndex index = indexAt(pos);
...
if (edit(index, NoEditTriggers, event))
return;
...
bool QAbstractItemView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)

} else {
w->setAttribute(Qt::WA_NoMouseReplay, false);
res = d->notify_helper(w, w == receiver ? mouse : &me);
e->spont = false;
}

KeyRelease
KeyPress

Изучаем QTableView::paintEvent(e) .

Сразу при открытие окна с QTableView начинается прорисовка всех ячеек таблицы. И это логично ведь само ничего не прорисуется.

После подготовительных операций вызывается drawCell для каждой видимой ячейки:
d->drawCell(&painter, option, index);
Это хорошо видно по index .

Далее после запроса флага Qt::ItemIsEnabled у модели данных выбирается соответсвующий вариант палитры. Также проверяется ниличие выделения по State_Selected. Проверяется QStyle::State_MouseOver над ячейкой. QStyle::State_HasFocus.

Отрисовка происходит по drawPrimitive. Тут указывается PrimitiveElement = PE_PanelItemViewRow.


    q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, q);

    q->itemDelegate(index)->paint(painter, opt, index);

Внутри itemDelegate всегда происходит отрисовка :
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);

CE_ItemViewItem это тип элемента управления (enum ControlElement из QStyle).