Qt SpinBox causes program to not respond - qt

I have a QSpinBox that changes the coloring of an QImage on a scene. Everything works just fine. The color updates correctly. If i hold down the arrow on my QSpinBox everything works fine. I do have a problem when I hold down the the arrow on my QSpinBox for a very long time. When I hold it for about a minute or so, my application eventually stops responding and sometimes the image disappears. I was wondering if anyone knew what could be potentially causing this. Is it possible that my application gets too bogged down with signals? if so, how do i fix this?
Thanks for your help!
Here is a snippet of code. I haven't included the stuff for setting each pixel value. that I know I'm doing correctly. The changeMinColor is one of the slots for the signal of a spinbox.
void binFileDialog::changeMinColor(double value)
{
lowColorValue = value;
ui->maxColorSpin->setMinimum(lowColorValue + .0001);
setBinScene();
}
void binFileDialog::setBinScene()
{
float lowValue = lowColorValue;
float highValue = highColorValue;
QImage img = QImage(bFile.ncols, bFile.nrows, QImage::Format_RGB32);
// go through and set call img.setPixel with rgb values based on contents of bFile
// and the min and max colors lowValue and highValue.
QPixmap pm = QPixmap::fromImage(img);
QGraphicsScene *scene = new QGraphichsScene;
ui->graphicsView->setSceneRect(0,0, bFile.ncols, bFile.nrows);
scene->addPixmap(pm);
ui->graphicsView->setScene(scene);
}
changeMinColor is connected to the QSpinBox's valueChanged signal:
connect(ui->minColorSpin, SIGNAL(valueChanged(double)),
SLOT(changeMinColor(double)));
I have also noticed that as I hold down the spinbox my memory increases. This has to be wrong. What am I forgetting? Thanks again for the help.

setBinScene() creates a new QGraphicsScene each time which is never deleted. As each value change of the spinbox calls setBinScene(), your code piles up leaked QGraphicsScene objects.
I'd suggest to avoid recreating the scene all together and just update a QGraphicsPixmapItem instead:
Initialize the scene (once):
QGraphicsScene *scene = new QGraphicsScene(this);
m_pixmapItem = new QGraphicsPixmapItem;
scene->addItem(m_pixmapItem);
ui->graphicsView->setScene(scene);
to set/update the image:
m_pixmapItem->setPixmap(pm);
ui->graphicsView->setSceneRect(0,0, bFile.ncols, bFile.nrows); //might want to avoid this one if the dimensions do not change

Related

create a QThread to make changes

