lag in GUI created in Qt (verified by QTime) - c++

I have a GUI C++ Class in Qt which has a SLOT which adds an array of Text box's & label's in a QGridLayout. The array is square depending upon a variable say n. i.e. if n == 10 then there are 10x10 QTextbox & QLabel in the QGridLayout. Basically when a user presses the Increase button the value of n is incremented by 1 & accordingly the QTextbox & QLabel are created in a SLOT & added in the QGridLayout. When the value of n is 15 it is taking 1-2 secs for the GUI to be updated. However when I used QTime in that SLOT it showed me that the actual time to execute that SLOT is about 100 ms. While in Debug mode I observed that QDebug used to print the elapsed time even though the GUI wasn't completely updated for higher values of n. I would like to know why is there so much delay & what is running after that SLOT is executed so that I can measure the time for the same. I hope I made myself clear. Please let me know if you could not understand my Question.
Thank You.
Actually I don't have the exact code with me right now. So I have created a sample code highlighting the logic of my code. Please ignore any syntax errors as I have prepared it as quickly as I could. Sorry for that :(
So here's the code snippet:
Class A
{
private:
int n;
QList <QLabel *> *labelList;
QList <QTextEdit *> *textList;
QGridLayout *inp;
public slots:
updateGUI();
}
A::updateGUI()
{
QTime t;
t.start();
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
int even = j%2;
QLabel *tempLab = new QLabel();
//some stuff for initialisation of the QLabel
QTextEdit *tempText = new QTextEdit();
//some stuff for initialisation of the QTextEdit
labelList->append(tempLab);
textList->append(tempText);
if(even == 0)
inp->AddWidget(tempLab, i, j, 5, 5, Qt::AlignCenter);
else
inp->AddWidget(tempText, i, j, 5, 5, Qt::AlignCenter);
}
}
QDebug("%d", t.elapsed());
}
PS: QTime tobj.start() is the 1st line in the SLOT & QDebug("%d", tobj.elapsed()) is the last line in the SLOT Ofcourse!

When you're doing a bulk update on a widget, turn off the updatesEnabled property. This prevents 21 individual updates when you're adding (11*11-10*10) new buttons.

The 0.1s delay is because you are creating a ton of widgets and then adding them to a grid (every time you add a widget, which obviously has an overhead, the dimensions of the other cells have to be updated as things 'shuffle' round).
So why the further delay after the timer? All the painting and resizing events that were generated by the slot have now been put into the event queue, which was halted whilst your slot was executing. Once your slot completes, all those tasks need to be processed.
Further to what other people have said, there are two fundamental flaws to what you are doing:
Deleting all the existing widgets to add possibly a single one. I didn't think I need to explain how horribly inefficient this is...
If you have managed to get into the situation where 100s of text boxes and labels in a grid are being shown to the user - your UI has failed. You need to come up with a better way of allowing the user to interact with all this data.

It would really help, if you post some code with the same problem. How will your slot react, when you select let's say n=10 and then n=7? Does it removes 3 last items from layout? And if you select n=7 and then n=10, will it try add only last 3 lines?
Is it possible, that you are not removing items from layout and trying to add new items over the existing ones?
If it is so - then you can remove all widgets from layout with this:
QLayoutItem* item;
while ( ( item = %your_layout_name%->takeAt( 0 ) ) != NULL ){
delete item->widget();
}
delete item;
or you can remove widget by it's object name (or by handle, stored in QList QVector or something):
swLayout->removeWidget(myTextEdit);
UPD:
now, that we have some code,
you also need to clear labelList and textList, which store handles if i'm correct.
Then, lines
inp->AddWidget(tempLab, i, j, 5, 5, Qt::AlignCenter);
inp->AddWidget(tempText, i, j, 5, 5, Qt::AlignCenter);
tries to add tempLab widget to layout inp in position (i,j) with 5 height and width, then you try to add another widget tempText to the same location, which is obviously wrong
Next, the loop itself is incorrect:
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
is you make a loop like this, then you will be adding widgets like this:
inp->AddWidget(tempLab, i, 0, 5, 5, Qt::AlignCenter);
inp->AddWidget(tempLab, i, 1, 5, 5, Qt::AlignCenter);
inp->AddWidget(tempLab, i, n, 5, 5, Qt::AlignCenter);
but you sat widget width and heigth as 5, so you need to add widgets another way or they will be painted over each other:
inp->AddWidget(tempText, i*5, j*5, 5, 5, Qt::AlignCenter);

