Qt: TableWidget's ItemAt() acting weirdly - c++

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!

Related

How to stop expansion of QGridLayout element

I have couple of labels (dynamically generated on button click and can vary in numbers which read from configuration file) and added to QGridLayout. Upon load which looks like below ...
these labels are generated and added as below
QGridLayout * layout = new QGridLayout(ui->pageInputWindow);
layout->setSpacing(5);
layout->setMargin(0);
ui->pageInputWindow->setLayout(layout);
// for question purpose I take number of row and col to 3
// but in actual they will be read from configuration file
for(int row = 0; row < 3; row ++)
{
for(int col = 0; col < 3; col++)
{
QLabel * placeHolder = new QLabel(ui->pageInputWindow);
placeHolder->setText("LABEL "+QString::number(10*(row+1) + col + 1));
layout->addWidget(placeHolder, row, col);
}
}
These label are placed here for rendering video streaming by setting pixmap of captured video frames when selected. I am setting pix as ....
// I am getting img object from other class, below code is just a snap of it.
// You can assume img as valid QImage objcet
QImage img
selectedLabel->setPixmap(QPixmap::fromImage(img).scaled(selectedLabel->size(),Qt::KeepAspectRatio, Qt::FastTransformation));
My ideal scenario is when I select any label it will remain with size which it got when loaded according to configuration settings at runtime. But when I start streaming below event happened.
Suddenly selected label ( see black pictured label at 'LABEL 11' position) starts expansding to much even causing application to expand. Idealy I want above event as below ( assume white background is video frame ) without any expansion.
My question is how can I stop this undesirable expansion of labels while setting pix map on them ?
Set the labels' size policies to ignored:
placeHolder->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);

How to appropriately get position of QGraphicsRectItem after drag-release?

I wanted to have an online monitoring system that could tell where the shape is currently, but am getting very weird coordinates of the item, also the dimensions of it get higher by 1 each time I create new one and drag it.
Initial position (map size is 751 by 751, checked by outputting to qDebug(), scene bound to yellow space) :
Dragging it to the left top corner.
As you can see in the beginning it was on (200;200), but after dragging it is on (-201;-196). After deleting it and creating new shape on the same position with the same properties, new shape can't be seen because it is outside of the map, which suggests that edits don't show correct data.
Here is the code of updating the edits:
void CallableGraphicsRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsRectItem::mouseReleaseEvent(event);
ptr->updateEdits(this);
}
Here is what I managed to cut down into updateEdits():
void MainWindow::updateEdits(QAbstractGraphicsShapeItem* item)
{
//stuff not related to scene
auto posReal = item->scenePos();
auto pos = posReal.toPoint();
//create QString from coordinates
QString coordinate;
coordinate.setNum(pos.x());
ui->leftXEdit->setText(coordinate);
coordinate.setNum(pos.y());
ui->upperYEdit->setText(coordinate);
//get width and height for rect, radius for circle
auto boundingRectReal = item->sceneBoundingRect();
auto boundingRect = boundingRectReal.toRect();
ui->widthEdit->setText(QString::number(boundingRect.width()));
//disables height edit for circles, not really relevant
if (!items[currentShapeIndex].isRect)
{
ui->heightEdit->setDisabled(true);
}
else
{
ui->heightEdit->setDisabled(false);
ui->heightEdit->setText(QString::number(boundingRect.height()));
}
}
Here is how I anchor the QGraphicsScene to the left top corner of the yellow area:
scene->setSceneRect(0, 0, mapSize.width() - 20, mapSize.height() - 20);
ui->graphicsView->setScene(scene);
How can I report the right data to the edits?
You're better off overriding the itemChange method and using the ItemPositionHasChanged notification. You have to set the ItemSendsGeometryChanges flag on the item so that it receives these notifications.
I'm not sure that your item's final position has been set when you're still in the mouseReleaseEvent method. Tracking it in itemChange will ensure that the data is valid, and this kind of thing is what it's for.
Also, note that "pos" is in the item's parent coordinates, and "boundingRect" is in the item's coordinate space. You should use "scenePos" and "sceneBoundingRect" if you want to be sure you're using scene coordinates. If the item doesn't have a parent, then "pos" and "scenePos" will return the same values, but "boundingRect" and "sceneBoundingRect" will generally differ.

How to change the background color from the header(horizontal / vertical) QTableWidget on Qt?

I would like to know how to change the background color from the headers (horizontal / vertical) from the object QTableWidget on Qt.
I already Know how to change all the headers together, using :
ui->tableWidget->setStyleSheet("QHeaderView::section {background-color:red}");
But I need change individually the items. Obviously if this is possible .
There are at least 2 ways to solve this problem. Very easy one:
Just use setHeaderData() and set specific colors for specific sections.
QTableView *tview = new QTableView;
QStandardItemModel *md = new QStandardItemModel(4, 4);
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
QStandardItem *item = new QStandardItem(QString("row %0, column %1").arg(row).arg(column));
md->setItem(row, column, item);
}
}
tview->setModel(md);
tview->model()->setHeaderData(0,Qt::Horizontal,QBrush(QColor("red")),Qt::BackgroundRole);
tview->show();
But u nfortunately it will not work on some systems... Qt uses the platform style. For example, my Windows doesn't allow to change header's color. So this code doesn't work on my machine. Fortunately, it can be solved easily with changing global style. So next code works:
//... same code ...
tview->show();
QApplication::setStyle(QStyleFactory::create("Fusion"));
If you don't want to change style, then you should create your own HeaderView. Probably, something similar as here.

Shuffling rendered primitives

This is just an experiment I'm doing, but suppose that we have three rectangles that are drawn:
void thisDialog::paintEvent(QPaintEvent *ev)
{
rectangle = new QRect(40, 80, 40, 90);
painter = new QPainter(this);
brush = new QBrush(QColor(QColor::green);
painter->fillRect(...);
painter->drawRect(...);
rectangle2 = new QRect(140, 80, 40, 90);
// and draw
rectangle3 = new QRect(240, 80, 40, 90);
// and draw...
}
And now I want to somehow shuffle the rectangles drawn. My solution is to use std::random_shuffle. The plan is we push all the rectangles into a vector thereof:
vector<QRect*> vecRect;
vecRect.push_back(rectangle);
// push back other rectangles
std::random_shuffle(vecRect.begin(), vecRect.end());
At this point, the order of the rectangles have been shuffled. The only problem is that although they're shuffled I have to somehow reflect that in their positions and also update what's shown on-screen for the changes to be apparent. So, I propose creating a function called shuffle. I want only the x-values to change, so the y-values and the spacings of the rectangles should be left alone.
void shuffle()
{
std::random_shuffle(vecRect.begin(), vecRect.end());
for (auto it : vecRect)
{
// do something to the x-values in rectangle; e.g.
it->setX(/*set something...*/);
}
}
To shuffle the rectangles along the x-axis and maintain the y-axis, and maintain the spacing in between I'd have to get a random number and add the spacing and ensure that it is within the window boundaries say 800x600.
Since I am new to Qt I have to know how Qt handles primitive positions, and so I guess that setX() from the docs would modify the x-position of the rectangle. My other enquiry is: how do we update the positions so they're reflected on what's drawn on-screen? Whenever I update positions it doesn't seem to take effect. I've tried update() but that doesn't seem to work.
Btw, I call the foresaid function through a key press, so if the key 'S' is pressed then the function is called.
TLDR; How can I shuffle x-positions in these rectangles and update their positions rendered?

lag in GUI created in Qt (verified by QTime)

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);