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);
}
Related
I want the progress of the function to be displayed on progressBar. Following the guides, I wrote the code below. But during the execution of the function, the program freezes, then progress bar's value becomes equal to one.
The program itself will not produce errors. The result of the function is correct.
I think my problem is that I don’t know how to connect the value of the progress of a function with the value of a progress bar.
(Form.h)
public:
MyObject object;
QFutureWatcher<QBitArray> FutureWatcher;
QFuture<QBitArray> future;
(Form.cpp) Form constructor body:
ui->setupUi(this);
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(100);
connect(&this->FutureWatcher, SIGNAL(progressValueChanged(int)), ui->progressBar, SLOT(setValue(int)));
(Form.cpp) Function on_button_clicked():
void Form::on_button_clicked()
{
QString message = ui->textEdit->toPlainText();
future = QtConcurrent::run(&this->object, &MyObject::longFunction, message);
this->FutureWatcher.setFuture(future);
QBitArray bitresult = future.result();
}
The problem is that you're immediately calling future.result(). The problem with this is that result() will wait until the QFuture has finished.
The Qt documentation says (https://doc.qt.io/qt-5/qfuture.html#result):
If the result is not immediately available, this function will block and wait for the result to become available.
The solution is to connect a slot to QFutureWatcher::finished():
auto *watcher = new QFutureWatcher<QBitArray>(this);
connect(watcher, &QFutureWatcher::finished, this, [=]() {
auto result = watcher->result();
// do something with the result
// important: delete watcher again
watcher->deleteLater();
});
// now start the task
QString message = ui->textEdit->toPlainText();
watcher->setFuture(QtConcurrent::run(myObject, &MyObject::longFunction, message));
I am using QProcess::start to launch Qt Assistant with my custom help project file. It works fine until i load project(not help project file) to my programm. Programm generates images from specific data using custom library. Even when all processes ends and i see generated images and nothing else happens, when i trying to launch Qt Assistant, my programm hangs at QProcess:start function when trying to start process. The code is:
show() function(public):
if (!run())
return false;
QByteArray ba("setSource ");
ba.append("qthelp://insyn_help/doc/");
proc->write(ba + page.toLocal8Bit() + '\n');
return true;
run() function(private):
if (!proc)
proc = new QProcess();
if (proc->state() == QProcess::Running)
return true;
QString app = QString(QT_BIN_DIR) + QDir::separator() + QString("assistant");
QString path = QString(PREFIX) + QString(HELP_INSTALL_PATH) + QString("/help_project.qhc");
QStringList args;
args << QLatin1String("-collectionFile")
<< QLatin1String(path.toLatin1())
<< QLatin1String("-enableRemoteControl");
QFileInfo help_project(path);
if (help_project.exists()) {
proc->start(app,args);
if (!proc->waitForStarted()) {
m_exitCode = 1;
emit closed();
return false;
}
}
This code is a part of AssistantLauncher class which was registered using qmlRegisterType and added to main.qml as a member of application window. My programm doesn't touch it anywhere (except calling a method show()). It is separate object (except it is a part of appWindow). The question is why does the process can not start only after my programm did some work? And why QProcess::start even dont have timeout.
UPD: I moved proc->start(app,args); to the child process, which i getting by using fork() and now my programm hangs on pid_t child = fork(). So the problem is that new process can not be created.
The answer is to do not use fork() because it is dangerous in big projects. More at http://www.evanjones.ca/fork-is-dangerous.html . posix_spawn also hangs my project. Now i decided to fork() new process at the beginning and send commands to it through the pipe.
I am showing a image in qt label. Below is my code:
void MyClass::onPushButtonClicked(QString myurl)
{
this->setCursor(Qt::WaitCursor);
ui.qtImageLabel->clear();
qDebug()<<QTime::currentTime()<<"MyClass: onPushButtonClicked";
QNetworkAccessManager *qnam_push_button_clicked_show_image;
QNetworkReply *reply;
QNetworkRequest request;
request.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
QUrl url(myurl);
request.setUrl(url);
qnam_push_button_clicked_show_image = new QNetworkAccessManager(this);
if(qnam_push_button_clicked_show_image)
{
QObject::connect(qnam_push_button_clicked_show_image, SIGNAL(finished(QNetworkReply*)),
this, SLOT(onPushButtonClickedRequestCompleted(QNetworkReply*)));
reply = qnam_push_button_clicked_show_image->post(request, url.encodedQuery());
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
}
}
void MyClass::onPushButtonClickedRequestCompleted(QNetworkReply *reply)
{
qDebug()<<QTime::currentTime()<<"MyClass: onPushButtonClickedRequestCompleted request completed";
if (reply->error() != QNetworkReply::NoError)
{
qDebug() << "Error in" << reply->url() << ":" << reply->errorString();
this->setCursor(Qt::ArrowCursor);
return;
}
QByteArray data = reply->readAll();
QPixmap pixmap;
pixmap.loadFromData(data);
int width;
int height;
//application size can be changed
QRect rec = QApplication::desktop()->screenGeometry();
height = rec.height();
width = rec.width();
qDebug()<<QTime::currentTime()<<width<<","<<height;
QSize *size = new QSize(width,height);
if(size)
{
QPixmap scaledPixmap = pixmap.scaled(*size);
ui.qtImageLabel->setPixmap(scaledPixmap);
}
if(size)
{
delete size;
size = NULL;
}
data.clear();
this->setCursor(Qt::ArrowCursor);
reply->deleteLater();
return;
}
On clicking push button It will send a request to server and will show a different image received from server. It is working fine if it does't exceeds 500 times. If it exceeds that first this error has been shown
QPixmap::scaled: Pixmap is a null pixmap
and it doesn't show the image. Then if someone again sends a request for an image then it shows the following error:
Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt. You must
re implement QApplication::notify() and catch all exceptions there.
I am not getting what is the error in the above code. Can someone please tell me how to solve this?
The obvious leak is qnam_push_button_clicked_show_image = new QNetworkAccessManager(this);, which doesn't have a balanced delete anywhere. QNAMs should typically created once, then reused for the lifetime of the application rather than created for a single request. So by turning qnam_push_button_clicked_show_image in a class member (same as ui) you'll fix both your leak and improve the efficiency of the code.
That said, I don't think that's what causes your QPixmap error. If you're running this code on X11, then QPixmap is backed by an X Pixmap resource, which is limited by various factors (software and hardware). Even though from your code there's no obvious leak, it could be that repeatedly allocating large pixmaps slowly fragments the memory pool managed by X, up to the point where it can't allocate a block large enough for the scaled pixmap and then triggers the error. Or it could be a driver bug somewhere in the graphics stack. Have you tried if changing the scaled size increases or decreases the limit before it starts breaking? If so, switching to QImage might help relieving the pressure on X.
Aside from that, the code could use some cleanup, especially that superfluous QEventLoop usage. I'm guessing it's a way to prevent the button from being clicked several times until the new image has been loaded, but I'd much rather implement this using button.setEnabled(false) while the image is downloading, because nested event loops combined with network events is a recipe for countless reentrancy issues and hard to debug crashes/bugs.
I'm also confused about why size is allocated on the heap , especially when it's deleted right after, and these if (size) are really confusing, as they can be understood as if (size->isValid()) while what they really mean is if (size != nullptr), which is pretty much guaranteed as the chance of getting an OOM on that line is infinitesimally low. (if you did eventually run out of memory, my guess is it would likely happen in the readAll() or loadFromData() calls above).
ps: good luck pressing that button another 500 times to check if fixing the leak helped ;)
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
Normal status message -these are always shown by an application unless a temporary message is shown. This is what I know about normal status message. So using these code on my constructor
ui.statusbar->showMessage("Temp message", 3000); // ui is the Ui::AutoGenHeaderForForm
QLabel *label = new QLabel;
ui.statusBar->addWidget(label);
label->setText("hello world");
I get that, when I run my project I get the status Temp message for 3 sec. Then I don't get the hello world back. Should the hello world come automatically after 3 sec in the position of Temp message ?
Assuming the code you show is in the constructor of your main window, the problem might be due to events not being properly processed because the event loop is not yet started at the time of the main window creation.
Try to execute the showMessage in a "delayed initialization" slot, e.g.
QLabel *label = new QLabel;
ui.statusBar->addWidget(label);
label->setText("hello world");
QTimer::singleShot ( 0, this, SLOT(delayedInit() );
void MainWindow::delayedInit()
{
ui.statusbar->showMessage("Temp message", 3000); // ui is the Ui::AutoGenHeaderForForm
}
I think the documentation is pretty clear:
The widget is located to the far left of the first permanent widget
(see addPermanentWidget()) and may be obscured by temporary messages.