Related

How can i toggle a widget visibility that is in a grid layout?

I want to do a list that changes it's fields numbers when the user changes a the value of a spinbox.
Something like this:
First 5 fields by default
Then just 1 field for example
And if the user wants to change it again, he can put 5 fields again.
I made a GridLayout and a couple of QList, one for the Labels and the other one for LineEdits. I did this:
I create a basic case (with just 1 field) and i later add more on excecution time adding Widgets to the GridLayout by:
gridLayout->addWidget(labels.at(x), 0, 1)
where labels is the QList. it works fine to add widgets but i can't remove and add again.
i tried using
gridLayout->removeWidget(lables.at(x), 0, 1)
labels.at(x)->hide()
label.at(x)->setVisible(false)
all works but i can't show it again with none of this:
gridLayout->addWidget(labels.at(x), 0, 1)
labels.at(x)->show()
label.at(x)->setVisible(true)
Layouts are handlers of the geometry of the widgets. If you use the removeWidget() function, you will only remove that element from the layout but it will still be visible. If you want it not to be visible you have to delete it with delete.
In the following example I show you how to add and remove the widgets using the valueChanged signal of the QSpinBox.
void Dialog::on_spinBox_valueChanged(int arg1)
{
int nElements = labels.count();
//add
if(arg1 > nElements){
for(int i=nElements; i < arg1; i++){
QLabel *label = new QLabel(QString::number(i), this);
QLineEdit *line = new QLineEdit(QString::number(i), this);
labels.append(label);
lines.append(line);
ui->gridLayout->addWidget(label, i, 0, 1, 1);
ui->gridLayout->addWidget(line, i, 1, 1, 1);
}
}
//remove
else if(arg1 < nElements){
for(int i=arg1; i < nElements; i++){
QLabel *label = labels.at(i);
QLineEdit *line = lines.at(i);
ui->gridLayout->removeWidget(label);
ui->gridLayout->removeWidget(line);
labels.removeAt(i);
lines.removeAt(i);
delete label;
delete line;
}
}
}
Add:
Remove:

Add widgets to QFileDialog

I need to add a widget (QTableWidget) into QFileDialog's layout. I know that it is QGridLayout with sizes (3,4). The table must be in 3-rd row and span all columns.
QTableWidget* tableWidget = new QTableWidget(this);
QGridLayout *layout = static_cast<QGridLayout*>(QFileDialog::layout());
layout->addWidget(tableWidget, 2, 0, 1, 4);
With this code the original 3-rd row which contains lineEdit and save/open pushButton disappears. How can I add widgets between already existing widgets of QGridLayout so that original widgets remain in the layout.
I strongly recommend you not to rely on QFileDialog's implementation. The layout can be different on different platforms or different versions of Qt. It may be more correct to place your table under the dialog or to the right of it. This can be done easily without altering the layout of the QFileDialog itself. Just create a QVBoxLayout and put QFileDialog and QTableWidget inside it.
However, the question has been asked, and the solution exists. QGridLayout has no functionality such as QBoxLayout::insertItem. So we need to implement this behavior manually. The plan is:
Obtain the list of layout items placed in 3rd and 4th rows.
Calculate new positions of items.
Take elements out of item and add them back at new positions.
Working code:
QFileDialog* f = new QFileDialog();
f->setOption(QFileDialog::DontUseNativeDialog, true); //we need qt layout
QGridLayout *layout = static_cast<QGridLayout*>(f->layout());
QList< QPair<QLayoutItem*, QList<int> > > moved_items;
f->show();
for(int i = 0; i < layout->count(); i++) {
int row, column, rowSpan, columnSpan;
layout->getItemPosition(i, &row, &column, &rowSpan, &columnSpan);
if (row >= 2) {
QList<int> list;
list << (row + 1) << column << rowSpan << columnSpan;
moved_items << qMakePair(layout->takeAt(i), list);
i--; // takeAt has shifted the rest items
}
}
for(int i = 0; i < moved_items.count(); i++) {
layout->addItem(moved_items[i].first,
moved_items[i].second[0],
moved_items[i].second[1],
moved_items[i].second[2],
moved_items[i].second[3]);
}
QTableWidget* tableWidget = new QTableWidget();
layout->addWidget(tableWidget, 2, 0, 1, 4);

