I'm trying to pause the rendering in a Qt app on android when put into background. After the initial state change, the app is stuck as no updates reach the connection.
QScopedPointer<QQuickWindow> window ...
QSemaphore renderSemaphore(1);
bool releaseOnRender {};
QObject::connect(window.data(), &QQuickWindow::beforeRendering,
qApp, [&] {
releaseOnRender = renderSemaphore.tryAcquire(1, -1);
}, Qt::DirectConnection);
QObject::connect(window.data(), &QQuickWindow::afterRendering,
qApp, [&] {
if (releaseOnRender) {
renderSemaphore.release(1);
}
}, Qt::DirectConnection);
Qt::ApplicationState prevApplicationState = Qt::ApplicationState::ApplicationActive;
QObject::connect(qApp, &QGuiApplication::applicationStateChanged,
qApp, [&] (Qt::ApplicationState state) {
qDebug() << "Changed state to" << state;
if (state != prevApplicationState) {
if (state == Qt::ApplicationState::ApplicationSuspended) {
renderSemaphore.acquire(1);
qDebug() << "Freezing render";
} else {
renderSemaphore.release(1);
qDebug() << "Releasing render";
}
prevApplicationState = state;
}
}, Qt::DirectConnection);
My implementation stems from the suggestion for a related question.
Is there any chance to achieve this? Perhaps using a different approach?
Related
I'm junior programmer
recently, I have implemented grabbing of Image using Halcon library.
when I press live button, Timer start to grab image. it works but main screen freezes to the timer cycle.
so, I am improving performance grabbing of Image using Thread
first I implemented thread like this
[ImageUpdateWorker.h]
class ImageUpdateWorker : public QObject
{
Q_OBJECT
public:
explicit ImageUpdateWorker(QObject* parent = 0, QString strThreadName = "ImageUpdateWorker");
~ImageUpdateWorker();
signals:
void finished();
void grab();
public slots:
void run();
private:
bool m_bStop{ false };
};
[ImageUpdateWorker.cpp]
ImageUpdateWorker::ImageUpdateWorker(QObject* parent, QString strThreadName)
: QObject(parent)
{
setObjectName(strThreadName);
}
ImageUpdateWorker::~ImageUpdateWorker()
{
}
void ImageUpdateWorker::run()
{
while (m_bStop == false)
{
emit grab();
}
emit finished();
}
second I implemented inherited QWidget UI Widget with output Screen like this
m_pThread = new QThread();
m_pUpdateWorker = new ImageUpdateWorker(nullptr, strName);
m_pUpdateWorker->moveToThread(m_pThread); // UpdateWorker move to Thread
connect(m_pThread, SIGNAL(started()), m_pUpdateWorker, SLOT(run()));
connect(m_pThread, SIGNAL(finished()), m_pThread, SLOT(deleteLater()));
connect(m_pUpdateWorker, SIGNAL(finished()), m_pThread, SLOT(quit()));
connect(m_pUpdateWorker, SIGNAL(finished()), m_pUpdateWorker, SLOT(deleteLater()));
connect(m_pUpdateWorker, SIGNAL(grab()), this, SLOT(onGrab()));
when I call "m_pThread->start();" screen starts to blokcing :(
If you have any advice or information, I would appreciate it. thank you for reading.
Use m_pImageUpdateThread->moveToThread(m_pThread);
I don't know in QT.
I sent you the code I used in C#.
Mainly you must use the delegates if you don't want to freeze the GUI.
hdisplay is the object HalconDotNet:HWindowControlWPF.
camera is a class where I define the camera parameters.
inside camera.Grab there is the code:
HOperatorSet.GrabImage(out ho_Image, _AcqHandle);
h_Image = new HImage(ho_Image);
At the initialization there is the code:
// Initialise the delegate
updateLiveDelegate = new UpdateLiveDelegate(this.UpdateLive);
HImage ho_Image = new HImage();
Here the code I use:
// ==================
// LIVE
// ==================
bool stopLive = true;
// Declare the thread
private Thread liveThread = null;
// Declare a delegate used to communicate with the UI thread
private delegate void UpdateLiveDelegate();
private UpdateLiveDelegate updateLiveDelegate = null;
private void btnLive_Click(object sender, RoutedEventArgs e)
{
try
{
stopLive = !stopLive;
// if stopLive = false, live camera is activated
if (!stopLive)
{
// Launch the thread
liveThread = new Thread(new ThreadStart(Live));
liveThread.Start();
}
}
catch (Exception ex)
{
// Error
}
}
private void Live()
{
try
{
while (stopLive == false)
{
if (camera.Grab(out ho_Image))
{
// Show progress
Dispatcher.Invoke(this.updateLiveDelegate);
}
else
{
// No grab
stopLive = true;
}
}
// here stopLive is true
}
catch (Exception ex)
{
// Error
}
}
private void UpdateLive()
{
try
{
int imageHeight;
int imageWidth;
string imageType;
ho_Image.GetImagePointer1(out imageType, out imageWidth, out imageHeight);
hDisplay.HalconWindow.SetPart(0, 0, imageHeight - 1, imageWidth - 1);
// display
hDisplay.HalconWindow.DispImage(ho_Image);
}
catch (Exception ex)
{
// Error
}
}
I am trying to commicate with a motor controller via modbus with Qt/C++.
I connect to it using this code that I took mainly from Qt SerialBus adueditor example:
void Stepper::connect_device(){
if (ui.pb_connect->text() == "Connect"){
m_device = device;device->setParameters(0, 0x0000, 0x0000, 0x000F, 0x0000);
m_device->setConnectionParameter(QModbusDevice::NetworkAddressParameter, ui.tcpAddressEdit->text());
m_device->setConnectionParameter(QModbusDevice::NetworkPortParameter, ui.tcpPortEdit->text());
m_device->setTimeout(1000);
m_device->setNumberOfRetries(3);connect(m_device, &QModbusDevice::errorOccurred, this, [this](QModbusDevice::Error) {
qDebug().noquote() << QStringLiteral("Error: %1").arg(m_device->errorString());
reset();
/*QMessageBox msgBox;
msgBox.setWindowTitle("Modbus TCP Client");
msgBox.setText("Connection error !");
msgBox.exec();
emit ui.pb_connect->clicked();*/
return;
}, Qt::QueuedConnection);
connect(m_device, &QModbusDevice::stateChanged, [this](QModbusDevice::State state) {
switch (state) {
case QModbusDevice::UnconnectedState:
qDebug().noquote() << QStringLiteral("State: Entered unconnected state.");
ui.pb_connect->setEnabled(true);
ui.pb_connect->setText("Connect");
break;
case QModbusDevice::ConnectingState:
qDebug().noquote() << QStringLiteral("State: Entered connecting state.");
ui.pb_connect->setEnabled(false);
ui.pb_connect->setText("Trying to connect..");
break;
case QModbusDevice::ConnectedState:
qDebug().noquote() << QStringLiteral("State: Entered connected state.");
ui.pb_connect->setText("Disconnect");
ui.pb_connect->setEnabled(true);
break;
case QModbusDevice::ClosingState:
qDebug().noquote() << QStringLiteral("State: Entered closing state.");
ui.pb_connect->setEnabled(true);
ui.pb_connect->setText("Connect");
break;
case QModbusDevice::TimeoutError:
qDebug().noquote() << QStringLiteral("State: Time out error.");
QMessageBox msgBox;
msgBox.setWindowTitle("Modbus TCP Client");
msgBox.setText("Time out !");
msgBox.exec();
}
});
m_device->connectDevice();
}
else
{
disconnectAndDelete();
}}
Once the connection is established, I commission the drive using this tcp pdu = "00000004084301000000000000", the device is commissionned and an error with code "E047" occurs because the communication was interrupted. The problem is when I try to make a reset (which is a rising edge on the reset bit) I send these two consecutive frames, but it doesn't work, the error still remains.
void Stepper::reset(){
QModbusReply *reply = nullptr;
Data = "00000004084301000000000000";
QByteArray pduData = QByteArray::fromHex(Data.toLatin1());
reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(0x0010), pduData), 0x01);
connect(reply, &QModbusReply::finished, [reply, this]() {
qDebug() << "Receive: Asynchronous response PDU: " << reply->rawResult() << endl;
Data = "00000004084B01000000000000";
QByteArray pduData = QByteArray::fromHex(Data.toLatin1());
QModbusReply *reply = nullptr;
while (reply)
reply = m_device >sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(0x0010), pduData), 0x01);
});}
I took a look at this simulator "Modbus TCP Client V1.0.0.12" https://www.festo.com/net/cs_cz/SupportPortal/default.aspx?q=modbus&tab=4 that festo offers,
when I click "Start" button, the frame is sent permanently, then I can click on the reset bit and the error is eliminated. The communication is interrupted and the error occurs only when I click "Stop". When the communication is running I can do other things like clicking stop button or changing function code, is it using multithreading? In this case, how can I provide a continuous communication with the controller like this simulator interface?
click here to see modbus tcp simulator images
It finally worked, here is the code:
void Stepper::reset(){
QTimer *timer1 = new QTimer(this);
timer1->setSingleShot(false);
timer1->start(100);
connect(timer1, &QTimer::timeout, [this]() {
send_packet("00000004084301000000000000");
});
QTimer *timer2 = new QTimer(this);
timer2->setSingleShot(false);
timer2->start(100);
connect(timer2, &QTimer::timeout, [this]() {
send_packet("00000004084B01000000000000");
});}
The send_packet function is simply this function :
void Stepper::send_packet(QString Data){
QModbusReply *reply = nullptr;
QByteArray pduData = QByteArray::fromHex(Data.toLatin1());
reply = m_device->sendRawRequest(QModbusRequest(QModbusRequest::FunctionCode(0x0010), pduData), 0x01);
if (reply)
return;}
I'm coding in c++ with Qt creator. I can get all windows on the screen but I would like to detect their movement directly. Is there any way to detect the movement thanks to a signal emitted by the window ?
You can try creating a QWindow from the target window and then wrap it in a QWidget using QWidget::createWindowContainer().
You can have a look at this QTBUG thread : https://bugreports.qt.io/browse/QTBUG-40320
It needs some effort to make it work properly. The captured window doesn't keep its initial dimensions, and releasing the window behave weirdly. Read the last QTBUG comments to find an improvement.
I added an event filter to this code to capture the window position in real time. But that might be unsatisfying.
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter(){}
virtual ~EventFilter(){}
protected:
bool eventFilter(QObject *obj, QEvent *event);
} ;
bool EventFilter::eventFilter(QObject *obj, QEvent *event)
{
qDebug() << event->type() ;
if (event->type() == QEvent::Move) {
QMoveEvent *moveEvent = static_cast<QMoveEvent *>(event);
qDebug() << "position" << moveEvent->pos() ;
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
I removed part 3 from the QTBUG snippet and installed the event handler on the inner widget. You can also remove the timers.
// From https://bugreports.qt.io/browse/QTBUG-40320
int main(int argc, char *argv[])
{
// Windows: Find HWND by window title
WId id = (WId)FindWindow(NULL, L"Calculator");
if (!id)
return -1;
QApplication a(argc, argv);
// Optional
QTimer t;
t.start(2500);
// Part 1
QWindow* window = QWindow::fromWinId(id);
window->show();
window->requestActivate();
// Optional
QObject::connect(&t, &QTimer::timeout, [=]
{
qDebug() << "=== Inner QWindow ===";
qDebug() << "Geometry:" << window->geometry();
qDebug() << "Active?:" << window->isActive();
qDebug() << "Flags:" << window->flags();
});
// Part 2
QWidget* widget = QWidget::createWindowContainer(window);
widget->show();
// Optional
QObject::connect(&t, &QTimer::timeout, [=]
{
qDebug() << "=== Outer QWidget ===";
qDebug() << "Geometry:" << widget->geometry();
qDebug() << "Active?" << widget->isActiveWindow();
qDebug() << "Flags:" << widget->windowFlags();
});
// Realtime position
EventFilter filter ;
widget->installEventFilter( &filter ) ;
return a.exec();
}
Output:
=== Inner QWindow ===
Geometry: QRect(0,0 640x480)
Active?: true
Flags: QFlags<Qt::WindowType>(ForeignWindow)
=== Outer QWidget ===
Geometry: QRect(2489,29 640x480)
Active? true
Flags: QFlags<Qt::WindowType>(Window|WindowTitleHint|WindowSystemMenuHint|WindowMinMaxButtonsHint|WindowCloseButtonHint)
QEvent::Type(Move)
position QPoint(2484,29)
QEvent::Type(Move)
position QPoint(2481,30)
QEvent::Type(Move)
position QPoint(2478,31)
QEvent::Type(Move)
position QPoint(2474,31)
See also http://blog.qt.io/blog/2013/02/19/introducing-qwidgetcreatewindowcontainer/
I have a lot of signals which all have the same parameters but perform different functions.
The connect and disconnect code for all the signals will be the same, as is the slot handler that the signals connect to.
Instead of writing this code over and over. I would like to use a function pointer or something similar to assign to the signal, then have a common code block which performs the connection or disconnection.
The following code is just to illustrate what I am describing, it isn't valid and will not compile.
void (*pfnSignal)(quint8, QString);
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = signalA;
break;
case SIGNAL_B:
pfnSignal = signalB;
break;
default:
pfnSignal = NULL;
}
if ( pfnSignal != NULL ) {
QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}
In Qt5, this can be done easily, as it allows connecting using a new pointer to member function syntax.
// Using decltype to avoid figuring out the ugly pointer-to-member-function syntax.
// Assumes all signals have the same arguments.
decltype<&ThatClass::someSignal> pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = &ThatClass::signalA;
break;
case SIGNAL_B:
pfnSignal = &ThatClass::signalB;
break;
}
if (pfnSignal) {
connect(pobjRef, pfnSignal, this, &ThisClass::handler);
}
But actually, this is even possible with Qt4, as the SIGNAL macro is of type const char*.
const char *pfnSignal = nullptr;
switch( eSigID ) {
case SIGNAL_A:
pfnSignal = SIGNAL(signalA(quint8, QString));
break;
case SIGNAL_B:
pfnSignal = SIGNAL(signalB(quint8, QString));
break;
}
if (pfnSignal) {
QObject::connect(pobjRef, pfnSignal, this, SLOT(handler(quint8, QString)));
}
C++11 allows you to write very concise Qt code.
Leverage range-based for loops to iterate over pointers. Those can be pointers to widgets, pointers to methods, etc:
for (auto signal : {&Class::signal1, &Class:signal2})
QObject::connect(sender, signal, receiver, slot);
Leverage lambda expressions to capture constant argument values:
auto const cMySlot = [&](void (Sender::*signal)(int)){
QObject::connect(sender, signal, receiver, slot);
Then:
for (auto signal : {&Class::signal1, &Class:signal2}) cMySlot(signal);
Full example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/signals-simpler-43631464
#include <QtWidgets>
#include <initializer_list>
class Receiver : public QLabel {
Q_OBJECT
public:
Receiver(QWidget * parent = {}) : QLabel{parent} {}
Q_SLOT void intSlot(int val) {
setText(QStringLiteral("int = %1").arg(val));
}
};
class Sender : public QWidget {
Q_OBJECT
QFormLayout m_layout{this};
QPushButton btn1{"Send 1"}, btn2{"Send 5"}, btn3{"Send 10"};
public:
Sender(QWidget * parent = {}) : QWidget{parent} {
m_layout.setMargin(1);
for (auto w : {&btn1, &btn2, &btn3}) m_layout.addWidget(w);
auto const clicked = &QPushButton::clicked;
connect(&btn1, clicked, this, [this]{ emit signal1(1); });
connect(&btn2, clicked, this, [this]{ emit signal2(5); });
connect(&btn3, clicked, this, [this]{ emit signal3(10); });
}
Q_SIGNAL void signal1(int);
Q_SIGNAL void signal2(int);
Q_SIGNAL void signal3(int);
};
using Widgets = std::initializer_list<QWidget*>;
int main(int argc, char **argv)
{
QApplication app{argc, argv};
QWidget win;
QVBoxLayout layout{&win};
Sender sender;
Receiver receiver;
for (auto w : Widgets{&sender, &receiver}) layout.addWidget(w);
// Factor out connection
auto const cIntSlot = [&](void (Sender::*signal)(int)){
QObject::connect(&sender, signal, &receiver, &Receiver::intSlot);
};
// Factor out connection on a list
for (auto signal : {&Sender::signal1, &Sender::signal2, &Sender::signal3})
cIntSlot(signal);
win.show();
return app.exec();
}
#include "main.moc"
Actually, Thomas McGuire was faster than me. (Damn.) Though, I want to add this answer because:
It provides a complete sample.
It uses functors instead of object/member function pointers for signal handlers.
Thus, it may be and add-on to the answer of Thomas McGuire.
Before Qt 5 the signal was described by a char* which should be very simple to handle. Therefore, I assume your question is concerning the new API since Qt 5.
This should work as well if you use the correct method pointer type. I did this for QPushButton and QCheckBox for demonstration because both are derived from QAbstractButton which in turn has two signals with equal signature. IMHO equal signature of signals is mandatory for your solution.
#include <QtWidgets>
enum SigType { None, Click, Toggle };
template <typename FUNCTOR>
void installSignalHandler(
QAbstractButton *pQBtn,
SigType sigType,
FUNCTOR sigSlot)
{
void (QAbstractButton::*pSignal)(bool) = nullptr;
switch (sigType) {
case Click: pSignal = &QAbstractButton::clicked; break;
case Toggle: pSignal = &QAbstractButton::toggled; break;
}
if (pSignal) QObject::connect(pQBtn, pSignal, sigSlot);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version: " << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QWidget qWin;
QVBoxLayout qVBox(&qWin);
QPushButton qBtn1("Button 1 -> Click");
qVBox.addWidget(&qBtn1);
QPushButton qBtn2("Button 2 -> Toggle");
qVBox.addWidget(&qBtn2);
QPushButton qBtn3("Button 3 -> None");
qVBox.addWidget(&qBtn3);
QCheckBox qTgl1("Toggle 1 -> Click");
qVBox.addWidget(&qTgl1);
QCheckBox qTgl2("Toggle 2 -> Toggle");
qVBox.addWidget(&qTgl2);
QCheckBox qTgl3("Toggle 3 -> None");
qVBox.addWidget(&qTgl3);
qWin.show();
// install signal handlers
installSignalHandler(&qBtn1, Click,
[](bool) { qDebug() << "Button 1 received clicked."; });
installSignalHandler(&qBtn2, Toggle,
[](bool) { qDebug() << "Button 2 received toggled."; });
installSignalHandler(&qBtn3, None, // will be actually never called
[](bool) { qDebug() << "Button 3 received none."; });
installSignalHandler(&qTgl1, Click,
[](bool) { qDebug() << "CheckBox 1 received clicked."; });
installSignalHandler(&qTgl2, Toggle,
[](bool) { qDebug() << "CheckBox 2 received toggled."; });
installSignalHandler(&qTgl2, None, // will be actually never called
[](bool) { qDebug() << "CheckBox 3 received none."; });
// run-time loop
return app.exec();
}
Compiled and tested with VisualStudio & Qt 5.6 on Windows 10 (64 bit):
I'm trying to get large data from a database but when running the main window freezes.
I am working under windows and according to this link Windows automatically set the program to a hanging state state after 5 seconds.
Is there a way to prevent freezing?
Here is the code:
void MainWindow::on_getDataButtonClicked()
{
ui->centralWidget->setEnabled(false);
QApplication::setOverrideCursor(Qt::WaitCursor);
try
{
Client client(user, password);
std::future<map<string, map<string, string> > > fut =
std::async(std::launch::async, &Client::get_data, &client);
// While not all data has been retrieved, set message to the status bar.
while (fut.wait_for(std::chrono::seconds(0)) != std::future_status::ready)
{
ui->statusBar->showMessage("Getting data.");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
ui->statusBar->showMessage("Getting data..");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
ui->statusBar->showMessage("Getting data...");
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
map<string, map<string, string> > exported_strings = std::move(fut.get());
ui->statusBar->showMessage("%All data has been retrieved!");
}
catch (std::string& s)
{
QMessageBox::critical(this, "Error", QString::fromStdString(s));
}
catch (std::exception& e)
{
QMessageBox::critical(this, "Error", QString(e.what()));
}
catch (...)
{
QMessageBox::critical(this, "Error", "An unknown error has occurred.");
}
ui->centralWidget->setEnabled(true);
QApplication::restoreOverrideCursor();
}
On a side note, the main window does not freezes when debugging.
There's no point to doing asynchronous work if you're waiting for it and blocking the GUI thread anyway in the while loop. You need to get rid of the while loop.
You could use QtConcurrent::run instead of std::async, and use QFutureWatcher to get notified asynchronously, without blocking, when the async task has finished.
// https://github.com/KubaO/stackoverflown/tree/master/questions/async-sane-39396761
#include <QtWidgets>
#include <QtConcurrent>
#include <map>
#include <string>
struct Client {
using result_type = std::map<std::string, std::map<std::string, std::string>>;
result_type get_data() {
QThread::sleep(5); // pretend to do some work
return result_type();
}
};
class MainWindow : public QMainWindow {
Q_OBJECT
Client::result_type exported_strings;
QWidget centralWidget;
QVBoxLayout layout{¢ralWidget};
QPushButton getDataButton{"Get Data"};
QStatusBar statusBar;
QTimer statusTimer;
QString statusMessage;
void setBusyStatus(const QString & status) {
centralWidget.setEnabled(false);
QApplication::setOverrideCursor(Qt::WaitCursor);
statusMessage = status;
statusTimer.start(0);
}
void setNormalStatus(const QString & status) {
centralWidget.setEnabled(true);
QApplication::restoreOverrideCursor();
statusBar.showMessage(status);
statusTimer.stop();
}
Q_SLOT void on_getDataButtonClicked();
public:
MainWindow() {
setStatusBar(&statusBar);
setCentralWidget(¢ralWidget);
layout.addWidget(&getDataButton);
int n = 0;
connect(&statusTimer, &QTimer::timeout, [=]() mutable {
statusBar.showMessage(QStringLiteral("%1%2").arg(statusMessage).arg(QString{n+1, QChar{'.'}}));
n = (n+1)%3;
statusTimer.start(500);
});
connect(&getDataButton, &QPushButton::clicked, this, &MainWindow::on_getDataButtonClicked);
}
};
void MainWindow::on_getDataButtonClicked()
{
auto future = QtConcurrent::run([=]{
Client client;
return client.get_data();
});
auto watcher = new QFutureWatcher<Client::result_type>{this};
connect(watcher, &QFutureWatcher<Client::result_type>::finished, this, [=]{
exported_strings = std::move(watcher->result());
watcher->deleteLater();
setNormalStatus("All data has been retrieved!");
});
watcher->setFuture(future);
setBusyStatus("Getting data");
}
int main(int argc, char ** argv) {
QApplication app{argc, argv};
MainWindow w;
w.show();
return app.exec();
}
#include "main.moc"
Alternatively, you could emit a signal from the async code, letting you retain the use of std::async if you prefer that:
#include <QtWidgets>
#include <future>
#include <map>
#include <string>
struct Client {
using result_type = std::map<std::string, std::map<std::string, std::string>>;
result_type get_data() {
QThread::sleep(5); // pretend to do some work
return result_type();
}
};
class MainWindow : public QMainWindow {
Q_OBJECT
Client::result_type exported_strings;
QWidget centralWidget;
QVBoxLayout layout{¢ralWidget};
QPushButton getDataButton{"Get Data"};
QStatusBar statusBar;
QTimer statusTimer;
QString statusMessage;
std::future<Client::result_type> resultFuture;
void setBusyStatus(const QString & status) {
centralWidget.setEnabled(false);
QApplication::setOverrideCursor(Qt::WaitCursor);
statusMessage = status;
statusTimer.start(0);
}
void setNormalStatus(const QString & status) {
centralWidget.setEnabled(true);
QApplication::restoreOverrideCursor();
statusBar.showMessage(status);
statusTimer.stop();
}
Q_SLOT void on_getDataButtonClicked();
Q_SIGNAL void hasResult();
public:
MainWindow() {
setStatusBar(&statusBar);
setCentralWidget(¢ralWidget);
layout.addWidget(&getDataButton);
int n = 0;
connect(&statusTimer, &QTimer::timeout, [=]() mutable {
statusBar.showMessage(QStringLiteral("%1%2").arg(statusMessage).arg(QString{n+1, QChar{'.'}}));
n = (n+1)%3;
statusTimer.start(500);
});
connect(&getDataButton, &QPushButton::clicked, this, &MainWindow::on_getDataButtonClicked);
}
};
void MainWindow::on_getDataButtonClicked()
{
connect(this, &MainWindow::hasResult, this, [this](){
exported_strings = std::move(resultFuture.get());
setNormalStatus("All data has been retrieved!");
}, Qt::UniqueConnection);
resultFuture = std::async(std::launch::async, [this]{
Client client;
auto result = client.get_data();
emit hasResult();
return result;
});
setBusyStatus("Getting data");
}
int main(int argc, char ** argv) {
QApplication app{argc, argv};
MainWindow w;
w.show();
return app.exec();
}
#include "main.moc"
If everything is happening on the same thread and some operation is slow, then you won't get back to the event-loop in time for the OS to not declare you as stuck.
The solution(s) are to either;
1) use multiple processes.
2) use multiple threads.
3) divide the work on your one thread into chunks that are all guaranteed to be small enough that you can get back to servicing the eventloop in a timely fashion.
To avoid multithreading, here's what you should do:
Cut your code (virtually, just in thought) into chunks
Run your program and study these chunks, and see which of them causes the freezing (takes longest to execute)
Once you know what part causes the freezing, use QApplication::processEvents(); to force the MainWindow to become responsive.
Example: Let me illustrate with an example. Say you have this function
void MainWindow::on_getDataButtonClicked()
{
//do some easy stuff
for(long i = 0; i < 100000; i++)
{
//do some stuff
//do some other stuff
}
//do some different stuff
}
Now while executing this nasty function, your window will definitely freeze until the whole function is finished. The "easy stuff" is not the problem, because it's fast. But the big loop is the problem. So all you have to do is tell Qt to reprocess the events that get the main window to become responsive again; Like this:
void MainWindow::on_getDataButtonClicked()
{
//do some easy stuff
for(long i = 0; i < 100000; i++)
{
QApplication::processEvents();
//do some stuff
//do some other stuff
}
QApplication::processEvents();
//do some different stuff
}
How many times should you call processEvents();? Call it whenever you want the window to respond again. If in doubt, just put processEvents() like everywhere! Infest every line with that. It's not the best practice, but then you can remove them one by one and see where your program starts freezing again.
One additional piece of advice: Don't throw std::string exceptions. This is a very bad idea. Learn how to throw classes inherited from std::exception. You can still hold your string inside the std::exception object. For example, you can do: throw std::exception("This is very very bad");. There are many other classes you can throw. Find them here.