QTableWidget resizeRowsToContents very SLOW - performance

I've a QTableWidget that contains over 200.000 rows and 8 columns.
The columns has a fixed size.
The rows has a variable size.
Adding the items to the table is fast (few seconds).
Then the call of resizeRowsToContents() takes almost 60/120 seconds! I see that this call is single thread! (Only one core works).
Can I force MultiThreading ?
How Can I speedup the row resizing ?
Thank you,
Salvo

One thing you might try is to update the QTableWidget row-by-row using QTableWidget::resizeRowToContents(Note: Row rather than Rows) with the updates being interleaved with other events in the queue. Overall the process would still take the same amount of time but your GUI would remain responsive during the process.
Firstly, take advantage of the fact that a QTimer with a zero timeout will emit its timeout signal whenever the event queue becomes empty. So code such as...
QTimer::singleShot(0, &my_callback);
will effectively wait until the event queue is empty and then invoke my_callback. If my_callback calls the same line of code then you have a function that automatically invokes itself during the next idle period.
Now write the function...
void resize_row (QTableWidget *view, int row, int count = 1)
{
/*
* Resize rows `row' -> `row + count - 1'
*/
for (int todo = count; row < view->rowCount() && todo--; ++row) {
view->resizeRowToContents(row);
}
/*
* If there are still rows remaining then reschedule.
*/
if (row < view->rowCount()) {
QTimer::singleShot(0, [=](){ resize_row(view, row, count); });
}
}
This will invoke view->resizeRowToContents(...) on all rows in the range [row, row + count). If the row index is still less that the row count it will schedule itself for the next idle period with updated parameters.
Now, replace...
table_widget->resizeRowsToContents();
with...
QTimer::singleShot(0, [table_widget, row = 0](){ resize_row(table_widget, row, 10); });
Seems to work fine in the basic tests I've done.

Resize has to re-calculate, each row hight and adjust. I would not recommend having this many rows in a single QTableWidget. I would go for a pagination technic or some dynamic loading functionality for this long list.

If you want more speed use QAbstractTableModel instead of QTableWidget and override the required functions. This is a simple and good example:
https://doc.qt.io/qt-5/modelview.html

Related

JqGrid - updating the values in all data cells

My JqGrid looks like this:
Staff
Room 1
Room 2
Jim
240
120
Dave
480
240
The staff and rooms are obtained from tables and are unknown in number at runtime. The figures (data) represent the total time spent by each staff in each room and are in minutes. I have all the above working.
All I want to do now is to iterate over all the data entries and change minutes (Eg: 240) to hours and minutes (EG 4h:0m). I'm ok with doing the math for the conversion, it's the looping over the cells and the reading and updating (just the displayed value) that has defeated me.
This is my code so far for the looping:
var $grid = jQuery('#statstab1grid')
var rows = $grid[0].rows
var crows = rows.length
var irow, row, cellsofrow
for (irow = 1; irow < crows; irow++)
{
row = rows[irow];
cellsofrow = row.cells;
alert('$(cellsofrow[0]).text() is ' + $(cellsofrow[0]).text())
alert('$(cellsofrow[1]).text() is ' + $(cellsofrow[1]).text())
}
The first alert outputs Jim then Dave, the second alert outputs nothing,
just the prompt. Even if I managed to access the data values, how would I write back to the grid the modified values?
It is good to post which version of jqGrid is used. This one is the important part.
The code you posted and your comments that nothing is alerted for cell index 1, can tell me that maybe you have a hidden field in your colModel, which value is empty. In this case it would be good to post your entire grid setup.
To the problem - you have a lot of options to do this conversion.
You can use custom formatter - more about this you can find here. This method is preferred.
You can use getRowData (without parameter) to get all the data in the grid and use setRowData to update the values. Be a careful with this method if you have a lot of data in the grid - it will be slowly in this case. See docs for grid methods
If your data is local (array) you can recalculate it, before to put it into the grid

HandsonTable not rendering all rows

