emit signal with delay - qt

the goal that I want to achieve is to emit a signal but delay it some time before it is actually emitted.
My idea for this was something like this:
emitLater(QObject *obj, QEvent *event, int msDelay)
{
QTimer...
connect(timout to obj and event...
start timer with msDelay...
//maybe I might need a public timer of some sort but the idea is clear
}
So this approach failed with the QEvent parameter because Signals could not be converted to QEvents.
I tried calling with std::bind in a couple of variations but I couldn't get it to work:
//the call
std::function<void(void)> signal = std::bind(&CustomPushButton_Header::clicked, &uiWindow->btnHeader_left); //just a random button to connect a valid signal
emitLater(signal, 2000);
//the function
emitLater(std::function<void(void)>, int msDelay)
{
qDebug() << "hi";
}
I would appreciate a solution that works for all Objects and all Signals... maybe only the part which does call the function. I think the delay will turn out easy.
Or you have a different and even easier approach. That would be nice.
some sources I looked in:
https://oopscenities.net/2012/02/24/c11-stdfunction-and-stdbind/
https://stackoverflow.com/a/47104204

There is a function QTimer::singleShot() that can trigger a signal/slot after a certain amount of time. I implemented it like this:
void MyWindow::emitLater(const QObject *obj, const char *signalOrSlot, int msDelay)
{
//use QTimer::singleShot() to trigger the signal/slot after a certain amount of time
QTimer::singleShot(msDelay, obj, signalOrSlot);
}
and use it like this:
void MyWindow::on_pushButton_5_clicked()
{
//trigger the "doing fancy stuff" function after 5 seconds (= 5000 ms)
emitLater(this, SLOT(setText()), 5000);
}
void MyWindow::setText()
{
//do some fancy stuff
ui->label_3->setText("Hello World!");
}
An alternative would be in the answer from ΦXocę 웃 Пepeúpa ツ and uses a lambda expression as call function for the QTimer::timout() signal:
void MyWindow::on_pushButton_5_clicked()
{
//initialize timer
QTimer* t= new QTimer(this);
t->setSingleShot(true);
//do some fancy stuff after timeout
connect(t, &QTimer::timeout, [this]()
{
ui->label_3->setText("Hello World!");
});
//start the timer with delay of 5 seconds (= 5000 ms)
t->start(5000);
}

you can use a timer for that, in the exmple above, you react to the click slot in a button, there define a QTimer, connect what is called when it times out(in this case is a lambda) and then you start the timer :)
void MyWindow::on_pushButton_5_clicked()
{
QTimer* t= new QTimer(this);
connect(t, &QTimer::timeout, [this](){ui->label_3->setText("done!");});
t->start(5000); //after 5 seconds
}

Here is my implementation in Python using PySide2.
Firstly I created a class:
class DelayedSignal:
def __init__(self, timeout, func):
self.timeout = timeout
self.func = func
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.setInterval(1000 * timeout)
self.timer.timeout.connect(func)
#Slot()
def __call__(self):
self.timer.stop()
self.timer.start()
Here is the rest of the code therefore all together is a minimum working example:
from PySide2.QtCore import QTimer, Slot
from PySide2.QtWidgets import QPushButton, QWidget, QHBoxLayout
class AppWindow(QWidget):
def __init__(self):
super().__init__()
self.btn = QPushButton("PRESS", self)
self.setLayout(QHBoxLayout())
self.layout().addWidget(self.btn)
self.delayed_signal = DelayedSignal(timeout=2, func=self.target)
self.btn.clicked.connect(self.delayed_signal)
#Slot()
def target(self):
print("END")
if __name__ == '__main__':
import sys
from PySide2.QtWidgets import QApplication
app = QApplication(sys.argv)
window = AppWindow()
window.show()
sys.exit(app.exec_())

Related

Connect Qtimer with a widget

