QTableView, glitch on showing hidden rows - c++

I'm not sure either it's some kind of bug\glitch or I'm doing something wrong.
I have some hidden rows in the table and on click I want them to appear but result you can see on the picture below.
This effect occurs only when slider is scrolled till the end. Glitch disappears if I change focus to another window or when I scroll table up until it goes out of view.
Here is my code:
connect(tableView_, SIGNAL(clicked(const QModelIndex &)), this,
SLOT(onTableClicked(const QModelIndex &)));
for(int i = 0; i < table_->rowCount(); i++)
{
if(i%table_->typesNumber())
tableView_->hideRow(i);
}
...
void DumpsComparisonWindow::onTableClicked(const QModelIndex& index)
{
...
tableView_->showRow(index.row() + i);
...
}
UPDATE: to make it possible to test I have created cut version of my project, you can find it here: https://gitlab.com/JuicyPussy/qtableview_glitch

After showing or hiding any rows make the model notify about the change in the layout to all views. You do that by calling layoutChanged.
void DumpsComparisonWindow::onTableClicked(const QModelIndex& index)
{
...
tableView_->showRow(index.row() + i);
...
table_->layoutChanged(); //add this
}

Related

How to select a specific entire colum of a QTableView

I have a QTableView with three columns like the example below:
| Id | name | ACoord |
I am trying to highlight the entire ACoord column no matter which cell I am clicking on in the ACoord only.
I have tried several examples but nothing is helpful.
The most promising (also from official QT documentation) seems to be setSelectionBehavior(QAbstractItemView::SelectColumns) but didn't work exactly as I need.
Here is the snipped of code:
connect(mTurnIntoExcelData, &QAction::triggered, [&]() {
int row = -1, column = -1;
QString reference;
QString type;
QModelIndex index;
int rowModel = index.row();
SelectionData currentData;
for(int i = 0; i < ui->tableViewLeft->model()->columnCount(); i++)
{
if(ui->tableViewLeft->model()->headerData(i, Qt::Horizontal).toString() == "ACoord") {
column = i;
ui->tableViewLeft->setSelectionBehavior(QAbstractItemView::SelectColumns);
ui->tableViewLeft->setSelectionMode(QAbstractItemView::SingleSelection);
type = "ACoord";
}
The expected result is: I click on any cell of the ACoord and the entire column becomes selectable.
However, the actual result is that if I click on any cell of ACoord column I am not able to select the entire column, but only the single cell.
Thanks for any insight.
I don't know if this is the most elegant way to do it, but I was able to get (what I believe is) the behavior you want by modifying Qt's "FrozenColumn" example program (in $QTDIR/qtbase/examples/widgets/itemviews/frozencolumn) in the following way:
In freezetablewidget.h, add the following declarations inside the private slots: section:
void currentColumnChanged(const QModelIndex &);
void autoSelectMagicColumn();
In freezetablewidget.cpp, add a #include <QTimer> to the includes-section at the top, then add the following lines to the end of the FreezeTableWidget::init() method:
connect(frozenTableView->selectionModel(), SIGNAL(currentColumnChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(currentColumnChanged(const QModelIndex &)));
connect(frozenTableView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(currentColumnChanged(const QModelIndex &))); // optional
... and finally, add the following new lines into freezetablewidget.cpp:
const int magicColumnIndex = 2; // or whichever column you want the auto-selecting to happen on
void FreezeTableWidget::currentColumnChanged(const QModelIndex & mi)
{
const int col = mi.column();
if (col == magicColumnIndex)
{
// Auto-select the entire column! (Gotta schedule it be done later
// since it doesn't work if I just call selectColumn() directly at this point)
QTimer::singleShot(100, this, SLOT(autoSelectMagicColumn()));
}
}
void FreezeTableWidget::autoSelectMagicColumn()
{
// Double-check that the focus is still on the magic column index, in case the user moves fast
if (selectionModel()->currentIndex().column() == magicColumnIndex) frozenTableView->selectColumn(magicColumnIndex);
}
With the above changes, the FrozenColumn example app will auto-select the entire "YDS" column whenever I click on any cell in that column (or navigate to any cell in that column via the arrow keys). Perhaps you can do something similar in your program.

QT Multiple item selection from a list of files and folders

I'm developing a Qt C++ application in Unix and I've been trying to do something similar to what this image shows:
As you can see, there is a list of files and folders and a user can select multiple of them (if a folder is selected, all childs also get selected). I don't really care if the folder/file icons are shown.
I was able to create a list of QDir which stores all the files and folders paths given a root path. The problem is that I don't really know which widgets to use to design the selection panel.
By the way, the lis of QDir is a vector, but it can be easily modified to anything else.
Thanks!
You can try to make proxy model for QFileSystemModel, override flags() with Qt::ItemIsUserCheckable, override setData() and apply the model to QTreeView. Full example can be found at https://github.com/em2er/filesysmodel. This code is just a concept, i have not tested it thoroughly, but you can take some ideas from it. It will look smth like on the screenshot:
.
Also you can combine it with Merged Proxy Model to display multiple starting paths at one view.
You might want to consider the QTreeWidget, or it's a tad more advanced version - QTreeView and an appropriate data model.
As some users suggested, I ended up using QFileSystemModel. I'm gonna give a full description of how I implemented it, in case someone else comes up with this problem and needs a clear response.
First of all, a QFileSystemModel is a file tree without checkboxes, to add them, a new class which extends QFileSystemModel and at least 3 methods must be overriden.
class FileSelector : public QFileSystemModel
{
public:
FileSelector(const char *rootPath, QObject *parent = nullptr);
~FileSelector();
bool setData(const QModelIndex& index, const QVariant& value, int role);
Qt::ItemFlags flags(const QModelIndex& index) const;
QVariant data(const QModelIndex& index, int role) const;
private:
QObject *parent_;
/* checklist_ stores all the elements which have been marked as checked */
QSet<QPersistentModelIndex> checklist_;
};
When creating the model a flag, to indicate that it should have a checkable box, must be set. This is why we will use the flags function:
Qt::ItemFlags FileSelector::flags(const QModelIndex& index) const
{
return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
}
When a click is made in the checkbox, the method setData will be called, with the index of the element that was clicked (not the checkbox itself, but the :
bool FileSelector::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole && index.column() == 0) {
QModelIndexList list;
getAllChildren(index, list); // this function gets all children
// given the index and saves them into list (also saves index in the head)
if(value == Qt::Checked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.insert(list[i]);
// signals that a change has been made
emit dataChanged(list[i], list[i]);
}
}
else if(value == Qt::Unchecked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.remove(list[i]);
emit dataChanged(list[i], list[i]);
}
}
return true;
}
return QFileSystemModel::setData(index, value, role);
}
When dataChanged is signaled or you open a new path of the tree, the data function will be called. Here you have to make sure to only display the checkbox at the first column (next to the filename), and to retrieve the state of the checkbox, to mark it as checked/unchecked.
QVariant FileSelector::data(const QModelIndex& index, int role) const
{
if (role == Qt::CheckStateRole && index.column() == 0) {
if(checklist_.contains(index)) return Qt::Checked;
else return Qt::Unchecked;
}
return QFileSystemModel::data(index, role);
}
The only thing I was not able to accomplish was getting all childs, since the folders must be open to retrieve the childs. So a closed folder won't have any child until you open it.
Hope this can help someone who has the same problem as I did!

Custom delegate Paint persist when editing

I am trying to write custom delegate and custom model as a part of learning Qt.
I made a simple custom model based on QAbstractTableModel. I did not do anything complicated. It only generates the data in its constructor as well as minimally implement the pure virtual function.
I made a custom delegate which display numerical data in terms of bars. I also implemented a spin box as an editor to edit data.
The program works well. I can view, edit and modify data through a QTableView with the delegate set.
But there is a small problem. When I call the editor, the data bar persists, which means I see the data bar at the background and the spin box on top.
Initially, I think it is because the Qt::EditRole in the QAbstractTableModel::data() has not been set properly. But, surprisingly, I find that the Qt::EditRole has never been called.
So, there are two question:
How to remove the data bar when I am having the spin box editor?
Why is the EditRole never been called in my custom model?
Here is part of my code:
My Custom Model:
MyModel::MyModel(QObject* parent):QAbstractTableModel(parent)
{
for (int i = 0; i < 10; ++i)
localData.push_back(i*i);
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
switch(role)
{
case Qt::EditRole:
qDebug() << "EditRole"; //Never Print Out
return 0;
case Qt::DisplayRole :
if (index.column() == 0)
return (index.row());
if (index.column() == 1)
return (localData.at(index.row()));
default:
return QVariant();
}
}
My Custom Delegate:
void MyDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
painter->save();
painter->setPen(Qt::red);
painter->setBrush(Qt::red);
double factor = 0;
if (index.data().toDouble() > 100)
factor = 1;
else
factor = index.data().toDouble() / (double) (100.0);
painter->drawRect(option.rect.x()+5, option.rect.y()+3, (option.rect.width()-10)*factor, option.rect.height()-6);
painter->restore();
}
QWidget* MyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSpinBox* box = new QSpinBox(parent);
box->setMinimum(0);
box->setMaximum(100);
return box;
}
Try to set setAutoFillBackground(true) for your view
Editrole is not called because your custom editor does not query the model for that data. You don not set any value for your spin box. Try to set it as:
box->setValue(model.data(Qt::EditRole));
in the MyDelegate::createEditor() function.

