PDA

View Full Version : Time taken to populate QTableWidget differs based on where I am in the app



Cupidvogel
24th May 2015, 11:14
I observed an interesting thing today. In my app, a certain windows has a tabbed interface (QTabWidget). There are three tabs, and in one of the three tab widgets, there is QTableWidget. Which is populated with rows of data when the user wants some information and clicks a button. Now for certain kind of queries the number of rows is huge, say 3000~4000. A simple qDebug() prints out all the 4000 rows almost instantly, but it takes upto 2-3 minutes for all the 4000 rows to be populated in the QTableWidget UI. That tells me that the time is not being taken to generate the data, time is taken to populate the table with so many rows. Which is justified.

Today, after I queried for one such data, I switched to another tab to check out something. When I switched back to the tablewidget tab, I found that all 4000 rows have been populated! I have been to the other tab for 5~6 seconds, and it generally takes 3 minutes for all rows to come up when I am in the tablewidget tab all through!

Why is this happening? I suspect something along the lines of the UI thread being changed to accommodate my viewing the new tab, and delegating the tablewidget tab to another thread which does it fast in the background. Is there something more to it? Can I tap this feature to ensure that the populating is done in 5 seconds always, no mater where I am?

wysota
24th May 2015, 12:44
First of all QTableWidget is not suited to situations when it has to hold that much data, you should rather use a model based approach. Second, you are asking this one by one possibly with having sorting or filtering applied or with column width fixed to size of its contents. One or more of these causes a costly operation to be performed after each of the rows is inserted. It's much better to insert all rows in one batch. This is again possible when using a regular model.

Cupidvogel
24th May 2015, 14:16
My column width is fixed. It doesn't depend on the content of the rows that are being populated one by one.

In our app, it is not feasible to populate all data in one go. Sometimes the total number of results may take say 5 seconds to collect, and we don't want the user to wait until 5 seconds before he sees all results at once. Instead it's better to display results on the fly when they are available.

But why does it perform so faster when I switch to another tab?

wysota
24th May 2015, 16:12
In our app, it is not feasible to populate all data in one go. Sometimes the total number of results may take say 5 seconds to collect, and we don't want the user to wait until 5 seconds before he sees all results at once. Instead it's better to display results on the fly when they are available.
5 seconds for 4000 rows gives about 1 record per 1 ms. You don't have to update every 1ms, do you? You can surely update in batches of 1000 every second.


But why does it perform so faster when I switch to another tab?
The view is not displayed which leads me to the conclusion that your column width is not as fixed as you think.

anda_skoa
24th May 2015, 16:15
But why does it perform so faster when I switch to another tab?
A widget that is not visible doesn't have to do any updating when the data it displays changes.
A visible widget does.

Cheers,
_

Cupidvogel
24th May 2015, 17:27
Thanks guys. I will display my code which does it, you decide whether the column width is fixed or not (yeah, I am setting the button's length on the fly because I need to elide the text):



//QueryView class

void QueryView::initUI()
{
queryButton = new QPushButton(this);
textBox = new QLineEdit(this);
/* ---other UI code --- */

fTableWidget = new QTableWidget(1,2,this);
fTableWidget->setGeometry(15,169,251,232);
fTableWidget->setColumnWidth(0,198);
fTableWidget->setColumnWidth(1,54);
fTableWidget->setFrameStyle(QFrame::NoFrame);
fTableWidget->setShowGrid(false);
fTableWidget->horizontalHeader()->hide();
fTableWidget->verticalHeader()->hide();
fTableWidget->verticalHeader()->setDefaultSectionSize(24);
fTableWidget->setObjectName("fTableWidget");
fTableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOf f);
}

void QueryVieww::bindSignalsAndSlots()
{
connect(queryButton, SIGNAL(clicked()), this, SLOT(doQuery()));
}

void QueryView::doQuery()
{
QString text = textBox->text();
QueryAction *action = new QueryAction(text, this);
action->execute();
}

