I've a dialog displaying progress bar + some other data, and I also have a cancel button on this dialog. While this dialog is displayed there is potentially heavy computation going on, which is show on progress bar. This computation is started from withing this dialog code so I have:
Counting_Progress_Dialog::Counting_Progress_Dialog(QWidget *parent) :
QDialog(parent)
{
setupUi(this);
thread_ = new Threaded;//THIS IS THE THREAD IN WHICH COMPUTATION IS BEING PERFORMED
connect(thread_,SIGNAL(counter_value(int)),this,SLOT(update_progress_bar(int)));
connect(this,SIGNAL(rejected()),thread_,SLOT(terminate()),Qt::QueuedConnection);//
HERE I'M CONNECTING REJECTED ON DIALOG TO TERMINATE ON THREAD
}
void Counting_Progress_Dialog::start()
{
thread_->start(QThread::LowestPriority);
}
and I do invoke this in part of the program:
void My_Class::dummy_()
{
auto old_priority = this->thread()->priority();
this->thread()->setPriority(QThread::HighestPriority);
Counting_Progress_Dialog progress;
progress.start();//this will start thread
progress.exec();//this will enter it's event loop
progress.wait();//this will wait until thread is finished
this->thread()->setPriority(QThread::NormalPriority);
}
But despite all this, when I press cancel on my dialog, the whole application freezes. What am I doing wrong? How to make it behave correctly?
UPDATED:
void Counting_Progress_Dialog::wait()
{
thread_->wait();
}
I see that you are connecting using 2 different strategies. But if thread_ and this(counting dialog) are really within two separated threads then the connection will always be Qt::QueuedConnection. Well that's not the issue.
progress.exec();//this will enter it's event loop
Calling exec() suspend the execution of dummy_() until the dialog have to return. And when the dialog return your thread is terminated. So I don't see the purpose of
progress.wait();//this will wait until thread is finished
By the way which function is that? the only one I know is Qthread::wait(). I am pretty confident the issue is here...
edit:
progress.wait() is not the issue... But it is possible that the events sent by the thread are causing trouble in some way. Use the debugger or some qDebug() to see if update_progress_bar is called after you push cancel.
Related
My goal is to show a brief "Please Wait..." dialog with an animated gif (spinner) in a Gtk::Dialog.
My problem is that when I do not use Gtk:Dialog::run(), the gif won't be animated, and when I do use the Gtk:Dialog::run() method it completely blocks my running code afterwards. And since I don't have any buttons in my dialog it would hang there indefinitely. Is there a way around that? I have had no success in getting the animated gif to work in a non-modal dialog, i.e without using the run() method.
I'm using gtkmm 3.0
Compile with : g++ examplewindow.cc main.cc -o main `pkg-config gtkmm-3.0 --cflags --libs`
main.cc
#include "examplewindow.h"
#include <gtkmm/application.h>
#include <iostream>
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create("org.gtkmm.example");
ExampleWindow window;
//Shows the window and returns when it is closed.
//return app->make_window_and_run<ExampleWindow>(argc, argv);
return app->run(window);
}
examplewindow.h
#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H
#include <gtkmm.h>
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
virtual ~ExampleWindow();
protected:
//Signal handlers:
void on_button_clicked();
//Child widgets:
Gtk::Box m_VBox;
Gtk::Box m_ButtonBox;
Gtk::Button m_Button;
};
#endif //GTKMM_EXAMPLEWINDOW_H
examplewindow.cc
#include "examplewindow.h"
#include <iostream>
ExampleWindow::ExampleWindow()
: m_VBox(Gtk::Orientation::ORIENTATION_VERTICAL),
m_ButtonBox(Gtk::Orientation::ORIENTATION_VERTICAL),
m_Button("Show Dialog")
{
set_title("Test animated gif");
set_default_size(800, 600);
add(m_VBox);
m_VBox.pack_start(m_ButtonBox);
m_ButtonBox.pack_start(m_Button);
m_Button.set_hexpand(true);
m_Button.set_halign(Gtk::Align::ALIGN_CENTER);
m_Button.set_valign(Gtk::Align::ALIGN_CENTER);
m_Button.grab_focus();
m_Button.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_button_clicked));
show_all_children();
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_button_clicked()
{
Gtk::Dialog m_Dialog;
m_Dialog.set_transient_for(*this);
m_Dialog.set_size_request(200, 200);
m_Dialog.set_decorated(false);
Gtk::Image imageLoading = Gtk::Image();
imageLoading.property_pixbuf_animation() = Gdk::PixbufAnimation::create_from_file("gtkmm_logo.gif");
m_Dialog.get_vbox()->pack_start(imageLoading);
m_Dialog.show_all();
m_Dialog.run();
/******** This, below, never gets executed as run() is blocking the program...********/
// Dummy "long" operation
for (int i = 0; i <= 2010101010; i++)
{
if (i == 2010101010)
std::cout << "Done" << std::endl;
}
m_Dialog.response(Gtk::RESPONSE_ACCEPT);
m_Dialog.hide();
}
Let us look at the original problem. You created a dialog, called show() on it, did some long-running process, then closed the dialog. The process worked, but your program froze during the processing. Why is that?
A graphical interface works by processing messages (events). Some events run off a timer, such as the ones that tell an animation to go to the next frame. Some are generated as needed, such as the ones that tell an image to draw the current frame. These events need to be both triggered and processed to be effective. You triggered the appropriate events with your call to show_all(), but you did not give your program a chance to handle those events.
You used a button click to start your long-running process. That click is an event that was handled by your main event handling loop. That loop then waited for the click to be fully handled before moving on to the next event. However, you have your long-running process in the handler. The main event loop had to wait for that process to finish before it could handle new events, such as the ones to show and animate your image. You never gave your dialog a chance to do its job before you destroyed it.
Calling the dialog's run() method partially fixed the situation by starting a new event loop for the dialog. So even though the main event loop was still blocked by your click handler, new events could be handled. The dialog's event loop received the events required to show an animation, hence your program was again responsive. Unfortunately, run() blocked your long-running process, so we're not really any better off.
The simplest fix is to no longer completely block your main event loop. You could have your long-running process periodically allow events to be processed via Gtk::Main::iteration(). This function invokes an iteration of the main event loop, allowing your program to stay responsive. Pass it a false argument so that it only processes events if there are some to process (rather than waiting for an event to occur).
for (unsigned long i = 0; i <= 2010101010; i++)
{
if (i == 2010101010)
std::cout << "Done" << std::endl;
// Periodically process events
if ( i % 10000 == 0 ) // <---- after some suitable amount of work
if ( !Gtk::Main::iteration(false) ) // <---- allow events to be processed
// Abort the work.
break;
}
The return value is supposed to tell you if you should quit or not, but I didn't get this working in my test (and the return value seemed to have the opposite meaning compared to the documentation). Maybe the dialog itself was keeping the app alive? Eh, that can be the next question, once this part is working.
Other approaches would move your long-running process out of the click handler. If you let the click handler end quickly, the main event loop can do its job without the extra prompting from you. However, this requires a few adjustments so that the Gtk::Dialog outlives the call to on_button_clicked(). That's a bit of refactoring, but it might be worth the time. I'll present two options (without code).
You could have your work operate on multiple timeout signals. Divide your long-running process into smaller chunks, each chunk suitably sized for a callback. (How big is that? Not sure. For now, let's say at most a few milliseconds.) Have the button click event start the first timeout signal with a priority that allows the GUI to update. (As I recall, PRIORITY_DEFAULT_IDLE should work.) For the interval, I would try 0 if that does not overly confuse Gtk+. (I have not tried it, but it seems plausible.) If the 0-interval works, it might be wise to use connect_once() instead of connect(), and have each chunk schedule the next with another timeout. The final chunk would be responsible for closing the dialog.
You could move your long-running process to another thread. Multi-threaded programming has its own set of problems and sometimes a lot of setup, but this is something it is well-suited for. If your long-running process is in a different thread than your main event loop, the operating system becomes responsible for making sure each thread gets some CPU time. Your long-running process can chug away, and the main event loop would simultaneously be able to process events with no special intervention from you.
Final notes:
If your dialog is for one-way communication to the user, it seems more like a monologue than a dialogue. Excuse me, more like an ordinary window than a dialog. Also, I'll make sure you are aware of Gtk::ProgressBar, which "is typically used to display the progress of a long running operation." Just an option; preferring your image is understandable.
I am working on a Qt-C++ based front-end app for a Raspberry Pi powered robot. I am using Qt version 5.9 along with libraries QSerialPort and Pigpio. In my app, when I give the run command for a command sequence to the robot, my Raspberry Pi starts a serial communication with a microcontroller in which it sends some message and then waits to receive a response. This sending and waiting causes the Mainwindow thread to freeze up. I am trying to build in a emergency stop functionality, which would stop the command execution in the middle of the run process.
Towards that effort, I tried to push my serial communication part to a separate thread(QThread). It didn't work out. Now I am trying to build the emergency stop part into a QDialog box that opens up when I give the run command, which contains a emergency stop QPushbutton. The Dialog box is being run in non-modal form. But in my current code, when I give the run command, a dialog box does open up, but the dialog box is completely blank and then closes up when the run command ends(which is intentional). I'll share some screenshots of the appearance.
Can you suggest where I might be going wrong? Or is there a better approach to this issue? Any criticism and suggestions are welcome!
Thanks!
One shouldn't block the main thread in the Qt. Everytime you call the blocking function, your GUI freezes, as well as Dialog boxes.
One solution is to use signal/slots. They blend really well into Qt. But doing a complicated request/response logic would require a huge state machine usually prone to errors.
Sometimes it is better to leave this code blocking, create a plain chain of request/response code, and put it in another non-GUI thread. Then use the signal to notify the main thread about the job result.
In order to stop the execution it is possible to use an atomic and check it between blocking steps. The biggest time delay before exiting the working function is the biggest delay of the single blocking function. You should carefully tune the timeouts. Or you can write your own function, which emulates timeout and a stop condition. It should check if incoming data is available in an infinite loop and check fro stop condition on each iteration, which must be a timeout AND a stop condition variable.
// pseudocode here
while (true) {
if (stopCondition) return; // check for emergency condition
it (currentTime - startTime > timeout) return;
if (serial->dataReady()) break;
}
auto data = serial->getData();
If a step can block forever, then this method can't be used.
There is an example with QtConcurrent framework, which demonstrates the use of QFuture and the work of a function in a separate thread without blocking the main thread. You can put all your communication logic inside it.
The code is example only!
#ifndef WORKERCLASS_H
#define WORKERCLASS_H
#include <QObject>
#include <QtConcurrent/QtConcurrent>
#include <QFuture>
class WorkerClass : public QObject
{
Q_OBJECT
public:
explicit WorkerClass(QObject *parent = nullptr) : QObject(parent) {
connect(&futureWatcher, &QFutureWatcher<void>::finished, [this] () {
emit workFinsihed();
});
}
void startWork(int value) {
atomic = 0;
future = QtConcurrent::run(this, &WorkerClass::workFunction, value);
futureWatcher.setFuture(future);
}
void stopWork() {
atomic = 1;
}
private:
QFuture<void> future;
QFutureWatcher<void> futureWatcher;
void workFunction(int value) {
for (int i = 0; i < value; ++i) {
if (atomic) return;
}
return;
};
QAtomicInt atomic{0};
signals:
void workFinsihed();
};
#endif // WORKERCLASS_H
I have this code:
mainwindow.h:
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow {
private:
QMutex mutex;
}
mainwindow.cpp:
void MainWindow::on_calculateBtn_clicked() {
QMutexLocker locker(&mutex);
qDebug() << "mutex has been locked" << endl;
ui->calculateBtn->setEnabled(false);
startProcess(); // huge calcutations
ui->calculateBtn->setEnabled(true); // performed before startProcess() has finished (why?)
qDebug() << "mutex will be unlocked" << endl;
}
If I click calculateBtn again while startProcess() has not finished, my program crashed:
pure virtual method called
The program has unexpectedly finished.
I tried:
void MainWindow::on_calculateBtn_clicked() {
if (!processing) {
processing = true;
ui->calculateBtn->setEnabled(false);
startProcess();
ui->calculateBtn->setEnabled(true); // performed before startProcess() has finished (why?)
processing = false;
}
}
There is no shared data, I just want one startProcess() will not be started before other startProcess() finished.
Why did it happen? I think that mutex have to lock function startProcess() in on_calculateBtn_clicked() and nothing should happens. It seems I don't know any important things. Thanks in advance for any advice.
The same mutex is locked twice from the same thread (the main thread, which contains the event loop), which is invalid for a non-recursive mutex.
But even a recursive mutex will not solve the basic problem of your code; you need a flag to indicate that you are already doing the calculations, and return from all subsequent calls to your method while they are running, else you'll start them multiple times in the same thread, one interrupting the other, probably with bad results. Even better, disable the button while the method is running and take care it isn't called by other ways.
However, if calling startProcess() multiple times and run it simultaneously is intended, you'll have to start a thread for each button press and take care for access to shared data (using mutexes, most probably) - that's where the real fun begins.
I think that you (by default) have a Qt::DirectConnection with this button press, right? i.e.:
connect(..., SIGNAL(...),
..., SLOT(:on_calculateBtn_clicked()), <by-default-Qt::DirectConnection>);
The issue I see here is that the first button press will run the function void MainWindow::on_calculateBtn_clicked() immediately.... which is all good so far, the mutex is locked and huge calcs are running.
However when you press the button again, void MainWindow::on_calculateBtn_clicked() is again immediate run (like an interrupt). The first thing it does is try to lock the mutex and it must hang here.
If you make connection to the slot void MainWindow::on_calculateBtn_clicked() Qt::QueuedConnection then it won't trigger the button press until it has clear the other tasks on its task queue.
but.... weather or not your design here is good is questionable, I think over you should re-think your strategy (as some comments have suggested)
EDIT
Oh yeah, meant to add..... to answer your question, therefore I don't think the mutex is begin unlocked twice... its just the nature of the direct connection
In my current project I need to perform some calculations when a specific button is pressed, and while I perform these calculations, I want to show a Gtk::MessageDialog that simply states that calculations are being performed. So, I initialize the MessageDialog like this (for the moment just ignore that I actually don't need the pointer here):
Gtk::MessageDialog *waitdialog;
Gtk::MessageDialog dia("Processing", false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_NONE, true);
dia.set_title("Wait.");
dia.set_transient_for(*(Gtk::Window *)this);
waitdialog = &dia;
Next I want to start a separate thread with the dialog:
std::thread dialog_thread(wait_dialog,waitdialog);
The wait_dialog method is defined as follows:
void wait_dialog(Gtk::MessageDialog *dialog){
dialog->run();
}
The problem now is, that even though the main window is darkened (because of set_transient_for), the message dialog is not visible. However, when I don't start a seperate thread, but just call waitdialog->run() instead, this will show the dialog properly (but will result in a loop).
So, the question is: why does the workaround with the separate thread not work? I can't make any sense of that :-(
GUI components are required to stay in the GUI loop. Your long running calculations belong in the thread. The calculation thread then signals back to the GUI thread to close the modal dialog. Also, you should use glib threads instead of std::threads. Here's how I would structure program:
// in header, member var dispatcher used to signal GUI thread
// and our member var thread
Glib::Dispatcher m_signalDone;
Glib::Thread* m_someThread;
...
// in constructor, hook up dispatcher event
m_signalDone.connect(sigc::mem_fun(this, &MyClass::OnDone));
...
// later when ready to kick off thread...
// show dialog or progess bar or something and kick off thread...
m_someThread = Glib::Thread::create(sigc::mem_fun(*this, &MyClass::CalcMethod), true);
...
void MyClass::CalcMethod()
{
// do your long running stuff...
// when done signal completion back to GUI
m_signalDone.emit();
}
...
void MyClass::OnDone()
{
// clean up dialog or progress bar or whatever
// kill thread
m_currentBackUpThread->join();
m_currentBackUpThread = NULL;
}
I want to monitor an Emergency Stop button in custom equipment attached to a Beaglebone, my code is developed with Qt 4.6.
At the moment, I successfully 'show' a message box (without any buttons) when the Emergency Stop button is pressed. What I want to do is to proceed with the program ONLY when the Emergency Stop button is released. The Button press/release emit separate signals on each event. However, using this code the EmergencyStopIsInactive signal is never detected.
QEventLoop loop;
connect(this, SIGNAL(EmergencyStopIsInactive()), &loop, SLOT(quit()));
loop.exec(QEventLoop::AllEvents);
qDebug() << "Emergency Stop Deactivated";
In fact, using breakpoints I can see that it is never generated. The Eventloop seems to not receive the signal.
If I comment out the loop.exec line, using breakpoints I can see that the code emits the signal. With the exec command back in, we never reach the breakpoints.
The exec() does not seem to be allowing the application to process events.
Can I get this to work the way I want? How?
Regards,
James
=======================================
Edit:
This is the code that generates the initial signal:
// Set up Emergency Stop Input
EmStop = new mita_gpio;
EmStop->initgpio_read(49);
connect(EmStop,SIGNAL(StateOutput(unsigned int)), this, SLOT(update_EmStop(unsigned int) ) );
connect(EmStop,SIGNAL(StateOutput(unsigned int)), Test_Screen, SLOT(update_EmStop(unsigned int) ) );
connect(this,SIGNAL(EmergencyStopIsInactive()), Probe_Screen, SLOT(quit() ) );
connect(Probe_Screen,SIGNAL(ShowEmergencyStopScreen()),this,SLOT(EmergencyStopScreenShow()) );
This signal is then chained to the following:
void Manager::update_EmStop(unsigned int state_value)
{
if (state_value == 1)
{
MitaData.EmergencyStop = 1;
emit EmergencyStopIsActive();
qDebug() << "Emergency Stop = 1";
}
else
{
MitaData.EmergencyStop = 0;
emit EmergencyStopIsInactive();
qDebug() << "Emergency Stop = 0";
}
}
SIGNAL(EmergencyStopIsInactive()) is executing in the main event loop of your program (I logically assume).
When you start a new event loop, you run it with the blocking function exec and thus despite your new loop is running, you are blocking the main event loop.
loop.exec(QEventLoop::AllEvents);
Since your signal is supposed to be sent from the main loop, that is blocked by your blocking function loop.exec, it will never be sent until the loop.exec is returning.
To solve that issue, whether generate the EmergencyStopIsInactive() signal from within the "loop" event loop, or put this second loop inside a separate thread.