How to pass coordinates of button in matrix to slot on click?

I'm beginning in C++ and Qt. I have a matrix of QPushButtons and I want to handle the click event for them. Problem is I'm not being able to tell the slot the coordinates os the button in the array so that I can play with them. I've succeed in passing a integer to my slot, but not both coordinates. I'm not sure if I'm making myself clear enough... That's my problematic code:
for (int i = 0; i < mapSize_x; i++) {
for (int j = 0; j < mapSize_y; j++) {
buttonsArray[i][j] = new QPushButton();
ui->mainLayout->addWidget(buttonsArray[i][j], i, j);
connect(buttonsArray[i][j], SIGNAL(clicked()),
signalMapper, SLOT(map()));
signalMapper->setMapping(buttonsArray[i][j], i, j); // here
}
}
connect(signalMapper, SIGNAL(mapped(int, int)),
this, SLOT(buttonClick(int, int)));
setMapping only accepts two parameters, and I want to pass three. Is there a workaround? I've googled a lot and still can't find an answer. I've also tried to pass the QPushButton object instead of the coordinates, but unsuccessfully as well. Thanks in advance.
Consider using a QHash to store your pushbuttons, keyed by the buttons themselves and pointing to a pair containing their row and column. You should then be able to set your mapping based on the widget pointer and then look up the associated row and column when the signal mapper emits its signal. For example, declare a class data member as follows:
QHash<QPushButton*, QPair<int, int> > buttonHash;
Then your code above could become
for (int i = 0; i < mapSize_x; i++) {
for (int j = 0; j < mapSize_y; j++) {
QPair<int, int> gridPair(i, j);
QPushButton* button = new QPushButton();
buttonHash.insert(button, gridPair);
ui->mainLayout->addWidget(button, i, j);
connect(button, SIGNAL(clicked()),
signalMapper, SLOT(map()));
signalMapper->setMapping(button, qobject_cast<QWidget*>(button));
}
}
connect(signalMapper, SIGNAL(mapped(QWidget*)),
this, SLOT(buttonClick(QWidget*)));
Finally, your buttonClick slot would become the following:
void SomeClass::buttonClick(QWidget* widget) {
QPair<int, int> pair = buttonHash.value(qobject_cast<QPushButton*>(widget));
int myRow = pair.first;
int myColumn = pair.second;
...
}
There are also at least 2 other ways of tackling this problem:
You could try to combine the row and column into a string and use QSignalMapper::setMapping(QObject*, const QString&). This would then require some logic to pull the row and column out of the string in the buttonClick slot.
You could try to combine the row and column into a single integer by using bit shifting and a bitwise OR. You could then rely on QSignalMapper::setMapping(QObject*, int). This would require some logic to pull the row and column out of the integer in the buttonClick slot.
I won't go into detail about these other solutions because they're somewhat ugly. The solution I provided above remains the most intuitive.
The QSignalMapper class can be very useful in certain circumstances, but it may be overkill for your current use-case.
Instead, consider using a QButtonGroup, which was designed for exactly what you're trying to do, and provides a much cleaner and simpler API.

QGridLayout issue with inserting widgets

