QtWebEngine - synchronously execute JavaScript to read function result - c++

I have the following method in one of my C++ classes (using QtWebEngine):
QString get()
{
QString result;
view->page()->runJavaScript("test();", [this](const QVariant &v)
{
result = v.toString();
});
return result;
}
It is to execute test() JS function and return the result of this invocation.
Unfortunately, the callback is asynchronous and the program crashes. How can I make it work?

The callback is asynchronous because the JavaScript execution occurs not only in another thread but in another process. So there is no way to make it fully synchronous.
The best possible solution would be to migrate your C++ code to work asynchronously. If you can't do that, the only feasible solution is to use QEventLoop, somewhat like this:
void ranJavaScript()
{
emit notifyRanJavaScript();
}
QString get()
{
QString result;
QEventLoop loop;
QObject::connect(this, SIGNAL(notifyRanJavaScript()), &loop, SLOT(quit()));
view->page()->runJavaScript("test();", [this](const QVariant &v)
{
result = v.toString();
this.ranJavaScript();
});
loop.exec();
return result;
}
However, note that this example is oversimplified for a real-world usage: you need to ensure the JavaScript was not ran before the event loop is started. The most proper way to do that would involve implementing a proper slot instead of a lambda + factoring out the call to view->page()->runJavaScript() into another slot which would be called asynchronously after starting the event loop. It is a lot of glue code for such a seemingly simple task but that's what it takes. Here's an example:
MainWindow.h
#ifndef TMP_MAIN_WINDOW_H
#define TMP_MAIN_WINDOW_H
#include <QMainWindow>
#include <QVariant>
class QWebEngineView;
class QPushButton;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget * parent = 0);
QString get();
void onScriptEnded(const QVariant & data);
Q_SIGNALS:
void notifyRanJavaScript();
private Q_SLOTS:
void onButtonPressed();
void startScript();
private:
QWebEngineView * m_view;
QPushButton * m_button;
QString m_scriptResult;
};
#endif // TMP_MAIN_WINDOW_H
MainWindow.cpp
#include "MainWindow.h"
#include <QWebEngineView>
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QEventLoop>
#include <QDebug>
#include <QTimer>
MainWindow::MainWindow(QWidget * parent) :
QMainWindow(parent)
{
m_view = new QWebEngineView;
QWebEnginePage * page = new QWebEnginePage(m_view);
m_view->setPage(page);
QString html = QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""
"\"http://www.w3.org/TR/html4/strict.dtd\"><html>"
"<head><h3>head</h3>\n</head>"
"<script type=\"text/javascript\">function test() { return \"A!\"; }</script>"
"<body>text\n</body></html>");
m_view->page()->setHtml(html);
m_button = new QPushButton;
m_button->setMinimumWidth(35);
m_button->setText(QStringLiteral("Test"));
QObject::connect(m_button, SIGNAL(pressed()), this, SLOT(onButtonPressed()));
QHBoxLayout * buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(m_button);
buttonLayout->addStretch();
QVBoxLayout * viewLayout = new QVBoxLayout;
viewLayout->addLayout(buttonLayout);
viewLayout->addWidget(m_view);
QWidget * widget = new QWidget(this);
widget->setLayout(viewLayout);
setCentralWidget(widget);
}
QString MainWindow::get()
{
QEventLoop loop;
QObject::connect(this, SIGNAL(notifyRanJavaScript()), &loop, SLOT(quit()));
// Schedule the slot to run in 0 seconds but not right now
QTimer::singleShot(0, this, SLOT(startScript()));
// The event loop would block until onScriptEnded slot is executed
loop.exec();
// If we got here, the script has been executed and the result was saved in m_scriptResult
return m_scriptResult;
}
void MainWindow::onScriptEnded(const QVariant & data)
{
qDebug() << QStringLiteral("Script ended: ") << data;
m_scriptResult = data.toString();
emit notifyRanJavaScript();
}
void MainWindow::onButtonPressed()
{
QString str = get();
QMessageBox::information(this, QStringLiteral("Script result"), str,
QMessageBox::StandardButton::Ok);
}
struct Functor
{
Functor(MainWindow & window) : m_window(window) {}
void operator()(const QVariant & data)
{
m_window.onScriptEnded(data);
}
MainWindow & m_window;
};
void MainWindow::startScript()
{
qDebug() << QStringLiteral("Start script");
m_view->page()->runJavaScript(QStringLiteral("test();"), Functor(*this));
}