I'm trying to monitor changes in a folder using QFileSystemWatcher, and whenever changes happen they will be reflected in the GUI.
The idea is that I have a folder that gets updated every second with new images, and I need to display those images in sequence
nevertheless, the changes happen very fast, and the GUI freezes.
how can I make this line run in a different thread whenever there's a change in the folder.
QObject::connect(&watcher, SIGNAL(directoryChanged(QString)), w, SLOT(showModified(QString)));
or how can I make the code that creates reflections on the GUI run in a separate thread?
To make QFileSystemWatcher work in a separate thread you can do:
Create new thread - auto thread = new QThread;
Create new file system watcher - auto watcher = new QFileSystemWatcher;
Move watcher to thread - watcher->moveToThread(thread);
Make connections
connect(thread, &QThread::finished, watcher, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
connect(watcher, &QFileSystemWatcher::directoryChanged, w, &<YourWidgetClassName>::showMidified, Qt::QueuedConnection);
Start thread thread->start();
Setup watcher with addPath or addPaths
Do not forget call thread->quit(); when it's not needed or before application exits
So about problem of bunch images rendering in GUI.
I suggest you to use QImage to load and scale images for QPixmap to be created and rendered in GUI. Here QtConcurrent::run can be used per image to preform an image loading in a separate thread. It returns a QFuture object, that can be tracked with a QFutureWatcher object, that emits a finished signal, when a watched future is complete. Once connected to a QFutureWatcher::finished signal you can handle QFuture's result to assign a loaded QImage to QPixmap.
So, you need a code like this for QFileSystemWatcher::directoryChanged signal handler:
QStringList filenames = <some code to get new image filenames to render>;
auto loadImage = [](const QString &aFilename) -> QPixmap {
auto image = QImage(aFilename).scaled(200, 200, Qt::KeepAspectRatio, Qt::SmoothTransformation);
return QPixmap::fromImage(image);
};
for (auto &filename : filenames) {
auto pixmap = QPixmap(200, 200); //create empty pixmap of desired size
auto label = new QLabel;
label->setPixmap(pixmap);
m_layout->addWidget(label);
auto future = QtConcurrent::run(loadImage, filename);
auto watcher = new QFutureWatcher<QPixmap>(this);
connect(watcher, &QFutureWatcher<QPixmap>::finished, this, [watcher, label]() { label->setPixmap(watcher->result()); });
connect(watcher, &QFutureWatcher<QPixmap>::finished, watcher, &QFutureWatcher<QPixmap>::deleteLater);
watcher->setFuture(future);
}

Qt - Dynamically create, read from and destroy widgets (QLineEdit)

I have the following situation:
I have QSpinBox where the user of my application can select how many instances of an item he wants to create. In a next step, he has to designate a name for each item. I wanted to solve this problem by dynamically creating a number of QLabels and QLineEdits corresponding to the number the user selected in the SpinBox. So, when the number is rising, I want to add new LineEdits, when the number falls, I want to remove the now obsolete LineEdits.
Well, guess what - this turns out much more difficult than I expected. I've searched the web, but the results were more than disappointing. There seems to be no easy way to dynamically create, maintain (maybe in a list?) and destroy those widgets. Can anybody point me in the right direction how to do this?
Take a while and check QListWidget, it does what you exactly want for you by using QListWidgetItem.
An little example: this function adds a new element to a QListWidgetwith a defined QWidget as view and return the current index:
QModelIndex MainWindow::addNewItem(QWidget* widget) {
QListWidgetItem* item = new QListWidgetItem;
ui->listWidget->addItem(item1);
ui->listWidget->setItemWidget(item, widget);
return ui->listWidget->indexFromItem(item);
}
Now, if your user selects X items, you should iterate to create X widgets and you could save all the widgets in a QList:
listWidget.clear();
for (int i=0; i<X; i++) {
QTextEdit* edit = new QTextEdit();
const QModelIndex& index = addNetItem(edit);
qDebug() << "New element: " << index;
listWidget.append(edit);
// Handle edit text event
connect(edit, SIGNAL(textChanged()), this, SLOT(yourCustomHandler()));
}
Now, just show the list with all the edit fields.

How can I simulate mouse clicks by posting events to the Qt event system?

I would like to do a rudimentary automation test of my Qt application. It records mouse events and writes them to a file (f.e. mousepress(300, 400)). When starting the automation, it reads the coordinates from the file, sends the appropriate mouse events and would do a pixel comparison with a previously saved screenshot.
Currently, I have an overlay widget that spans the app and has transparent mouse events. All it does is track the coordinates.
When reading the data back in, that overlay paints a rectangle on the mouse press location.
I need help when sending mousePressEvents to Qt's event system. It draws the points on the correct location but never actually does the physical click.
Is there a way to do this with Qt or would I have to use Window's SendInput()?
Is there a way to pause and wait until the mouse event has finished? I would need to know when the event is complete in order to start the pixel by pixel comparison.
Widget::Widget( QWidget *parent )
: QFrame( parent )
, mPoint(QPoint(0,0))
{
setWindowFlags(Qt::WindowStaysOnTopHint);
setStyleSheet("background-color: rgba(0, 0,255, 2%);");
setAttribute(Qt::WA_TransparentForMouseEvents, true);
setGeometry(parent->geometry());
...
}
void Widget::run()
{
QFile file( "coordinates.txt", NULL );
if(!file.open( QIODevice::ReadOnly ))
return;
QTextStream in(&file);
int i = 0;
while (!in.atEnd())
{
QString line = in.readLine();
if(line.startsWith("mousepress"))
{
int startIndex = line.indexOf('(');
int endIndex = line.indexOf(')');
QString coord = line.mid(startIndex+1, line.size() - startIndex - 2);
QStringList nbr = coord.split(',');
mPoint = QPoint(nbr[0].toInt(), nbr[1].toInt());
QWidget *receiver = QApplication::widgetAt(mPoint);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, mPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::postEvent(receiver, event); // same result with sendEvent()
QCoreApplication::processEvents();
update();
// wait till the event finished, then continue with next point
}
}
}
void Widget::paintEvent(QPaintEvent *event)
{
QPainter p( this );
QPen pen;
pen.setBrush(Qt::NoBrush);
if(!mPoint.isNull())
{
pen.setColor(Qt::red);
pen.setWidth( 2 );
p.setPen(pen);
p.drawRoundRect(mPoint.x(), mPoint.y(), 10, 10, 25, 25);
p.drawText(mPoint, QString::number(mPoint.x()) + ", " + QString::number(mPoint.y()));
}
}
[Edited]
I followed ddriver's suggestion and it works after a few changes: I save global and local positions in the file, to send to the QMouseEvent.
How could I be sure that the mouse click is complete before doing a screenshot and comparing it to a saved image?
void Widget::DoStep()
{
if(!mInStream.atEnd())
{
QString line = mInStream.readLine();
if(line.startsWith("MouseButtonPress"))
{
QPoint pos = parseGlobalPos();
QPoint localPos = parseLocalPos();
QWidget *receiver = QApplication::widgetAt(pos);
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress,localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, event);
QMouseEvent *eventRelease = new QMouseEvent(QEvent::MouseButtonRelease, localPos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(receiver, eventRelease);
// after successful click, take screenshot and compare them
}
}
if (!mInStream.atEnd())
QMetaObject::invokeMethod(this, "DoStep", Qt::QueuedConnection);
else
file.close();
}
If I understand the problem correctly, its source is the blocking while loop, which blocks the thread and doesn't allow the event loop to spin and process events. There is no way to "pause" as that would block the event loop as well, and it wouldn't be able to do the work either, but there is a way to split the work to be done one step at a time, one event loop cycle at a time.
The solution is to not use a blocking while loop but implement an event system driven loop. Then you process one line on every event loop cycle until you run out of work.
Move the file, stream and all that stuff outside of the function, make them class members so they persist. Then in run() you simply setup the input for reading, and move all the event posting stuff to a new function, for example doStep(), and in run() after you setup you have a:
QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
In doStep(), aside from the event stuff, at the end you also do scheduling:
if (!in.atEnd()) QMetaObject::invokeMethod(this, "doStep", Qt::QueuedConnection);
else // we are done, close file, cleanup, whatever
This way only one step will be executed per event loop cycle, allowing the event loop to spin and process the events. While there is work, a step will be scheduled for the next event loop cycle. You can also use a single shot timer to do this, or even a queued connection, which you can trigger by emitting overloading the event to emit a signal on completed. You do not, and should not force events to be processed, using this approach there will be no need to.
The concept in general is about a non-blocking async event driven self scheduling worker, I have posted a fully implemented one here. The upsides are you can track progress, pause, cancel and all that good stuff.
Try sending event to QGraphicsView's viewport:
qApp->sendEvent(view->viewport(), &mousePressEvent);

Destroy and rebuild object on button clicked in Qt

This is a basic example that I need to get to work to use it in my project.
I need someone to help to destroy and rebuild an object on a button click. What I mean is:
//mainwindow.cpp
{//constructor
ui->setupUi(this);
/*private*/frame = new QFrame(ui->centralWidget);
/*private*/temp = new QPushButton(frame);
QPushButton ok = new QPushButton(ui->centralWidget);
ok->setGeometry(100,100,50,50);
connect(ok, SIGNAL(clicked()), SLOT(des()));
}
{//slot des()
temp->~QPuhsButton();
temp = new QPushButton(frame);
}
See, all I need is temp to be destroyed and rebuilt.
The line temp = new QPushButton(frame); is not working because with or without it, temp disappears from the layout, meaning temp->~QPuhsButton(); is working.
Now, the reason this is bothering me is because this works:
{//constructor
ui->setupUi(this);
frame = new QFrame(ui->centralWidget);
temp = new QPushButton(frame);
temp->~QPuhsButton();
temp = new QPushButton(frame);
/*QPushButton ok = new QPushButton(ui->centralWidget);
ok->setGeometry(100,100,50,50);
connect(ok, SIGNAL(clicked()), SLOT(des()));*/
}
I tried this last piece of code to see if it was possible to destroy and rebuild the object the way I was trying to with a button being clicked. Turns out it is, this time temp = new QPushButton(frame); is working and the button stays there.
EDIT:
thanks for the answer and the comment, but I am sorry because I didnĀ“t realize something before asking the question.
The buttons were being deleted/destroyed, they were just not being "repainted" inside the frame when I write temp = new QPushButton(frame); again, actually they still are not. Help regarding this new problem and again sorry.
Don't manually call destructors, unless you are using memory pools and placement new.
In Qt it is best to use delateLater() in order to avoid errors from unprocessed events. Also, widgets are by default created hidden, so you need to show() the widget.
So your code should be:
{//slot des()
if (temp) temp->deleteLater();
temp = new QPushButton(frame);
temp->show();
}
you can use delete to destroy objects. destroy method can only be called by the object itself. and qwidgets can be automatically recycled by the application. use pointer to point to the new memory space.

Catching/Connecting QPushButtons inside a QTableWidget to a function

Im a student developer using Qt to build a GUI to help users plot specific columns of data located in multiple files. The feature I'm setting up allows users to select a file using a button in each row. So the button originally would say browse and then user clicks it to open a dialog to select a file then the button text is replaced with the file name selected. Sorry for the story; my simple attempt to add some clarity.
The problem I'm having is I'm not sure how to set a policy up for the button clicked. I'd imagine that I'd have to extend the functionality of each of the QPushButtons but I don't really know how to do that. So far I am using the following to set the cell widget.
//with row count set dimensions are set becasue column count is static
//begin bulding custom widgets/QTableWidgetItems into cells
for(int x = 0; x < ui->tableWidgetPlotLineList->rowCount(); x++)
{
for(int y = 0; y < ui->tableWidgetPlotLineList->columnCount(); y++)
{
if(y == 1)
{
//install button widget for file selection
QPushButton *fileButton = new QPushButton();
if(setDataStruct.plotLineListData.at(rowCount).lineFileName != "");
{
fileButton->setText(setDataStruct.plotLineListData.at(rowCount).lineFileName);
}
else
{
fileButton->setText("Browse...");
}
ui->tableWidgetPlotLineList->setCellWidget(x, y, fileButton);
}
I was thinking that
connect(ui->tableWidgetPlotLineList->row(x), SIGNAL(fileButton->clicked()), this, SLOT(selectPlotLineFile(x));
might do the trick but I think I'm probably going in the wrong direction here. Honestly I'm not even too sure as to where it would go...
Thanks so much for reading my post. Please let me know if there is anything lacking from this post and I will update it immediately. I'd also like to thank any contributions to this post in advance!
connect(ui->tableWidgetPlotLineList->row(x), SIGNAL(fileButton->clicked()), this, SLOT(selectPlotLineFile(x));
Is not syntactically correct for a signal/slot connection. Something like this would be more appropriate:
connect(fileButton, SIGNAL(clicked()), this, SLOT(selectPlotLineFile(x));
...
If you need access to the specific button that emited the clicked() signal than you could use the sender() function in your slot:
void selectPlotLineFile() {
QPushButton *button = dynamic_cast<QPushButton*>( sender() )
}
Now you may be wondering how you know which row to operate on. There are several different approaches, one of the easier ones being to maintain a QMap<QPushButton*, int> member variable that you can use to lookup which button belongs to which row.

Resources