(Using Qt 4.6.3, x64, linux)
I'm testing how to properly insert widgets into a existing a QGridLayout filled with various widgets. A broken down contrived case is the following:
QApplication app(argc,argv);
QWidget w;
QGridLayout* gl = new QGridLayout(&w);
QLabel* label = new QLabel("Image Size:");
QLineEdit* wedit = new QLineEdit("100");
QLabel* xlabel = new QLabel("x");
wedit->setAlignment(Qt::AlignRight);
gl->addWidget(label);
gl->addWidget(xlabel, 0, 1, 1, 1);
gl->addWidget(wedit, 0, gl->columnCount());
Which creates the following widget:
.
Assuming that have an existing QGridLayout as above, but without the "x" label, and I wished to insert this into the layout, switching the latter two addWidget lines might seem valid, i.e.:
\\ same as above
gl->addWidget(label);
gl->addWidget(wedit, 0, gl->columnCount());
gl->addWidget(xlabel, 0, 1, 1, 1);
This however, creates the following:
The gl->columnCount() after this is still 2, as both the x-label and the QLineEdit are filling the same cell. Based on this knowledge, the following code produces the initial desired result:
gl->addWidget(label);
gl->addWidget(wedit, 0, 2); // note: specified column 2, columnCount() is now 3
gl->addWidget(xlabel, 0, 1, 1, 1);
Though this is not particularly useful, as the original layout in question isn't built with later layouts in mind.
Since addWidget allows for specifying cell position, as well as row/column span, it seems odd that Qt wouldn't automatically replace the existing widgets in the layout. Does anyone have a suggestion as to how I might overcome this? I assume it would be possible to recreate a QGridLayout and copy into it the children of the original, taking care to insert the additional widget in the right location. This however is ugly, and susceptible to Qt version issues (as I want to modify a built in widget).
Edit:
I realize that I'm making the assumption of thinking in a QHBoxLayout way, where inserting a widget is uniquely understood, whereas in a QGridLayout this isn't the case (?).
I can clarify that I ultimately would like to modify QFileDialog::getSaveFileName, by inserting a widget (similar to the widget shown above) right above the two lower rows (i.e. above "File &Name:").
Thanks
Switching the latter two addWidget lines is not valid. For the following code:
gl->addWidget(label);
gl->addWidget(wedit, 0, gl->columnCount());
gl->addWidget(xlabel, 0, 1, 1, 1);
The arguments for the addWidget() calls are evaluated prior to adding the widget. Therefore, gl->columnCount() evaluates to one instead of two for the second call, since the column still has to be created. You are effectively adding two widgets to column one.
A possible solution is to re-add the widgets that should be relocated. I.e.
QLayoutItem* x01 = gl->itemAtPosition(0,1);
gl->addWidget(x01->widget(), 0, 2);
gl->addWidget(xlabel, 0, 1, 1, 1);
Now, this isn't particularly pretty, or easy to maintain, as a new version of Qt might change the original widget, and blindly handpicking and relocating children isn't that clever. The following real example (the one I actually wanted to solve) was to alter the Qt's "Save As" dialog window, that shows up using QFileDialog::getSaveFileName.
class ImageFileDialog : public QFileDialog {
public:
ImageFileDialog(QWidget* parent);
~ImageFileDialog();
QString getFileName() const;
QSize getImageSize() const;
QDialog::DialogCode exec(); // Overriden
protected:
void showEvent(QShowEvent* event); // Overriden
private:
QString fileName_;
QSize imageSize_;
QLineEdit* widthLineEdit_;
QLineEdit* heightLineEdit_;
};
And in the source (showing just constructor, focus handling and exec):
ImageFileDialog::ImageFileDialog(QWidget* parent)
: fileName_(""),
imageSize_(0,0),
widthLineEdit_(0),
heightLineEdit_(0)
{
setAcceptMode(QFileDialog::AcceptSave);
setFileMode(QFileDialog::AnyFile);
setConfirmOverwrite(true);
QGridLayout* mainLayout = dynamic_cast<QGridLayout*>(layout());
assert(mainLayout->columnCount() == 3);
assert(mainLayout->rowCount() == 4);
QWidget* container = new QWidget();
QGridLayout* glayout = new QGridLayout();
QLabel* imageSizeLabel = new QLabel("Image Size:");
widthLineEdit_ = new QLineEdit("400");
heightLineEdit_ = new QLineEdit("300");
widthLineEdit_->setAlignment(Qt::AlignRight);
heightLineEdit_->setAlignment(Qt::AlignRight);
container->setLayout(glayout);
glayout->setAlignment(Qt::AlignLeft);
glayout->addWidget(widthLineEdit_);
glayout->addWidget(new QLabel("x"), 0, 1);
glayout->addWidget(heightLineEdit_, 0, 2);
glayout->addWidget(new QLabel("[pixels]"), 0, 3);
glayout->addItem(new QSpacerItem(250, 0), 0, 4);
glayout->setContentsMargins(0,0,0,0); // Removes unwanted spacing
// Shifting relevant child widgets one row down.
int rowCount = mainLayout->rowCount();
QLayoutItem* x00 = mainLayout->itemAtPosition(mainLayout->rowCount()-2,0);
QLayoutItem* x10 = mainLayout->itemAtPosition(mainLayout->rowCount()-1,0);
QLayoutItem* x01 = mainLayout->itemAtPosition(mainLayout->rowCount()-2,1);
QLayoutItem* x11 = mainLayout->itemAtPosition(mainLayout->rowCount()-1,1);
QLayoutItem* x02 = mainLayout->itemAtPosition(mainLayout->rowCount()-1,2);
assert(x00); assert(x01); assert(x10); assert(x11); assert(x02);
mainLayout->addWidget(x00->widget(), rowCount-1, 0, 1, 1);
mainLayout->addWidget(x10->widget(), rowCount, 0, 1, 1);
mainLayout->addWidget(x01->widget(), rowCount-1, 1, 1, 1);
mainLayout->addWidget(x11->widget(), rowCount, 1, 1, 1);
mainLayout->addWidget(x02->widget(), rowCount-1, 2, 2, 1);
// Adding the widgets in the now empty row.
rowCount = mainLayout->rowCount();
mainLayout->addWidget(imageSizeLabel, rowCount-3, 0, 1, 1 );
mainLayout->addWidget(container, rowCount-3, 1, 1, 1);
// Setting the proper tab-order
QLayoutItem* tmp = mainLayout->itemAtPosition(mainLayout->rowCount()-2,1);
QLayoutItem* tmp2 = mainLayout->itemAtPosition(mainLayout->rowCount()-1,1);
assert(tmp); assert(tmp2);
QWidget::setTabOrder(heightLineEdit_ , tmp->widget());
QWidget::setTabOrder(tmp->widget(), tmp2->widget());
}
// Makes sure the right widget is in focus
void ImageFileDialog::showEvent(QShowEvent* event)
{
widthLineEdit_->setFocus(Qt::OtherFocusReason);
}
// Called to create the widget
QDialog::DialogCode ImageFileDialog::exec()
{
if (QFileDialog::exec() == QDialog::Rejected)
return QDialog::Rejected;
// The code that processes the widget form and stores results for later calls to
// getImageSize()
return QDialog:Accepted;
}
Which, using for instance
ImageFileDialog* dialog = new ImageFileDialog(&w);
dialog->exec();
Creates the following widget:
Comments and ways to do this better, or why this is just plain wrong are most welcome :)