I want to use a QTimer object to control a LED indictor status. A QLed class inherited QWidget is created to control the LED indicator. Here below its two major functions relevant:
void QLed::setLEDFlashing(bool value)
{
ledStatus = value; //Boolean value to accept a user-defined LED status
m_value = ledStatus; //m_value is used in painting LED (with QtSvgRenderer)
QTimer ledTimer;
ledTimer.setInterval(300);
if(!ledTimer.isActive())
{
ledTimer.start();
}
//Here is the connection between the timer and this (i.e., QLed*) object
connect(&ledTimer, SIGNAL(timeout()), this, SLOT(setLEDFlashingTimerHandler()));
}
//I want to use this function to make LED keep flashing
void QLed::setLEDFlashingTimerHandler()
{
//qDebug()<<"setLEDFlashingTimerHandler()";
if (ledStatus)
{
m_value = TRUE;
ledStatus = FALSE;
}
else
{
m_value = FALSE;
ledStatus =TRUE;
}
}
//This is to paint the LED widget
void QLed::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
//based on m_value, different svg file is loaded
if(m_value)
ledShapeAndColor.append(colors[m_onColor]);
else
ledShapeAndColor.append(colors[m_offColor]);
renderer->load(ledShapeAndColor);
renderer->render(&painter);
//qDebug()<<"paintEvent m_value="<<m_value;
}
In the mainwindow.ui, I add a QLabel object (named led) and promote it to QLed, and in the mainwindow.cpp:
ui->led->setLEDFlashing(TRUE);
The above codes cannot result in a flashing LED indicator. Actually, the connection between ledTimer and setLEDFlashingTimerHandler does NOT take effect for some reason, and m_value is not updated in paintEvent. Anyone can help debug my codes? Thanks!
Edits:
I have solved the connection issue by using QTimer *ledTimer in stead of QTimer ledTimer. But the painting still not works as expected, since m_value is not updated in that function or the function is only invoked for the very first time?
In your function QLed::setLEDFlashing, you create a local instance of QTimer which will be destroyed at the end of your function.
You should declare your QTimer as an attribute of your class or use a inner timer with QObject::startTimer

QTimer activeChanged signal

In the documentation of QTimer there is a Properties-section, containing the active-property.
This made me believe, there might be something like a activeChanged-signal, I could connect to.
For IMHO unapparent reasons
QObject::connect(m_timer, &QTimer::activeChanged, this, &MyObject::mySlot);
failes, stating activeChanged is no member of QTimer.
Basically, I want to do something, when the timer gets initially started (so not on restart) or finally stopped. When the signal activeChanged does not exist, has anyone knowledge:
Why it is a property at all?
If there are some other signals to connect to, to do this?
Any way to hook in, and do something when the timer is started or stopped?
test in main.cpp
QTimer* tim = new QTimer;
QObject::connect(tim, &QTimer::activeChanged, qApp, [tim](){qDebug() << "Active changed" << tim->isActive(); });
tim->start(40000); // I want to get a signal
tim->start(100); // I don't want to get a signal
tim->stop(); // I want to get a signal
Create your own timer class and encapsulate QTimer:
class Timer : public QObject
{
Q_OBJECT
QTimer m_timer;
public:
Timer ()
{
connect(&m_timer, &QTimer::timeout, this, &Timer::timeout);
}
void start(int msec)
{
if (m_timer.isActive())
{
// Restart detected -> block signal
m_timer.blockSignals(true);
m_timer.start(msec);
m_timer.blockSignals(false);
}
else
{
m_timer.start(msec);
}
}
}
Since the class Timer has the full control and knowledge of the QTimer, you can have any apparent behavior you want.

Why does my asynchronous QProgressBar render black?