HandsonTable is not rendering all rows - it loads only part of all rows. But when I do Ctrl+A and paste into Excel I see all the rows. Why is Handsontable not displaying all the rows?
<hot-table col-headers="true" row-headers="true" datarows="data" context-menu width="1080">
<hot-column ng-repeat="column in columns" data="{{column.data}}"></hot-column>
</hot-table>
To render all rows, just set renderAllRows: true
The current answer does not answer the original question.
Handsontable does not render all cells at once because it is designed to be efficient for very large data sets. It does this using virtual rendering, dynamically modifying the DOM to include only the cells at the scroll position.
The rows virtual rendering can be disabled by setting renderAllRows: true, as described in the docs: "If typed true then virtual rendering mechanism for handsontable will be disabled." Although it will then not be as efficient for large data sets.
You can also change the number of pre-rendered rows and columns instead of rendering them all. From the performance tips,
You can explicitly specify the number of rows and columns to be rendered outside of the visible part of the table. In some cases you can achieve better results by setting a lower number (as less elements get rendered), but sometimes setting a larger number may also work well (as less operations are being made on each scroll event). Tweaking these settings and finding the sweet spot may improve the feeling of your Handsontable implementation.
This is done by setting viewportRowRenderingOffset and viewportColumnRenderingOffset in the handsontable options. These are by default set to auto which lets handsontable try to find the best value, but may be provided an integer value (e.g. viewportRowRenderingOffset: 70, viewportColumnRenderingOffset: 70).
I had the same problem (using HandsOnTable 6.2.1 and the old AngularJS) and customers would start complaining about not being sure if they were at the end of the table or not.
I was able to create two buttons linked to the functions 'scrollToBeginning' and 'scrollToEnd'. This way the user is sure to be at the last line. Three things specific about my answer:
I expose the functions to the DOM using $scope;
I have an object 'goToLine' holding 3 properties (scrollingToEnd: boolean, row: number, col: number), it is used in other functions not posted here;
I have a list of ID referencing HandsOnTable objects stored in $scope.hots.
Here is my raw solution, feel free to adapt / enhance:
$scope.stopScrollingToEnd = function () {
$scope.goToLine.scrollingToEnd = false;
};
$scope.scrollToBeginning = function (goToLine) {
$scope.stopScrollingToEnd();
const hot = $scope.hots[goToLine.id];
hot.scrollViewportTo(0, 0);
};
/**
* Scroll to the end of the List Element.
* We need this functionality because of a bug in HandsOnTable related to its Virtualization process.
* In some cases (complex table), when manually scrolling, the max row is wrong, hence causing major confusion for the user.
* #param {*} goToLine
* #returns
*/
$scope.scrollToEnd = function (goToLine) {
// We scroll to the first line before going to the last to avoid the bug and being sure we get to the last line
$scope.scrollToBeginning(goToLine);
const hot = $scope.hots[goToLine.id];
var numberOfRows = hot.countRows();
// This variable is used to repeat the scrollViewportTo command.
// It is built using the length of `numberOfRows`.
var repeat = numberOfRows ? 1 * Math.ceil(Math.log10(numberOfRows + 1)) : 1;
// Used in other goTo function to avoid conflict.
$scope.goToLine.scrollingToEnd = true;
// FIXME : not supposed to call scrollViewportTo several times... => fixed in recent versions of HandsOnTable ?
for (let n = 0; n < repeat; n++) {
if (!$scope.goToLine.scrollingToEnd) {
return;
}
setTimeout(function () {
if (!$scope.goToLine.scrollingToEnd) {
return;
}
hot.scrollViewportTo(numberOfRows - 1, 0);
}, 500);
}
};

Timer Efficiency

I'm working on an AS3 project and for one of the effects I use timers to switch the colors then stop. The function is below.
//global variable
private var valueAnimationTimer:Timer = new Timer(50);
//constructor
valueAnimationTimer.addEventListener(TimerEvent.TIMER, scrollUp );
//function
private function scrollUp(e:TimerEvent):void
{
var i:int = e.currentTarget.currentCount as int;
if (i < 10)
{
if (colored){
if (i % 2 == 0){
ChangeColor(ico, flickerColor);
}
else{
ico.transform.colorTransform = new ColorTransform();
}
}
tfValue.y -= 7.5;
}
else
{
RemoveFilters(ico);
tfValue.y = ico.height / 2;
e.currentTarget.reset();
RemoveSprite(tfValue);
colored = false;
}
}
Each character (object) has it's own version of this function and it happens at different times (like when it is injured or poisoned). The listener is added once in the constructor, it is only removed when the character dies and is removed from the stage. The issue here is after the timer is used on at least 3 characters, the frame rate begins to drop. Every time the function is called, the frame rate drops lower and lower.
What I don't understand is, if the timer is stopped, and the listeners are only added once so it doesn't overload the stack, then why does the frame rate begin to decline after the listener is actually used? It doesn't run forever only for a small amount of time, but it happens again and again. When the frame rate drops the entire program begins to lag badly and eventually freezes. I have no idea what is causing this
Also be aware that inside of the Timer function, the first number is your count in MILLISECONDS and the second is repeat count
var fl_TimerInstance:Timer = new Timer(240000, 1);
So this example above is a 4 minute timer that repeats 1 time
I bring this up because yours is set for 50 milliseconds which is very quick lol

jqGrid: update a row AND have its formatters updated as well