Qt: TableWidget's ItemAt() acting weirdly

i'm working on a windows application, where in a dialog i query some data from Postgres, and manually show the output in a table widget.
m_ui->tableWidget->setRowCount(joinedData.count());
for(int i=0; i<joinedData.count(); i++) //for each row
{
m_ui->tableWidget->setItem(i, 0, new QTableWidgetItem(joinedData[i].bobin.referenceNumber));
m_ui->tableWidget->setItem(i, 1, new QTableWidgetItem(QString::number(joinedData[i].bobin.width)));
m_ui->tableWidget->setItem(i, 2, new QTableWidgetItem(QString::number(joinedData[i].tolerance.getHole())));
m_ui->tableWidget->setItem(i, 3, new QTableWidgetItem(QString::number(joinedData[i].tolerance.getLessThanZeroFive()))); m_ui->tableWidget->setItem(i, 4, new QTableWidgetItem(QString::number(joinedData[i].tolerance.getZeroFive_to_zeroSeven())));
m_ui->tableWidget->setItem(i, 5, new QTableWidgetItem(QString::number(joinedData[i].tolerance.getZeroFive_to_zeroSeven_repetitive())));
m_ui->tableWidget->setItem(i, 6, new QTableWidgetItem(QString::number(joinedData[i].tolerance.getZeroSeven_to_Three())));
m_ui->tableWidget->setItem(i, 7, new QTableWidgetItem(QString::number(joinedData[i].tolerance.getThree_to_five())));
m_ui->tableWidget->setItem(i, 8, new QTableWidgetItem(QString::number(joinedData[i].tolerance.getMoreThanFive())));
}
Also, based on row and column information, i paint some of these tablewidgetitems to some colors, but i don't think it's relevant.
I reimplemented the QDialog's contextMenuEvent, to obtain the right clicked tableWidgetItem's row and column coordinates:
void BobinFlanView::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu(m_ui->tableWidget);
//standard actions
menu.addAction(this->markInactiveAction);
menu.addAction(this->markActiveAction);
menu.addSeparator();
menu.addAction(this->exportAction);
menu.addAction(this->exportAllAction);
//obtain the rightClickedItem
QTableWidgetItem* clickedItem = m_ui->tableWidget->itemAt(m_ui->tableWidget->mapFromGlobal(event->globalPos()));
// if it's a colored one, add some more actions
if (clickedItem && clickedItem->column()>1 && clickedItem->row()>0)
{
//this is a property, i'm keeping this for a later use
this->lastRightClickedItem = clickedItem;
//debug purpose:
QMessageBox::information(this, "", QString("clickedItem = %1, %2").arg(clickedItem->row()).arg(clickedItem->column()));
QMessageBox::information(this, "", QString("globalClick = %1, %2\ntransformedPos = %3, %4").arg(event->globalX()).arg(event->globalY())
.arg(m_ui->tableWidget->mapFromGlobal(event->globalPos()).x()).arg(m_ui->tableWidget->mapFromGlobal(event->globalPos()).y()));
menu.addSeparator();
menu.addAction(this->changeSelectedToleranceToUygun);
menu.addAction(this->changeSelectedToleranceToUyar);
menu.addAction(this->changeSelectedToleranceToDurdurUyar);
//... some other irrevelant 'enable/disable' activities
menu.exec(event->globalPos());
}
The problem is, when i right click on the same item i get the same global coordinates, but randomly different row-column information. For instance, the global pos is exactly 600,230 but row-column pair is randomly (5,3) and (4,3). I mean, what?!
Also, when i click to an item from the last to rows (later than 13, i guess) will never go into condition "if (clickedItem && clickedItem->column()>1 && clickedItem->row()>0)", i think it's mainly because 'clickedItem' is null.
I'll be more than glad to share any more information, or even the full cpp-h-ui trio in order to get help.
Thanks a lot.
Try this:
QTableWidgetItem* clickedItem = m_ui->tableWidget->itemAt(event->pos());
The problem is that you are trying to map the global position to the table widget position, without considering the scrollable area. To map the global position into something you can pass to itemAt, use tableWidget->viewport()->mapFromGlobal.
Came here to thank Lukáš Lalinský, as his explanation put an end to half a day of misery for me.
In my case, itemAt() when fed with QDropEvent::pos(), was returning items from a table row, offset by 1.
The reason was that I had a horizontal header, which shrunk the viewport, same with the vertical header, but since it was very narrow, I did not realize it caused an offset too.
The fix was to call QTableWidget's viewport()->mapFromParent( [drop pos] ), as the position was in QTableWidget coordinates already.
Again, thanks A LOT!