void QueryView::updateTable(QString text1, QString text2)
{
int rowNum = fTableWidget->rowCount();
fTableWidget->setRowCount(rowNum+1);

QPushButton *textSnippet = new QPushButton();
textSnippet->setStyleSheet("text-align: left; border: none; padding-left: 10px;");
textSnippet->setCursor(Qt::PointingHandCursor);
textSnippet->setFixedHeight(24);
textSnippet->setFixedWidth(198);

QPushButton *button2 = new QPushButton();
button2->setStyleSheet("text-align: left; border: none; padding-left: 10px;");
button2->setCursor(Qt::PointingHandCursor);
button2->setFixedHeight(24);
button2->setFixedWidth(54);
button2->setText(text2);

fTableWidget->setCellWidget(rowNum,0,textSnippet);
fTableWidget->setCellWidget(rowNum,1,button2);


//here text is added last because ellipsis text needs the width of the button, so first attaching it to the view,
//then extracting its width and showing ellipsis at 5 px from the end, i.e 198-5 = 193
QFontMetrics metrics(textSnippet->font());
QString elidedText = metrics.elidedText(text1, Qt::ElideRight, 193);
textSnippet->setText(elidedText);

}

//QueryAction class

QueryAction::QueryAction(QString queryText, QueryView *view)
{
fQueryText = queryText;
fView = view;
}

QueryAction::execute()
{
//search a binary tree with queryText, if query matches, update view immediately
while (rootNode != NULL)
{
data = rootNode.data;
if (data.text == fQueryText)
{
fView->updateTable(data.keyData, data.valueData);
}
//traverse to left and right of rootNode
}
}


And I also verified whether doing it in one go gives me better performance by modifying the execute method like this:



QueryAction::execute()
{
//search a binary tree with queryText, if query matches, update view immediately
std::vector<RootNode.Data> temp;
while (rootNode != NULL)
{
data = rootNode.data;
if (data.text == fQueryText)
{
//commented out, no updating on the fly
//fView->updateTable(data.keyData, data.valueData);
temp.push_back(data);
}
//traverse to left and right of rootNode
}

//now that all the data are there, populate UI in one go
for (int i = 0; i < temp.size(); i++)
fView->updateTable(temp[i]);
}


This does give me a better performance, but pretty marginal, by about 10%. The queries which were taking say 2 minutes to populate (around 3500 rows) are now taking 1 minute 50 seconds (I am discounting the initial time gap after the button is clicked because the results are being cached first. I started counting time only when the first row was populated, so that I can find out the exact time taken to populate all rows). Whereas switching the tab was doing it in 5-6 seconds. :)

wysota
24th May 2015, 20:38
Using cell widgets has very negative impact on performance, those buttons do not have any signals connected to them so I don't understand why you are using them at all instead of setting data directly on the item (or the model). Off the top of my head I will not say whether the fixed size you set on the buttons is respected by the view or not but surely your columns are not fixed, you can grab the header and resize it (yes, I know you are hiding the header but it doesn't make the columns fixed size).

Cupidvogel
24th May 2015, 20:56
Oh, yes, I am sorry, I forgot to include the signal connection for the rows. Here is the revised method:



void QueryView::updateTable(QString text1, QString text2)
{
int rowNum = fTableWidget->rowCount();
fTableWidget->setRowCount(rowNum+1);

QPushButton *textSnippet = new QPushButton();
textSnippet->setStyleSheet("text-align: left; border: none; padding-left: 10px;");
textSnippet->setCursor(Qt::PointingHandCursor);
textSnippet->setFixedHeight(24);
textSnippet->setFixedWidth(198);
textSnippet->setProperty("data",text1);

QPushButton *button2 = new QPushButton();
button2->setStyleSheet("text-align: left; border: none; padding-left: 10px;");
button2->setCursor(Qt::PointingHandCursor);
button2->setFixedHeight(24);
button2->setFixedWidth(54);
button2->setText(text2);
button->setProperty("data",text2);

fTableWidget->setCellWidget(rowNum,0,textSnippet);
fTableWidget->setCellWidget(rowNum,1,button2);


//here text is added last because ellipsis text needs the width of the button, so first attaching it to the view, then extracting its width and showing
//ellipsis at 5 px from the end, i.e 198-5 = 193
QFontMetrics metrics(textSnippet->font());
QString elidedText = metrics.elidedText(text1, Qt::ElideRight, 193);
textSnippet->setText(elidedText);

//connect both columns to the same method
connect(textSnippet, SIGNAL(clicked()), this, SLOT(showAlert()));
connect(button2, SIGNAL(clicked()), this, SLOT(showAlert()));

}