Dmitry's solution only works partially, unfortunately. In his example the code is invoked upon pressing a button. However, if the same code is moved to execute while the window is loading, the slot for the finished script (onScriptEnded) is never called. To fix this, I had to wrap the call to the evaluation of JS itself (called evaluateJavaScript in my project) in another QTimer::singleShot:
QTimer::singleShot(0, this, [&] { evaluateJavaScript(); });
Unfortunately, in my real project I have many calls, often one evaluation waiting for another before it to finish. It's practically impossible to use this every single time so I still look for a solution.

In the kchmviewer project, I found a very easy way to wait for the result of the runJavaScript function. The following code is part of the ViewWindow class which inherits from QWebEngineView.
int ViewWindow::getScrollbarPosition()
{
QAtomicInt value = -1;
page()->runJavaScript("document.body.scrollTop", [&value](const QVariant &v)
{
qDebug( "value retrieved: %d\n", v.toInt());
value = v.toInt();
});
while (value == -1)
{
QApplication::processEvents();
}
qDebug( "scroll value %d", value.load() );
return value;
}

Related

Modal QProgressDialog updated from worker thread

I want to update a modal QProgressDialog from my worker thread. However, if I set the dialog to be modal, my application crashes (and the dialog did not show any progress). If I do not, everything goes fine (but the user can tinker around with the rest of the program, which may cause issues).
What am I doing wrong?
Minimum code sample follows:
filereader qfr;
QProgressDialog progress("Importing file.", "Cancel", 0, file_size);
connect(&qfr, &filereader::signalProgress, &progress, &QProgressDialog::setValue, Qt::QueuedConnection);
QThread worker_thread;
std::atomic<bool> success = false;
connect(&worker_thread, &QThread::started,
[&]() {
success = qfr.read_file(/* parameters */);
worker_thread.quit();});
worker_thread.start();
//progress.setWindowModality(Qt::WindowModal); // Works only fine when this line is commented !!
while (worker_thread.isRunning()) {
QApplication::processEvents();
QThread::sleep(0);
}
progress.close();
Your thread is pretty much pointless. It serves no real purpose. You could have as well just called QApplication::processEvents in your read_file method. But you shouldn't, calling processEvents is bad practice.
What you should do is remove that while loop, and make your progress dialog a member of your class. I don't really like how that lambda looks either. I would personally just use filereader::read_file as a slot.
Note that Qt::windowModal blocks input to the parent window. Your progress dialog has no parent. So you would either have to call progress->setModal(true), or progress.setWindowModality(Qt::ApplicationModal);. Or set a parent to it.
Here is a small example (it is not tailor made for your application, but it should point you in the right direction):
#include <QtWidgets>
class Worker : public QObject
{
Q_OBJECT
public:
Worker(QObject *parent = nullptr) : QObject(parent){}
public slots:
void simulateLongProcess()
{
for(int i = 0; i < 101; i++)
{
emit progressChanged(i);
QThread::msleep(100);
}
emit finishedWorking(true);
}
signals:
void progressChanged(int progress);
void finishedWorking(bool result);
};
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr) : QWidget(parent)
{
setLayout(new QHBoxLayout);
progress_dialog.setModal(true);
progress_dialog.setAutoReset(false);
progress_dialog.setCancelButton(nullptr);
QThread *thread = new QThread(this);
connect(thread, &QThread::started, &worker, &Worker::simulateLongProcess);
connect(&worker, &Worker::finishedWorking, thread, &QThread::quit);
connect(&worker, &Worker::progressChanged, &progress_dialog, &QProgressDialog::setValue);
connect(&worker, &Worker::finishedWorking, &progress_dialog, &QProgressDialog::close);
connect(&worker, &Worker::finishedWorking, this, &Widget::handleResult);
QPushButton * start_button = new QPushButton("START");
connect(start_button, &QPushButton::clicked, this, [=]
{
progress_dialog.show();
thread->start();
});
layout()->addWidget(start_button);
resize(400, 300);
}
public slots:
void handleResult(bool result)
{
// do something with the result
}
private:
QProgressDialog progress_dialog;
Worker worker;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"

