I am not sure how to go about this big data problem. Instead of using a Table Widget, it is suggested to use a Table View for big data for speed. The data will be at most 200000 rows by 300 columns of strings. Also, it might have gaps in the structure. For the View, it is needed to define a Model. I was not able to do this yet and I wonder the following:
Will it really be faster to use a View if the user only wants replace some values in a column, and inspect the data? Is a QList a smart choice given speed needs, or are there better options? And how to implement this as a Model?
A QTableWidget uses its own Model as backend (container for its data). One can't (or it is not intented that one should) customize a QTableWidget's model. Each cell in a QTableWidget is represented by a QTableWidgetItem. They are written to solve general problems and are not that fast.
When one decides to use a QTableView with it's own model deriving from QAbstractTableModel as backend, one can speed things up allot. I've found that one of the things that took the most processing time was the size calculation of rows. I overloaded sizeHintForRow in my table like this:
int MyTable::sizeHintForRow ( int row ) const
{
//All rows have the same height in MyTable, hence we use the height of the
// first row for performance reasons.
if( cachedRowSizeHint_ == -1 )
{
cachedRowSizeHint_ =
model()->headerData( 0, Qt::Vertical, Qt::SizeHintRole ).toSize().height();
}
return cachedRowSizeHint_;
}
Implementing a simple table model is quite easy (Qt Help files are adequate...). One can start by providing the data for the minimum roles (Edit and Display). TableModel data can always be modified by using QIdentityProxyModels where necessary. If your models don't work, post your code. Remember to not return invalid data for roles e.g:
QVariant IO_PluginMngr::data(const QModelIndex & index, int role) const
{
if( index.isValid() )
{
if( role == Qt::DisplayRole)
{
return sequence_[index.row()][index.column()];
}
}
return QVariant();
}
Returning for example erroneous data for especially SizeHintRole will cause havoc in the views.
I've noticed that one should, when implementing models, be diligent in calling begin/endInsertRows when appropriate, otherwise persistent model indexes don't work.
We've written more complicated tables where the data is modified in threads in the background, and swapped out prior to updating the table. One of the areas in which one could have speed improvement, is to never order the data in your model, but rather use an ordered reference sequence that references the real (unordered) data. We have tables that have about a thousand rows and 50 columns that get updated once a second (with many views), and the processor our processor is sitting a about 20%, but the greatest speed improvement was the caching of the sizeHint.
Related
I have a QTableWidget.
I have a problem with sorting files and folders.I have tried out the following meethods:
ui.table->horizontalHeader()->sortIndicatorOrder();
ui.table->sortItems(columnNumber, Qt::AscendingOrder);
Suppose my first column consists of files and folders.How do I ensure that, folders always remain on top and files remain after the folders?
Do I need to implement some kind of boolean operator " < " for QTableWidget class.
Thanks in Advance!
You should not be using QTableWidget for files and folders, because the number of files and folders is not known. QTableWidget's performance with a huge number of items is bad.
You should learn how to do Model/View programming, and in the link there's an example on how to deal with files.
With model view, you have a view that takes information from a model. The model can be (almost) arbitrarily huge, and the view doesn't care because it only loads what it's viewing. This is unlike QTableWidget which loads everything all the time, and causes performance hick-ups.
Now regarding your question about sorting, you should use QSortFilterProxyModel, which you set as a model for your view, and you set inside QSortFilterProxyModel a source model. The proxy works as a middle-man between your view and the model, and can sort and filter the information before passing it. That's the right way of sorting tables in Qt.
Yes, you can overload <() operator and implement a sorting logic.
class CustomTableWidgetItem : public QTableWidgetItem
{
public:
CustomTableWidgetItem(const QString txt = QString("0"))
:QTableWidgetItem(txt)
{
setTextAlignment( Qt::AlignCenter ); /* center the text */
}
bool operator <(const QTableWidgetItem& other) const
{
/* Implement logic here */
/* e.g if(column == file) return true; */
/* else return false; */
}
};
Then use new CustomTableWidgetItem instead of new QTableWidgetItem. Read my other post to see how it's done on a widget.
Performance note: If you have many rows use the model view approach, if you just deal with a couple of rows QTableWidget should be fine. If you notice the sorting will take a couple of seconds it's time to switch.
I am interested to know how would views behave in cases when the main data model with incremental data fetching is behind a proxy or chain of proxies.
How proxy which does item rearrangements like ungrouping proxy (example: http://lynxline.com/jongling-qt-models/) should implement support of fetchMore()/canFetchMore()?
Examining QSortFilterProxyModel's source allows me to conclude that:
QSortFilterProxyModel allows access to already fetched rows. Calling rowCount, data, etc. will behave like there is no more data in the source table.
QSortFilterProxyModel (and QAbstractProxyModel by default) routes canFetchMore and fetchMore calls to according methods of the source model.
It means that when you scroll down a view with a QSortFilterProxyModel populated from a dynamically fetched table, it will be dynamically populated with new data from source model. However you can't be sure that new items will be added to the end. Instead, when scrolling down the table, its data can completely change. New rows can be added anywhere depending on current sorting and filtering settings.
When no additional support of canFetchMore and fetchMore is given, all proxy models (including the linked UngroupProxyModel class) will behave like that by default.
QTableView behaves buggy displaying such models. For example, if the source model has rows -2; -3; 2; 3 (let ; be a row separator in my answer), the view without sorting would usually display something like -2; -3; 2; 3; empty row; empty row. Empty rows are displayed in the bottom of the table. When user scrolls down enough close to these items, the view requests fetching more data. However, when sorting is enabled, the view shows -3; -2; empty row; empty row; 2; 3, i.e. it moves empty invalid rows to the middle or to the top of the table. It doesn't affect functionality but looks awkward.
However, all aforementioned issues are implementation dependent. Refer to the code and documentation of the used model classes for more details. I think it's possible to implement proxy models that act however you like.
Also, it would be reasonable in some cases to fetch source model completely before displaying sorted data in the table. Proper sorting or joining is impossible while not all data has been fetched. If you fetched the source model data (e.g. while(model->canFetchMore()) { model->fetchMore(); }) then proxy models will behave exactly like the model is not dynamically populated.
For some (well, performance) reason, Qt's "model" classes only fetch 256 rows from the database so if you want to append the row to the end of the recordset, you, apparently, must do something along the lines of
while (model->canFetchMore()) {
model->fetchMore();
}
This does work and when you do model->insertRow(model->rowCount()) afterwards, the row is, indeed, appended after the last row of the recorset.
There are various other problems related to this behaviour, such as when you insert or remove rows from the model, the view that renders it gets redrawn with only 256 rows showing and you must manually make sure, that the missing rows are fetched again.
Is there a way to bypass this behaviour altogether? My model is very unlikely to display more, than, say, 1000 rows, but getting it to retrieve those 1000 rows seems to be a royal pain. I understand, that this is a great performance optimization if you have to deal with larger recordsets, but for me it is a burden rather than a boon.
The model needs to be writable so I can't simply use QSqlQueryModel instead of QSqlRelationalTableModel.
From the QSqlTableModel documentation:
bool QSqlTableModel::insertRecord ( int row, const QSqlRecord & record )
Inserts the record after row. If row is negative, the record will be appended to the end.
Calls insertRows() and setRecord() internally.
Returns true if the row could be inserted, otherwise false.
See also insertRows() and removeRows().
I've not tried yet, but I think that it's not necessary to fetch the complete dataset to insert a record at the end.
I have a QTableView thats using a model i made which extends the QAbstractTableModel. The model uses a
QList< QVector<QString> * >
as the collection. The table view is used to display logging messages from the application so the collection will eventually get extremely large... After it inserts a few thousand rows i notice the table view starts to slow down a lot and eventually the view freezes for a few seconds before refreshing.. Is it the type of collection im using thats making it slow down soo much? Is there a better way to store the data thats being inserted? Does the QTableView support a large amount of data?
Edit
Posted code on Qt forumn:
http://www.qtforum.org/article/37326/qttableview-slows-down-when-a-lot-of-data-is-inserted.html
I have successfully used QTableView displaying ~10000 rows so QTableView is capable of supporting it but your collection leaves to be desired.
QList is very expensive to insert in the middle since you have to reallocate everything sitting below the index you are trying to insert at granted you are only shifting pointers around but still.
Normally for data storage I would use an std::vector< data_struct * > rather then using vectors of strings. QVariant is quite capable of presenting integers and other types so you don't have to do the conversion up front.
The best suggestion that I can come up with would be to run gprof or a similar tool to time where exactly you are spending time and then address that particular piece.
Greetings,
I've been writing some nasty code to support the undo/redo of deletion of an arbitrary set of objects from my model. I feel like I'm going about this correctly, as all the other mutators (adding/copy-pasting) are subsets of this functionality.
The code is nastier than it needs to me, mostly because the only way to mutate the model involves calling beginInsertRows/beginRemoveRows and removing the rows in a range (just doing 1 row at a time, no need to optimize "neighbors" into a single call yet)
The problem with beginInsertRows/beginRemoveRows is that removal of a row could affect another QModelIndex (say, one cached in a list). For instance:
ParentObj
->ChildObj1
->ChildObj2
->ChildObj3
Say I select ChildObj1 and ChildObj3 and delete them, if I remove ChildObj1 first I've changed ChildObj3's QModelIndex (row is now different). Similar issues occur if I delete a parent object (but I've fixed this by "pruning" children from the list of objects).
Here are the ways I've thought of working around this interface limitation, but I thought I'd ask for a better one before forging ahead:
Move "backwards", assuming a provided list of QModelIndices is orderered from top to bottom just go from bottom up. This really requires sorting to be reliable, and the sort would probably be something naive and slow (maybe there's a smart way of sorting a collection of QModelIndexes? Or does QItemSelectionModel provide good (ordered) lists?)
Update other QModelIndeces each time an object is removed/added (can't think of a non-naive solution, search the list, get new QModelIndeces where needed)
Since updating the actual data is easy, just update the data and rebuild the model. This seems grotesque, and I can imagine it getting quite slow with large sets of data.
Those are the ideas I've got currently. I'm working on option 1 right now.
Regards,
Dan O
Think of beginRemoveRows/endRemoveRows, etc. as methods to ask the QAbstractItemModel base class to fix up your persistent model indexes for you instead of just a way of updating views, and try not to confuse the QAbstractItemModel base class in its work on those indexes. Check out http://labs.trolltech.com/page/Projects/Itemview/Modeltest to exercise your model and see if you are keeping the QAbstractItemModel base class happy.
Where QPersistentModelIndex does not help is if you want to keep your undo/redo data outside of the model. I built a model that is heavily edited, and I did not want to try keeping everything in the model. I store the undo/redo data on the undo stack. The problem is that if you edit a column, storing the persistent index of that column on the undo stack, and then delete the row holding that column, the column's persistent index becomes invalid.
What I do is keep both a persistent model index, and a "historical" regular QModelIndex. When it's time to undo/redo, I check if the persistent index has become invalid. If it has, I pass the historical QModelIndex to a special method of my model to ask it to recreate the index, based on the row, column, and internalPointer. Since all of my edits are on the undo stack, by the time I've backed up to that column edit on the undo stack, the row is sure to be there in the model. I keep enough state in the internalPointer to recreate the original index.
I would consider using a "all-data" model and a filter proxy model with the data model. Data would only be added to the all-data model, and never removed. That way, you could store your undo/redo information with references to that model. (May I suggest QPersistentModelIndex?). Your data model could also track what should be shown, somehow. The filter model would then only return information for the items that should be shown at a given time.