emit signal with delay

Viewed 2282

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:

3 Answers

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