Qt - MainWindow freezes when running

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{&centralWidget};
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(&centralWidget);
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{&centralWidget};
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(&centralWidget);
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.

Getting "destroyed" signal in Qt Script

How to properly connect to QObject's destroyed signal from Qt Script?
When I connect to it like to your average signal, it does not work. I did test that I removed the object and that other QObjects did get the signal, but the script function I connected to it is not invoked.
Below is the sample I'm using for testing. The most important part is the line:
_engine.evaluate("obj.destroyed.connect(function() { debug(\"obj destroyed\") })");
I'd expect it to invoke that function when obj is destroyed. The code below ensures that object is deleted when even loop already started and it also tests that the object did send destroyed signal to another QObject. What I want to fix is for the script to invoke the script function with debug("obj destroyed") after destroyed signal is emitted.
ScriptTester.h:
#ifndef SCRIPTTESTER_H
#define SCRIPTTESTER_H
#include <QtScript>
#include <QtCore>
class ScriptTester: public QObject {
Q_OBJECT
public:
explicit ScriptTester(QObject *parent = 0);
private slots:
void signalTest();
void boo();
private:
QScriptEngine _engine;
};
#endif // SCRIPTTESTER_H
ScriptTester.cpp:
#include "ScriptTester.h"
static QScriptValue scriptDebug(QScriptContext *context, QScriptEngine *engine) {
Q_UNUSED(engine);
if (context->argumentCount() >= 1) {
QString msg = context->argument(0).toString();
for (int i = 1; i < context->argumentCount(); ++i) {
msg = msg.arg(context->argument(i).toString());
}
qDebug() << msg;
}
return QScriptValue();
}
ScriptTester::ScriptTester(QObject *parent) :
QObject(parent)
{
QTimer::singleShot(0, Qt::CoarseTimer, this, SLOT(signalTest()));
_engine.globalObject().setProperty("debug", _engine.newFunction(scriptDebug, 1));
}
void ScriptTester::signalTest() {
QObject *obj = new QObject(this);
_engine.globalObject().setProperty("obj", _engine.newQObject(obj));
_engine.evaluate("obj.destroyed.connect(function() { debug(\"obj destroyed\") })");
if (_engine.hasUncaughtException()) {
qDebug() << "Exception:" << _engine.uncaughtException().toString();
}
connect(obj, SIGNAL(destroyed()), this, SLOT(boo()));
QTimer *timer = new QTimer;
_engine.globalObject().setProperty("timer", _engine.newQObject(timer));
_engine.evaluate("timer.timeout.connect(function() { debug(\"timer timeout\"); obj.deleteLater(); })");
if (_engine.hasUncaughtException()) {
qDebug() << "Exception:" << _engine.uncaughtException().toString();
}
timer->setSingleShot(true);
timer->start(100);
}
void ScriptTester::boo() {
qDebug() << "was destroyed!";
}
Note that I don't want a hack like passing destroyed first to C++ code and then manually or by a signal informing script of that. I'm searching for an implementation that's done fully in the script.

Qt QLCDNumber time_t Timer

