Reputation: 43
I have a simple QTableView with a QSortFilterProxyModel and a source model of a custom TableModel subclass that inherits from QAbstractTableModel. The model is dynamically updated with additional rows.
My problem is this: If I sort the table on a column, then scroll to a specific row, and then more rows are added above this row it pushes the row down. Data is coming in fast enough that it makes it difficult to click on rows to edit them without the row changing underneath my cursor.
Is there a way to stop the table from scrolling and maintain the position of the table relative to say a selected row?
Upvotes: 3
Views: 2212
Reputation: 20141
QTableView::rowViewportPosition()
can be used to get the current view port position which has to be corrected if something is inserted before current index.
It can be retrieved before and after insertion of a row using signal handlers.
Thus, the scrolling can be adjusted accordingly in the signal handler after the insertion. This is done changing the value of the vertical scrollbar. (The vertical scroll mode is changed to QTableView::ScrollPerPixel
to ensure correct vertical adjustment.)
A minimal code sample:
#include <iostream>
#include <QApplication>
#include <QMainWindow>
#include <QScrollBar>
#include <QStandardItemModel>
#include <QTableView>
#include <QTimer>
enum { NCols = 2 }; // number of columns
enum { Interval = 1000 }; // interval of auto action
enum { NRep = 5 }; // how often selected auto action is repeated
// fills a table model with sample data
void populate(QStandardItemModel &tblModel, bool prepend)
{
int row = tblModel.rowCount();
if (prepend) tblModel.insertRow(0);
for (int col = 0; col < NCols; ++col) {
QStandardItem *pItem = new QStandardItem(QString("row %0, col %1").arg(row).arg(col));
tblModel.setItem(prepend ? 0 : row, col, pItem);
}
}
// does some auto action
void timeout(QTimer &timer, QStandardItemModel &tblModel)
{
static int step = 0;
++step;
std::cout << "step: " << step << std::endl;
switch (step / NRep % 3) {
case 0: break; // pause
case 1: populate(tblModel, false); break; // append
case 2: populate(tblModel, true); break; // prepend
}
}
// managing the non-scrolling when something is inserted.
struct NoScrCtxt {
QTableView &tblView;
int y;
NoScrCtxt(QTableView &tblView_): tblView(tblView_) { }
void rowsAboutToBeInserted()
{
y = tblView.rowViewportPosition(tblView.currentIndex().row());
}
void rowsInserted()
{
int yNew = tblView.rowViewportPosition(tblView.currentIndex().row());
if (y != yNew) {
if (QScrollBar *pScrBar = tblView.verticalScrollBar()) {
pScrBar->setValue(pScrBar->value() + yNew - y);
}
}
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
// build some GUI
QMainWindow win;
QStandardItemModel tblModel(0, NCols);
for (int i = 0; i < 10; ++i) populate(tblModel, false);
QTableView tblView;
tblView.setVerticalScrollMode(QTableView::ScrollPerPixel);
tblView.setModel(&tblModel);
win.setCentralWidget(&tblView);
win.show();
// setup a "no-scroll manager"
NoScrCtxt ctxt(tblView);
QObject::connect(&tblModel, &QStandardItemModel::rowsAboutToBeInserted,
[&ctxt](const QModelIndex&, int, int) { ctxt.rowsAboutToBeInserted(); });
QObject::connect(&tblModel, &QStandardItemModel::rowsInserted,
[&ctxt](const QModelIndex&, int, int) { ctxt.rowsInserted(); });
// initiate some auto action
QTimer timer;
timer.setInterval(Interval); // ms
QObject::connect(&timer, &QTimer::timeout,
[&timer, &tblModel]() { timeout(timer, tblModel); });
timer.start();
// exec. application
return app.exec();
}
I compiled and tested this in Windows 10, VS2013, Qt 5.7:
Upvotes: 3