QListView with millions of items slow with keyboard

I'm using a QListView with a custom model derived from QAbstractItemModel. I have on the order of millions of items. I have called listView->setUniformItemSizes(true) to prevent a bunch of layout logic from being called when I'm adding items to the model. So far, everything works as expected.
The problem is that using the keyboard to navigate the list is slow. If I select an item in the list, then press up/down, the selection moves fast until the selection needs to scroll the list. Then it becomes extremely laggy. Pressing page-up or page-down is also very laggy. The problem seems to be when an item is selected (aka the "current item") with the keyboard and the list is also scrolled up/down.
If I use the mouse, navigating the list is fast. I can use the mouse wheel, which is fast. I can drag the scroll bar up/down as fast as I want--from the top of the list to the bottom--and the list view updates wickedly fast.
Any ideas on why the combination of changing selections and scrolling the list is so slow? Is there a viable work-around?
Update 9/9/15
In order to better illustrate the issue, I'm providing amplifying information in this update.
Performance Issues with KEYBOARD + SCROLLING
This is mostly a performance question, although it does tie in with the user experience (UX) somewhat. Check out what happens as I use the keyboard to scroll through a QListView:
Notice the slow-down near the bottom? This is the focal point of my question. Let me explain how I am navigating the list.
Explanation:
Starting at the top, the first item in the list is selected.
Pressing and holding the down arrow key, the current item (selection) is changed to the next item.
Changing selection is fast for all of the items that are currently in view.
As soon as the list needs to bring the next item into view, the selection rate slows down significantly.
I expect that the list should be able to scroll as fast as the typematic rate of my keyboard--in other words, the time it takes to select the next item should not slow down when the list is scrolled.
Fast Scrolling with MOUSE
Here's what it looks like when I use the mouse:
Explanation:
Using the mouse, I select the scroll bar handle.
Quickly dragging the scroll bar handle up and down, the list is scrolled accordingly.
All movements are extremely fast.
Note that no selections are being made.
This proves two main points:
The model is not the problem. As you can see, the model has no problem whatsoever performance-wise. It can deliver the elements faster than they can be displayed.
Performance is degraded when selecting AND scrolling. The "perfect storm" of selecting and scrolling (as illustrated by using the keyboard to navigate through the list) causes the slowdown. As a result, I surmise that Qt is somehow doing a lot of processing when selections are being made during scrolling that aren't normally performed.
Non-Qt Implementation is FAST
I want to point out that my issue seems to be specific to Qt.
I have already implemented this type of thing before using a different framework. What I am trying to do is within the scope of model-view theory. I can do exactly what I am describing at blazing fast speeds using juce::ListBoxModel with a juce::ListBox. It's stupid fast (plus, there's no need to create a duplicate index such as a QModelIndex for every single item when each item already has a unique index). I get that Qt needs a QModelIndex for each item for its model-view architecture, and although I don't like the overhead cost, I think I get the rational and I can live with it. Either way, I don't suspect that these QModelIndexes are what is causing my performance slow-down.
With a JUCE implementation, I can even use the page-up & page-down keys to navigate the list, and it just blazes through the list. Using the Qt QListView implementation, it chugs along and is laggy, even with a release build.
A model-view implementation using the JUCE framework is extremely fast. Why is the Qt QListView implementation such a dog?!
Motivating Example
Is it hard to imagine why you'd need so many items in a list view? Well, we've all seen this kind of thing before:
This is the Visual Studio Help Viewer index. Now, I haven't counted all of the items--but I think we'd agree that there are a lot of them! Of course to make this list "useful," they added a filter box that narrows down what is in the list view according to an input string. There aren't any tricks here. It's all practical, real-world stuff we've all seen for decades in desktop applications.
But are there millions of items? I'm not sure it matters. Even if there were "only" 150k items (which is roughly accurate based on some crude measurements), it's easy to point out that you have to do something to make it useable--which is what a filter will do for you.
My specific example uses a list of German words as a plain text file with slightly more than 1.7 million entries (including inflected forms). This is probably only a partial (but still significant) sample of words from the German text corpus that was used to assemble this list. For linguistic study, this is a reasonable use case.
Concerns about improving the UX (user experience) or filtering are great design goals, but they are out of the scope of this question (I'll certainly address them later in the project).
Code
Want a code example? You got it! I'm not sure how useful it will be; it's as vanilla as it gets (about 75% boilerplate), but I suppose it will provide some context. I realize that I'm using a QStringList and that there is a QStringListModel for this, but the QStringList that I'm using to hold the data is a placeholder--the model will eventually be somewhat more complicated, so in the end, I need to use a custom model derived from QAbstractItemModel.
//
// wordlistmodel.h ///////////////////////////////////////
//
class WordListModel : public QAbstractItemModel
{
Q_OBJECT
public:
WordListModel(QObject* parent = 0);
virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex& index) const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex & parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
public slots:
void loadWords();
signals:
void wordAdded();
private:
// TODO: this is a temp backing store for the data
QStringList wordList;
};
//
// wordlistmodel.cpp ///////////////////////////////////////
//
WordListModel::WordListModel(QObject* parent) :
QAbstractItemModel(parent)
{
wordList.reserve(1605572 + 50); // testing purposes only!
}
void WordListModel::loadWords()
{
// load items from file or database
// Due to taking Kuba Ober's advice to call setUniformItemSizes(true),
// loading is fast. I'm not using a background thread to do
// loading because I was trying to visually benchmark loading speed.
// Besides, I am going to use a completely different method using
// an in-memory file or a database, so optimizing this loading by
// putting it in a background thread would obfuscate things.
// Loading isn't a problem or the point of my question; it takes
// less than a second to load all 1.6 million items.
QFile file("german.dic");
if (!file.exists() || !file.open(QIODevice::ReadOnly))
{
QMessageBox::critical(
0,
QString("File error"),
"Unable to open " + file.fileName() + ". Make sure it can be located in " +
QDir::currentPath()
);
}
else
{
QTextStream stream(&file);
int numRowsBefore = wordList.size();
int row = 0;
while (!stream.atEnd())
{
// This works for testing, but it's not optimal.
// My real solution will use a completely different
// backing store (memory mapped file or database),
// so I'm not going to put the gory details here.
wordList.append(stream.readLine());
++row;
if (row % 10000 == 0)
{
// visual benchmark to see how fast items
// can be loaded. Don't do this in real code;
// this is a hack. I know.
emit wordAdded();
QApplication::processEvents();
}
}
if (row > 0)
{
// update final word count
emit wordAdded();
QApplication::processEvents();
// It's dumb that I need to know how many items I
// am adding *before* calling beginInsertRows().
// So my begin/end block is empty because I don't know
// in advance how many items I have, and I don't want
// to pre-process the list just to count the number
// of items. But, this gets the job done.
beginInsertRows(QModelIndex(), numRowsBefore, numRowsBefore + row - 1);
endInsertRows();
}
}
}
QModelIndex WordListModel::index(int row, int column, const QModelIndex& parent) const
{
if (row < 0 || column < 0)
return QModelIndex();
else
return createIndex(row, column);
}
QModelIndex WordListModel::parent(const QModelIndex& index) const
{
return QModelIndex(); // this is used as the parent index
}
int WordListModel::rowCount(const QModelIndex& parent) const
{
return wordList.size();
}
int WordListModel::columnCount(const QModelIndex& parent) const
{
return 1; // it's a list
}
QVariant WordListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
else if (role == Qt::DisplayRole)
{
return wordList.at(index.row());
}
else
{
return QVariant();
}
}
//
// mainwindow.h ///////////////////////////////////////
//
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void updateWordCount();
private:
Ui::MainWindow *ui;
WordListModel* wordListModel;
};
//
// mainwindow.cpp ///////////////////////////////////////
//
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->listView->setModel(wordListModel = new WordListModel(this));
// this saves TONS of time during loading,
// but selecting/scrolling performance wasn't improved
ui->listView->setUniformItemSizes(true);
// these didn't help selecting/scrolling performance...
//ui->listView->setLayoutMode(QListView::Batched);
//ui->listView->setBatchSize(100);
connect(
ui->pushButtonLoadWords,
SIGNAL(clicked(bool)),
wordListModel,
SLOT(loadWords())
);
connect(
wordListModel,
SIGNAL(wordAdded()),
this,
SLOT(updateWordCount())
);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateWordCount()
{
QString wordCount;
wordCount.setNum(wordListModel->rowCount());
ui->labelNumWordsLoaded->setText(wordCount);
}
As noted, I've already reviewed and taken Kuba Ober's advice:
QListView takes too long to update when given 100k items
My question is not a duplicate of that question! In the other question, the OP was asking about loading speed, which as I've noted in my code above, is not a problem due to the call to setUniformItemSizes(true).
Summary Questions
Why is navigating a QListView (with millions of items in the model) using the keyboard so slow when the list is scrolled?
Why does the combination of selecting and scrolling items cause a slow-down?
Are there any implementation details that I am missing, or have I reached a performance threshold for QListView?
1. Why is navigating a QListView (with millions of items in the model)
using the keyboard so slow when the list is scrolled?
Because when you navigate through your list using the keyboard, you enter the internal Qt function QListModeViewBase::perItemScrollToValue, see stack:
Qt5Widgetsd.dll!QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize, QAbstractItemView::ScrollHint hint, Qt::Orientation orientation, bool wrap, int itemExtent) Ligne 2623 C++
Qt5Widgetsd.dll!QListModeViewBase::verticalScrollToValue(int index, QAbstractItemView::ScrollHint hint, bool above, bool below, const QRect & area, const QRect & rect) Ligne 2205 C++
Qt5Widgetsd.dll!QListViewPrivate::verticalScrollToValue(const QModelIndex & index, const QRect & rect, QAbstractItemView::ScrollHint hint) Ligne 603 C++
Qt5Widgetsd.dll!QListView::scrollTo(const QModelIndex & index, QAbstractItemView::ScrollHint hint) Ligne 575 C++
Qt5Widgetsd.dll!QAbstractItemView::currentChanged(const QModelIndex & current, const QModelIndex & previous) Ligne 3574 C++
Qt5Widgetsd.dll!QListView::currentChanged(const QModelIndex & current, const QModelIndex & previous) Ligne 3234 C++
Qt5Widgetsd.dll!QAbstractItemView::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) Ligne 414 C++
Qt5Cored.dll!QMetaObject::activate(QObject * sender, int signalOffset, int local_signal_index, void * * argv) Ligne 3732 C++
Qt5Cored.dll!QMetaObject::activate(QObject * sender, const QMetaObject * m, int local_signal_index, void * * argv) Ligne 3596 C++
Qt5Cored.dll!QItemSelectionModel::currentChanged(const QModelIndex & _t1, const QModelIndex & _t2) Ligne 489 C++
Qt5Cored.dll!QItemSelectionModel::setCurrentIndex(const QModelIndex & index, QFlags<enum QItemSelectionModel::SelectionFlag> command) Ligne 1373 C++
And this function does:
itemExtent += spacing();
QVector<int> visibleFlowPositions;
visibleFlowPositions.reserve(flowPositions.count() - 1);
for (int i = 0; i < flowPositions.count() - 1; i++) { // flowPositions count is +1 larger than actual row count
if (!isHidden(i))
visibleFlowPositions.append(flowPositions.at(i));
}
Where flowPositions contains as many items as your QListView, so this basically iterates through all your items, and this will definitely take a while to process.
2. Why does the combination of selecting and scrolling items cause a slow-down?
Because "selecting and scrolling" makes Qt call QListView::scrollTo (to scroll the view to a specific item) and this is what ends up calling QListModeViewBase::perItemScrollToValue. When you scroll using the scroll bar, the system does not need to ask the view to scroll to a specific item.
3. Are there any implementation details that I am missing, or have I reached a performance threshold for QListView?
I'm afraid you are doing the things right. This is definitely a Qt bug. A bug report must be done to hope having this fixed in later releases. I submitted a Qt bug here.
As this code is internal (private data classes) and not conditionnal to any QListView setting, I see no way to fix it except by modifying and recompiling the Qt source code (but I don't know exactly how, this would require more investigation). The first function overidable in the stack is QListView::scrollTo but I doubt it would be easy to oevrride it without calling QListViewPrivate::verticalScrollToValue...
Note: The fact that this function goes through all items of the view was apparently introduced in Qt 4.8.3 when this bug was fixed (see changes). Basically, if you don't hide any items in your view, you could modify Qt code as below:
/*QVector<int> visibleFlowPositions;
visibleFlowPositions.reserve(flowPositions.count() - 1);
for (int i = 0; i < flowPositions.count() - 1; i++) { // flowPositions count is +1 larger than actual row count
if (!isHidden(i))
visibleFlowPositions.append(flowPositions.at(i));
}*/
QVector<int>& visibleFlowPositions = flowPositions;
Then you'll have to recompile Qt and I'm pretty sure this will fix the issue (not tested however). But then you'll see new problems if you one day hide some items...to support filtering for instance!
Most likely the right fix would have been to have the view maintain both flowPositions and visibleFlowPositions to avoid creating it on the fly...
I have made the following test:
First of all i create a class to check in the calls:
struct Test
{
static void NewCall( QString function, int row )
{
function += QString::number( row );
map[ function ]++;
}
static void Summary( )
{
qDebug() << "-----";
int total = 0;
QString data;
for( auto pair : map )
{
data = pair.first + ": " + QString::number( pair.second );
total += pair.second;
qDebug( ) << data;
}
data = "total: " + QString::number( total ) + " calls";
qDebug() << data;
map.clear();
}
static std::map< QString, int > map;
};
std::map<QString,int> Test::map;
Then I insert a call to NewCall in index, parent and data methods of WordListModel. Finally i add a QPushButton in the dialog, the clicked signal is linked to a method which call to Test::Summary.
The steps of the test are the next:
Select the last showed item of the list
Press the Summary button to clear the calling list
With tab key select the list view again
Perform a scroll with the direction keys
Press Summary button again
The printed list shows the problem. QListView widget makes a big number of calls. It seems the widget is reloading all the data from the model.
I don't know if it can be improved but you can't do anything but filter the list to limit the number of items to show.
Unfortunately, I believe that you can't do much about this.
We don't have much control over widgets.
Although you can avoid that issue by using ListView instead.
If you try my quick example below you'll notice how fast it can be even using delegates which is costly.
Here is the example:
Window{
visible: true
width: 200
height: 300
property int i: 0;
Timer {
interval: 5
repeat: true
running: true
onTriggered: {
i += 1
lv.positionViewAtIndex(i, ListView.Beginning)
}
}
ListView {
id:lv
anchors.fill: parent
model: 1605572
delegate: Row {
Text { text: index; width: 300; }
}
}
}
I put a Timer to simulate the scrolling, but of course you can turn on or off that timer depending on whether keys are pressed as well as changing i += 1 by i += -1 if ▲ is pressed instead of ▼. You'd have to add overflow and underflow checks too.
You can also choose the scrolling speed by changing interval of Timer. Then it's just a matter of modifying the selected element's color etc. to show it's selected.
On top of which you can use cacheBuffer with ListView to cache more elements but I don't think it is necessary.
If you want to use QListView anyway take a look at this example: http://doc.qt.io/qt-5/qtwidgets-itemviews-fetchmore-example.html
Using the fetch method allow to keep performance even with big datasets. It allows you to fill the list as you scroll.

QSortFilterProxyModel and lazily populated treeviews

I have implemented a lazily populated treeview by subclassing QAbstractItemModel. The implementation looks something like:
https://gist.github.com/gnufied/db9c4d805e2bb24d8c23
(I am not pasting code inline, so as to mess with messaging)
It is basically a tree representation of hierarchical data stored in table. Now, I want users to be able to sort the rows based on columns. Where columns are, "count" or "reference count". These values are basically integers.
The implementation on its own works, until I throw in QSortFilterProxyModel and I start to get lots of empty rows in the view. The hard problem is, this tends to happen only when I have lots of rows (like thousands or so).
The code for implementing sorting proxy is:
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
ui->treeView->setModel(proxyModel);
ui->treeView->setSortingEnabled(true);
I have subclassed QSortFilterProxyModel class and subclass implementation is really simple:
https://gist.github.com/gnufied/115f1a4fae3538534511
The documentation does say -
"This simple proxying mechanism may need to be overridden for source models with more complex behavior; for example, if the source model provides a custom hasChildren() implementation, you should also provide one in the proxy model."
But beyond that, I am not sure - what I am missing.
So, I think I have found the solution and the fix appears to be reimplementing fetchMore in proxy model subclass, because inserted rows reported by source model, do not match place where rows actually exist in view (Rows in view are owned by proxy model), so this seems to have fixed it for me:
#include "sortobjectproxymodel.h"
SortObjectProxyModel::SortObjectProxyModel(QObject *parent) :
QSortFilterProxyModel(parent)
{
}
bool SortObjectProxyModel::hasChildren(const QModelIndex &parent) const
{
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->hasChildren(sourceIndex);
}
int SortObjectProxyModel::rowCount(const QModelIndex &parent) const
{
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->rowCount(sourceIndex);
}
bool SortObjectProxyModel::canFetchMore(const QModelIndex &parent) const
{
if(!parent.isValid())
return true;
else {
const QModelIndex sourceIndex = mapToSource(parent);
return sourceModel()->canFetchMore(sourceIndex);
}
}
void SortObjectProxyModel::fetchMore(const QModelIndex &parent)
{
if (parent.isValid() && parent.column() == 0) {
int row = parent.row();
int startRow = row + 1 ;
const QModelIndex sourceIndex = mapToSource(parent);
RBKit::HeapItem *item = static_cast<RBKit::HeapItem *>(sourceIndex.internalPointer());
if (!item->childrenFetched) {
qDebug() << "Insert New Rows at :" << startRow << " ending at : " << startRow + item->childrenCount();
beginInsertRows(parent, startRow, startRow + item->childrenCount());
item->fetchChildren();
endInsertRows();
}
}
}
Thank you for responding. At this point I really don't care about resorting rows that were lazy loaded (when a node was expanded), so I went ahead and disabled sortingEnabled and disabled dynamicSortFiltertoo.
The new code looks like:
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->sort(2, Qt::DescendingOrder);
proxyModel->setDynamicSortFilter(false);
ui->treeView->setModel(proxyModel);
That still leaves with empty rows though.
In my oppinion You don't need to subclass QSortFilterProxyModel for sorting on the top layer. If sortingEnabled == true for your view then the view will perform sorting on the proxy model, which is not desirable as the model should sort itself. What you need is to to call proxyModel->sort(desiredColumn) and that will display your model sorted in the view without altering your data. By default the QSortFilterProxyModel has its dynamicSortFilter property on, which will cause the proxy model to automatically re-sort when data changes or row is inserted or removed. I didn't see emitting dataChanged signal anywhere in your HeapDataModel, so maybe that could be a hint for you to get dynamically sorted rows. If you need to sort subitems then it goes little more complicated and then maybe you'll need to subclasss QSortFilterProxyModel. These model-view abstractions are hard to learn but once you get it you can do miracles rapidly.
rootItem = RBKit::SqlConnectionPool::getInstance()->rootOfSnapshot(snapShotVersion);
model = new RBKit::HeapDataModel(rootItem, this);
proxyModel = new SortObjectProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->sort(column);
ui->treeView->setModel(proxyModel);