I have a tool button in one .cpp (i.e. ExerciseControl.cpp). When the button is clicked, it will trigger two timers in another .cpp (i.e. StatusBar.cpp).
However, the following codes does not work. The timers do not start when I click the button in ExerciseControl.cpp to trigger startExercise().
ExerciseControl.cpp:
ExerciseControl::ExerciseControl(void)
{
myStatusBar = new StatusBar;
}
void ExerciseControl::startExercise()
{
myStatusBar ->simulationTimer->start(1000);
myStatusBar ->elapsedTimer->start(1000);
}
StatusBar.cpp:
StatusBar::StatusBar()
{
simulationTimer = new QTimer;
QObject::connect(simulationTimer, SIGNAL(timeout()), this, SLOT(tickSimulation()));
elapsedTimer = new QTimer;
QObject::connect(elapsedTimer, SIGNAL(timeout()), this, SLOT(tickSimulation()));
createButtons();
};
void StatusBar::createButtons()
{
...
}
void StatusBar::tickSimulation()
{
...
}
However, if I declare the following in my constructor, the timers can start automatically, which is not I wanted.
StatusBar::StatusBar()
{
simulationTimer = new QTimer;
simulationTimer->start(1000);
QObject::connect(simulationTimer, SIGNAL(timeout()), this, SLOT(tickSimulation()));
elapsedTimer = new QTimer;
elapsedTimer->start(1000);
QObject::connect(elapsedTimer, SIGNAL(timeout()), this, SLOT(tickSimulation()));
}
I need to trigger the timers through my button in ExerciseControl.cpp.
Please help.
Thanks.
On the example below, ExerciseControl is a specialization of QMainWindow that does 2 things:
Display a QTooButton inside the window;
Allocate a StatusBar object.
Clicking the button invokes the slot _timers_must_start() of the class. This slot is responsible to call start_timers() from the StatusBar object, which in turn starts the timers of that object.
I made each timer call a slot that prints something to the screen when the timeout() is triggered.
Enjoy.
main.cpp:
#include "exercise_control.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
ExerciseControl ec;
ec.show();
return app.exec();
}
exercise_control.h:
#pragma once
#include "status_bar.h"
#include <QMainWindow>
#include <QToolButton>
class ExerciseControl : public QMainWindow
{
Q_OBJECT
public:
ExerciseControl()
{
_button = new QToolButton(this);
_button->setText("Press Me!");
QObject::connect(_button, SIGNAL(clicked()), this, SLOT(_timers_must_start()));
_statusbar = new StatusBar;
// Set the size of the window to the size of the button
setFixedSize(_button->width() ,_button->height());
}
private slots:
/* This slot is called by clicking the button */
void _timers_must_start()
{
qDebug() << "ExerciseControl::_timers_must_start: button clicked! Trying to start timers...";
_statusbar->start_timers();
}
private:
QToolButton* _button;
StatusBar* _statusbar;
};
status_bar.h:
#pragma once
#include <QObject>
#include <QTimer>
#include <QDebug>
class StatusBar : public QObject
{
Q_OBJECT
public:
StatusBar()
{
_timer1 = new QTimer;
QObject::connect(_timer1, SIGNAL(timeout()), this, SLOT(_timer1_task()));
_timer2 = new QTimer;
QObject::connect(_timer2, SIGNAL(timeout()), this, SLOT(_timer2_task()));
}
~StatusBar()
{
_timer1->stop();
delete _timer1;
_timer2->stop();
delete _timer2;
}
void start_timers()
{
if (!_timer1->isActive() && !_timer2->isActive())
{
qDebug() << "StatusBar::start_timers: OK!";
_timer1->start(1000); // 1 second interval
_timer2->start(5000); // 5 seconds interval
return;
}
qDebug() << "StatusBar::start_timers: timers already started, nothing to be done here!";
}
private:
QTimer* _timer1;
QTimer* _timer2;
private slots:
/* These slots are called by the timers of this class, hence PRIVATE SLOTS */
void _timer1_task()
{
qDebug() << "StatusBar::_timer1_task: 1 second has elapsed";
}
void _timer2_task()
{
qDebug() << "StatusBar::_timer2_task: 5 seconds has elapsed";
}
};
test.pro:
QT += widgets
SOURCES += \
main.cpp
HEADERS += \
exercise_control.h \
status_bar.h

Can not emit QThread's signals