Ideally I would call setRowData and have my cell formatters and rowattr function re-run, but cell formatters and rowattr functions don't re-run when calling setRowData (I don't know why but that's another question maybe), so setRowData isn't really helpful for me.
It seems the next easiest thing to do would be to remove a row and re-add a new one at the same position with the same model. To do that I need to get the rowid of the row above the selected row so that I can call addRowData and specify the ID of the row above in srcrowid and use 'after' for the position. This is what I'm thinking:
$.jgrid.extend({
updateRow: function(rowid, model){
// get index from id
var index = this.jqGrid('getInd', rowid);
// note: my first row's index is 1 (is that normal?)
if ( index == 1 ){
position = 'first';
srcrowid = 'n/a';
}
else{
position = 'after';
srcrowid = _____ how to get rowid of row above selected row _____???
}
// delete row
this.jqGrid('delRowData', rowid);
// insert at index
this.jqGrid('addRowData', rowid, model.attributes, position, srcrowid);
}
});
How can I get the rowid of the row above the selected row? (Is there an easier way? Is this a bad strategy?)
Note: I'm using backbone.js collections and models
I find the best way to change the row is to use setRowData instead of usage delRowData and addRowData. If you know rowid then you can use $("#" + rowid); (or if rowis have special characters like :, . an so on then $("#" + $.jgrid.jqID(rowid));) to get the <tr> element. Then you can use jQuery.addClass, jQuery.css, jQuery.attr to change the attributes of the row.
It's important to understand that jqGrid uses the same methods internally it it's required to modify element of the grid. The main goal of rowattr is another one. During filling of the grid data there are many scenarios. One can create DOM elements for <td> and <td> and insert there in the grid. The main problem is performance in case of working with DOM. It's much slowly as building of strings. Moreover DOM is even much more slowly if the elements are exist on the the HTML page (in opposite to disconnected elements). If one modify one element only from the grid having 500 rows then the position of elements of all other rows need be recalculated.
Because of the problem jqGrid construct the whole body of the grid first in string format and then assign all <tr> and <td> elements using one set of innerHTML. It improves dramatically the performance of filling of the grid. See the answer for additional information. The formatters and callbacks cellattr and rowattr are introduced to allow to customize cell and row attributes during building of grid body in string format. It gives you customization possibilities without reducing of performance.
On the other side if you need to modify the row which are attached already on HTML page then you will have no advantage with working with strings instead of DOM. Because of that I recommend you just use jQuery.addClass, jQuery.css, jQuery.attr directly. If you need to change multiple classes, assign multiple css rules or multiple attributes then you should use one call of above functions. You can use object form of jQuery.css, jQuery.attr for it.
The updateRow extension below works, BUT I ended up not using it. Not so much for the reasons Oleg talked about (which I assume are valid and something you should definitely consider), but because I had a filter provider that was too difficult to sync with (e.g. after soft deleting a row, I need to now determine if new deleted status agrees with current filter...and that's a pretty simple example). So I just defer to the data the filter provider gives me and repopulate the grid each time, which I'm not fond of, but I don't see other easy options.
As far as the extension below goes, here are pros/cons as I see them:
Pros:
easy to use
You just rely on the rowattr functions and cell formatters you already defined. You don't have to write those twice.
Cons:
possibly performance--see Oleg's answer
please add any you see
Unknowns:
performance? I have no idea how much worse it performs. It would be interesting to do benchmarks with different browsers. I saw no problems, but I only have maybe 20 rows. Let's say we're working with 500 rows and adding/removing classes and calling .css() took 30 ms but using the extension took 300 ms on a 'typical' machine/browser. That would be 10 times slower, but for me it would probably be worth it because I don't have to write something twice.
here's the extension:
$.jgrid.extend({
updateRow: function(rowid, data){
// get index from id
var index = this.jqGrid('getInd', rowid);
// note: my first row's index is 1 (is that normal?)
if ( index == 1 ){
position = 'first';
}
else{
position = 'after';
srcrowid = $(this).find('tr#' + rowid).prev()[0].id;
}
// delete row
this.jqGrid('delRowData', rowid);
// insert at index
this.jqGrid('addRowData', rowid, data, position, srcrowid);
}
});

Selection count in extjs works too slow

I use standart Ext.grid.Panel in extJs.
I need to select up to 500 rows in grid, and I want to know how many rows I have selected. I do it in such way:
multiSelect: true,
listeners:
{
'select':
function( combo, record)
{
mainNS.app.pagingTB.items.get('selected-sited').update(' count of selected rows: ' +
mainNS.app.mainGrid.getSelectionModel().getCount())
}
},
But it works too slow, how can I optimize this operation?
The select event will fire for every record during a multiple selection. So if n rows are being selected at once, that means you're updating the content and, most importantly, triggering a layout n times.
You have 2 options:
Listen to the selectionchange event, which will fire only once for a batch of selections.
Add buffer: 1 as an option to the select event, so it will buffer the event from firing until the last selection has occurred.
No. 1 is the preferred solution.

Resources