I want a QProgressBar to show in a modal dialog and update asynchronously as another thread does work.
struct ProgressThread : public QThread
{
QDialog m_dialog;
QProgressBar * m_bar;
ProgressThread (QWidget * parent, int range)
: m_dialog (parent)
, m_bar (new QProgressBar ())
{
auto l = new QGridLayout (& m_dialog);
l -> addWidget (m_bar, 0, 0);
m_bar -> setRange (0, range);
}
void run () override
{
m_dialog .exec ();
}
};
ProgressThread thread (this, range);
thread .start ();
int num = 0;
for (each job)
{
do_some_work ();
thread .m_bar -> setValue (++ num);
}
thread .m_dialog .accept ();
thread .wait (1000);
thread .quit ();
It basically works except the progress bar renders as a black block. Why is this happening?
This is a design problem. Neither Qt nor most of other UI frameworks designed to run multiple threads for UI though this innovative approach may take place. Most of UI classes just expect to be running on same main thread. Qt can never guarantee that it works the way you are attempting: running the dialog exec() on a worker thread.
How to convert the source code you have to Qt way? Your source code encapsulates QDialog and QProgressBar immediately in thread instance which is maybe not incorrect yet until you do moveToThread(uiObject). But still, it shows the wrong design. You can just follow normal pattern for the worker thread interacting with UI:
// make sure to place it in the header file
class ProgressThread : public QDialog
{
Q_OBJECT
public:
// if you ever need to access that from outside
// QProgressBar* bar() {return m_bar;}
public slots:
void moveProgress(int);
private:
QProgressBar* m_bar;
};
class ProgressThread : public QThread
{
Q_OBJECT
public:
// add methods
signals:
moveProgress(int);
};
You need to connect the slot to signal:
// for interthread communication a queued connection will be automatical
// figure out how to get the pointer to that dialog class (dialogPtr)
QObject::connect(&thread, SIGNAL(moveProgress(int)), dialogPtr(), SLOT(moveProgress(int));
And from the thread code you emit the signal to advance the progress bar on UI thread:
emit moveProgress(value);
The similar question/answers: QProgressBar not showing progress?
And make sure that UI thread starts from the application main() function!

Qt widget element doesn't want to hide

In my Qt application after pressing button I want to hide that button and start quite long process. While this process is running PushButton shouldn't be visible but it seems to be waiting for process being executed and after that hide button. It looks like QWidget is refreshing after end of PushButton slot function. Here's my simplified code:
void MainWindow::on_pushButton_clicked()
{
ui->progressBar->setVisible(true);
ui->pushButton->setVisible(false);
while(x<1000000) x++; //e.g of my long time function
}
When that function (on_pushButton_clicked() -> generated by mouse->go to slot) ends up my "view" is updated and button dissappear.
Is there any function to refresh my widget or maybe I forgot about sth?
Thanks in advance
Changes to the gui aren't shown until the program has a chance to redraw itself which won't happen until you return.
you'll need to defer the execution of the code somehow:
void MainWindow::on_pushButton_clicked()
{
ui->progressBar->setVisible(true);
ui->pushButton->setVisible(false);
QMetaObject::invokeMethod(this, &MainWindow::longFunction, Qt::QueuedConnection);
}
void MainWindow::longFunction()
{
while(x<1000000) x++; //e.g of my long time function
}
This returns to the event loop and then runs the longFunction but it will still block on and the progress bar won't show any updates until it is done.
To fix that you will either need to move the execution to a new thread or split the function up in shorter parts and invoke them in sequence with QMetaObject::invokeMethod and a QueuedConnection.
In order for the button to change state, it needs to return to process events in the event loop.
You could call QApplication::processEvents before the while loop in order to fix this, though it would be better to return to the event loop naturally, before you start the long-time function, by invoking the function as a QueuedConnection.
Alternatively, the better method would be to run the function in a separate thread, which will enable your GUI to remain active during the processing of the 'long function'
Start by creating an object to encapsulate the function that will do the work:-
class Worker : public QObject {
Q_OBJECT
public:
Worker();
~Worker();
public slots:
void process(); // This is where your long function will process
signals:
void finished();
void error(QString err);
};
void Worker::process()
{
while(x<1000000) x++; //e.g of my long time function
emit finished();
}
Create a new thread and start it when the button is clicked
void MainWindow::on_pushButton_clicked()
{
// change button visibility
ui->progressBar->setVisible(true);
ui->pushButton->setVisible(false);
// create the new thread and start the long function
QThread* thread = new QThread;
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString)));
connect(thread, SIGNAL(started()), worker, SLOT(process()));
//ensure the objects are cleared up when the work is done
connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
//start the thread and the long function processing
thread->start();
}

Propagate custom QEvent to parent widget in Qt/PyQt