QT 5.1.0rc2 , msvc 2010 , Microsoft Visual Studio 2010
It is working code on Qt 4.8.4 , msvc 2008
I have compile error at
#if defined( HANDLE_PROCESSING_IN_MAIN_THREAD )
if(QThread::currentThread() != this)
emit started();
#endif
inherited::run();
and
#if defined( HANDLE_PROCESSING_IN_MAIN_THREAD )
if(QThread::currentThread() != this)
emit finished();
#endif
error C2660: 'QThread::started' : function does not take 0 arguments
error C2660: 'QThread::finished' : function does not take 0 arguments
In QThread i have seen
Q_SIGNALS:
void started(
#if !defined(Q_QDOC)
QPrivateSignal
#endif
);
void finished(
#if !defined(Q_QDOC)
QPrivateSignal
#endif
);
when I defined Q_QDOC I got many errors in QT sources.
QPrivateSignal is empty structure that defined in macro Q_OBJECT
Need a solution that does not affect the architecture of the application, as to be backward compatible with Qt4.8.4
Some ideas?
The thread's signals are emitted automatically. You should never emit them manually.
You're trying to use preprocessor to handle two variants of the code: processing in the gui thread or a dedicated thread. Qt provides a very easy way of dealing with it.
Implement your processing functionality in a slot in a class deriving from QObject. You could also do it in the reimplemented event() method if it's easier for you to start processing by posting an event rather than invoking a slot.
If you want your object's slots to run in a different thread, use the moveToThread() method.
You don't need to derive from QThread. Its default implementation of run() spins a local event loop.
If you want your object to be compatible with living in the GUI thread, it must do its processing in small chunks and relinquish control so that the GUI doesn't stall.
Below is a complete example that demonstrates how you can start the worker object in either GUI or a separate thread, and how you can safely move it between threads.
// https://github.com/KubaO/stackoverflown/tree/master/questions/qobject-thread-18653347
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
/// See http://stackoverflow.com/a/40382821/1329652
bool isSafe(QObject * obj) {
Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
auto thread = obj->thread() ? obj->thread() : qApp->thread();
return thread == QThread::currentThread();
}
class Helper : private QThread {
public:
using QThread::usleep;
};
class Worker : public QObject {
Q_OBJECT
int m_counter;
QBasicTimer m_timer;
void timerEvent(QTimerEvent * ev) override;
public:
Worker(QObject *parent = nullptr) : QObject(parent) {}
/// This method is thread-safe.
Q_SLOT void start() {
if (!isSafe(this)) return (void)QMetaObject::invokeMethod(this, "start");
if (m_timer.isActive()) return;
m_counter = 0;
m_timer.start(0, this);
}
/// This method is thread-safe.
Q_INVOKABLE void startInThread(QObject *targetThread) {
if (!isSafe(this)) return (void)QMetaObject::invokeMethod(this, "startInThread", Q_ARG(QObject*, targetThread));
QObject::moveToThread(qobject_cast<QThread*>(targetThread));
start();
}
Q_SIGNAL void done();
Q_SIGNAL void progress(int percent, bool inGuiThread);
};
void Worker::timerEvent(QTimerEvent * ev)
{
const int busyTime = 50; // [ms] - longest amount of time to stay busy
const int testFactor = 128; // number of iterations between time tests
const int maxCounter = 30000;
if (ev->timerId() != m_timer.timerId()) return;
const auto inGuiThread = []{ return QThread::currentThread() == qApp->thread(); };
QElapsedTimer t;
t.start();
while (1) {
// do some "work"
Helper::usleep(100);
m_counter ++;
// exit when the work is done
if (m_counter > maxCounter) {
emit progress(100, inGuiThread());
emit done();
m_timer.stop();
break;
}
// exit when we're done with a timed "chunk" of work
// Note: QElapsedTimer::elapsed() may be expensive, so we call it once every testFactor iterations
if ((m_counter % testFactor) == 0 && t.elapsed() > busyTime) {
emit progress(m_counter*100/maxCounter, inGuiThread());
break;
}
}
}
class Window : public QWidget {
Q_OBJECT
QVBoxLayout m_layout{this};
QPushButton m_startGUI{"Start in GUI Thread"};
QPushButton m_startWorker{"Start in Worker Thread"};
QLabel m_label;
QThread m_thread{this};
Worker m_worker;
Q_SLOT void showProgress(int p, bool inGuiThread) {
m_label.setText(QString("%1 % in %2 thread")
.arg(p).arg(inGuiThread ? "gui" : "worker"));
}
Q_SLOT void on_startGUI_clicked() {
m_worker.startInThread(qApp->thread());
}
Q_SLOT void on_startWorker_clicked() {
m_worker.startInThread(&m_thread);
}
public:
Window(QWidget *parent = {}, Qt::WindowFlags f = {}) : QWidget(parent, f) {
m_layout.addWidget(&m_startGUI);
m_layout.addWidget(&m_startWorker);
m_layout.addWidget(&m_label);
m_thread.start();
connect(&m_worker, SIGNAL(progress(int,bool)), SLOT(showProgress(int,bool)));
connect(&m_startGUI, SIGNAL(clicked(bool)), SLOT(on_startGUI_clicked()));
connect(&m_startWorker, SIGNAL(clicked(bool)), SLOT(on_startWorker_clicked()));
}
~Window() {
m_thread.quit();
m_thread.wait();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"