I didn't get what you said about the column size. I am setting the column size width fixed in the initUI method, right? I am specifying the height and width for each button because I need it to elide text, which needs a fixed width to work. If cellwidgets have negative impact what other way can I do this task?

wysota
24th May 2015, 21:29
Oh, yes, I am sorry, I forgot to include the signal connection for the rows.
And what are you going to connect them to in your real code?


I didn't get what you said about the column size. I am setting the column size width fixed in the initUI method, right?
I don't see any call in there that would do that.


If cellwidgets have negative impact what other way can I do this task?

Well... QTableWidgetItem::setText() would be my first choice :)

Cupidvogel
24th May 2015, 21:45
The 9th and 10th lines of my initUI code:



fTableWidget = new QTableWidget(1,2,this);
fTableWidget->setGeometry(15,169,251,232);
fTableWidget->setColumnWidth(0,198);
fTableWidget->setColumnWidth(1,54);


Yes, I may change it to QTableWidgetItem, change the clicked event to cell clicked event of the table widget itself. But again, will that increase the performance significantly? Like switching tab here does? I want that performance. :)

Upon clicking, I get the data associated with that row (as data1 and data2), which we use to take the user to certain part of the document where the query value is showing (think of it as an inline Google app. :) ), or show an alert if it is not available for some reason.

wysota
24th May 2015, 21:54
The 9th and 10th lines of my initUI code:



fTableWidget = new QTableWidget(1,2,this);
fTableWidget->setGeometry(15,169,251,232);
fTableWidget->setColumnWidth(0,198);
fTableWidget->setColumnWidth(1,54);


That doesn't say anything to be "fixed".


But again, will that increase the performance significantly?
Yes.


I want that performance. :)
Use a real model and insert data in batches instead of a series of single inserts.


Upon clicking, I get the data associated with that row (as data1 and data2), which we use to take the user to certain part of the document where the query value is showing (think of it as an inline Google app. :) ), or show an alert if it is not available for some reason.
Ok but why a button?

Cupidvogel
24th May 2015, 21:58
I needed to get the click event. Now that I see the table widget has a cell clicked event, I can try that as well. The second code sample I gave did all the updating in one go, right? But like I said that was faster by only around 10%. So okay, I will try the same in one go chunk updating with the table widget item, will see whether performance improves drastically or not..

And I thought setting the column width fixed it? What else do you mean by 'fixed'? :(

Do you mean this - http://stackoverflow.com/questions/9079120/how-to-prevent-user-from-resizing-columns-of-qtablewidget ?

wysota
24th May 2015, 22:38
The second code sample I gave did all the updating in one go, right?
This one?


for (int i = 0; i < temp.size(); i++)
fView->updateTable(temp[i]);

No. This loops over all items to be inserted and adds them one by one here:

fTableWidget->setRowCount(rowNum+1);

You would need to call this function once for the whole update. But in general as I wrote in the very beginning you will not be able to do it properly with QTableWidget. You would have to implement a proper model (subclass of QAbstractItemModel) and use QTableView instead of QTableWidget.


And I thought setting the column width fixed it? What else do you mean by 'fixed'? :(
I mean QHeaderView::​setSectionResizeMode() with QHeaderView::Fixed. And the consequences for Qt code paths this bears.

Cupidvogel
24th May 2015, 22:47
Okay. I have never used QTableView with a model before, so will take some time to reimplement my working. Can I make it look just like the table widget?

And secondly, if I am doing it through a model, then I will have to insert all the data into the model one by one right like I was updating the rows here one by one, like here - http://doc.qt.digia.com/4.6/sql-presenting.html? Won't that take as much time as inserting the rows? Or is it that because it is a data model and not an UI element, inserting 4000 rows will be kind of instantaneous?

wysota
24th May 2015, 23:57
Can I make it look just like the table widget?
Have a look what is the base class of QTableWidget.


And secondly, if I am doing it through a model, then I will have to insert all the data into the model one by one right like I was updating the rows here one by one
No. You can turn an empty model into an infinite one in an instance.

Cupidvogel
25th May 2015, 00:03
Okay. Will try it then and let you know.

Cupidvogel
12th June 2015, 00:32
Thanks guys. I updated my code to do it via QTableView model based design. It now updates tens and thousands of rows in the blink of an eye! This is severely cool.. :)