Fist, apologies for the length of the question.
I am trying to propagate custom Qt event from child widgets to a top parent widget in order to trigger some action based on event type instead of linking signals.
Qt docs suggests that every event posted with postEvent() that have accept() and ignore() methods can be propagated (meaning each QEvent subclass).
I have tried to override customEvents method instead of events but to no avail.
Python
I've tried this in Python using PyQt4 (Qt version is 4.6).
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Foo(QWidget):
def doEvent(self):
QApplication.postEvent(self, QEvent(12345))
def event(self, event):
event.ignore()
return False
class Bar(QWidget):
def __init__(self, *args, **kwargs):
super(Bar, self).__init__(*args, **kwargs)
self.foo = Foo(self)
layout = QHBoxLayout()
layout.addWidget(self.foo)
self.setLayout(layout)
def event(self, event):
if event.type() == 12345:
self.someEventHandler()
return True
def someEventHandler(self):
print 'Handler in {0}'.format(self.__class__.__name__)
if __name__=='__main__':
app = QApplication([''])
bar = Bar()
bar.show()
bar.foo.doEvent()
app.exec_()
In this example Bar.someEventHandler() would only trigger if event was posted with self.parent() as the first argument like so:
def doEvent(self):
QApplication.postEvent(self.parent(), QEvent(12345))
Which is understandable since the event is passed directly to receiving object.
C++
Similar example in C++:
foobar.h
#ifndef FOOBAR_H
#define FOOBAR_H
#include <QtGui>
class Foo : public QWidget
{
Q_OBJECT
public:
Foo(QWidget *parent = 0);
void doEvent();
bool event(QEvent *);
};
class Bar : public QWidget
{
Q_OBJECT
public:
Bar(QWidget *parent = 0);
Foo *foo;
bool event(QEvent *);
};
#endif // FOOBAR_H
foobar.cpp
#include "foobar.h"
Foo::Foo(QWidget *parent)
: QWidget(parent) {}
void Foo::doEvent() {
QEvent *event = new QEvent(QEvent::User);
QApplication::postEvent(this, event);
}
bool Foo::event(QEvent *event)
{
event->ignore();
return QWidget::event(event);
}
Bar::Bar(QWidget *parent)
: QWidget(parent)
{
foo = new Foo(this);
}
bool Bar::event(QEvent *event)
{
if (event->type() == QEvent::User) {
qDebug() << "Handler triggered";
return true;
}
return QWidget::event(event);
}
main.cpp
#include <QtGui>
#include "foobar.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Bar bar(0);
bar.show();
bar.foo->doEvent();
return app.exec();
}
Same as in python this only works if event is passed directly to an object.
void Foo::doEvent() {
QEvent *event = new QEvent(QEvent::User);
QApplication::postEvent(this->parentWidget(), event);
}
Perhaps I missed the point, is it possible that only Key and Mouse events are propagated upwards?
I've spent some time browsing the Qt source in order to answer your question, and what I finally came up with is this: propagation of events to parent widgets is performed by QApplication::notify, basically a long switch statement with all sorts of events. For example, this is how it's done for QEvent::WhatsThisClicked:
...
case QEvent::WhatsThisClicked:
{
QWidget *w = static_cast<QWidget *>(receiver);
while (w) {
res = d->notify_helper(w, e);
if ((res && e->isAccepted()) || w->isWindow())
break;
w = w->parentWidget();
}
}
...
Now, the salient point: this is not performed for user defined events (and many other explicitly handled standard Qt events, as well), since the default clause is:
default:
res = d->notify_helper(receiver, e);
break;
And notify_helper doesn't propagate events. So, my answer is: apparently, user defined events are not propagated to parent widgets, you will have to do it yourself (or better: override QApplication::notify (it's a virtual public member) and add event propagation for your event(s)).
I hope that helps.
Re-implementation of QApplication.notify in Python which would propagate custom events.
In Qt notfy_helper makes sure the event filters are called (both QApplications and receivers) but I skipped that as I don't need them and notfy_helper is private member.
from PyQt4.QtCore import QEvent
from PyQt4.QtGui import QApplication
class MyApp(QApplication):
def notify(self, receiver, event):
if event.type() > QEvent.User:
w = receiver
while(w):
# Note that this calls `event` method directly thus bypassing
# calling qApplications and receivers event filters
res = w.event(event);
if res and event.isAccepted():
return res
w = w.parent()
return super(MyApp, self).notify(receiver, event)
And instead of using instance of QApplication we use instance of our subclass.
import sys
if __name__=='__main__':
app = MyApp(sys.argv)
As an alternative to rebus's answer, this snippet implements manual propagation of an event:
def _manual_propagate(target, evt):
app = QtGui.QApplication.instance()
while target:
app.sendEvent(target, evt)
if not evt.isAccepted():
if hasattr(target, 'parent'):
target = target.parent()
else:
target = None
return evt.isAccepted()
Note that this uses sendEvent, and thus must be called on the thread that the target object lives on. That limitation can be worked around with more indirection.
Note that you'll need to call _manual_propagate /instead of/ sendEvent yourself. This is a "less automatic" version of rebus's technique.
In addition, because this version uses sendEvent, event filters on objects are properly called.
The relevant reason custom events are not propagated, at least in Qt 4.8, is this:
//qobject.cpp
bool QObject::event(QEvent *e)
{
switch (e->type()) {
//several cases skipped
default:
if (e->type() >= QEvent::User) {
customEvent(e);
break;
}
return false;
}
return true;
}
//more skips
/*!
This event handler can be reimplemented in a subclass to receive
custom events. Custom events are user-defined events with a type
value at least as large as the QEvent::User item of the
QEvent::Type enum, and is typically a QEvent subclass. The event
is passed in the \a event parameter.
\sa event(), QEvent
*/
void QObject::customEvent(QEvent * /* event */)
{
}
That is, QObject::event calls another method when a custom event is received, and then breaks out of it's switch statement and executes return true;'. This return true signals the caller (generally QCoreApplication::notify that the event was handled.

Resources