Change interval timeouts of QTimer with slider - c++

timer = new QTimer(this);
timer->setInterval(50);
QPushButton *start = new QPushButton("Start/Stop", this);
start->setText("Start/Stop");
layout->addWidget(start);
connect(start, &QPushButton::clicked, this, [this]() {
if (!timer->isActive()) {
timer->start();
} else {
timer->stop();
}
});
connect(timer, &QTimer::timeout, this, [&slider, sliderDisplay]() {
//increment slider value until at max, then reset to min
}
How can I use another slider to change the interval of timeouts emitted by my QTimer? I've tried using setInterval but it seems that I can't change the interval once it is set. Is the best way to do this just delete this QTimer and create a new QTimer with the specified interval every time?

OP has claimed that
How can I use another slider to change the interval of timeouts emitted by my QTimer? I've tried using setInterval but it seems that I can't change the interval once it is set.
I wondered a bit about that claim because I could've sworn that you can change the interval at any time.
The only restriction, I would consider: the new set interval might not be considered before the next timeout happens. (Usually, I use single-shot timers or timers with small intervals so that you wouldn't notice the difference.)
While playing with my demo, I got the impression that a call of QTimer::setInterval() restarts the interval.
Unfortunately, the doc. of QTimer::setInterval() doesn't mention this behavior explicitly, except:
Setting the interval of an active timer changes its timerId().
Thanks to #Scopchanov who had the look into source code (I was too lazy to).
QTimer::setInterval():
void QTimer::setInterval(int msec)
{
inter = msec;
if (id != INV_TIMER) { // create new timer
QObject::killTimer(id); // restart timer
id = QObject::startTimer(msec, Qt::TimerType(type));
}
}
So, in fact, setting the interval while the timer is running, it kills the currently running timer and restarts a new one.
My MCVE for demonstration – testQTimer.cc:
// Qt header:
#include <QtWidgets>
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
QWidget qWinMain;
qWinMain.setWindowTitle("Test QTimer");
QFormLayout qForm;
QSlider qSliderInterval(Qt::Horizontal);
qSliderInterval.setRange(100, 1000);
qForm.addRow("Interval: ", &qSliderInterval);
QSlider qSliderStep(Qt::Horizontal);
qSliderStep.setRange(1, 10);
qForm.addRow("Step: ", &qSliderStep);
QPushButton qBtnStartStop("Start / Stop");
qForm.addRow("Timer:", &qBtnStartStop);
QSlider qSliderAnim(Qt::Horizontal);
qForm.addRow("Animation:", &qSliderAnim);
qSliderAnim.setRange(0, 100);
qWinMain.setLayout(&qForm);
qWinMain.show();
// setup timer
QTimer qTimer;
qTimer.setInterval(qSliderInterval.value());
// install signal handlers
QObject::connect(&qSliderInterval, &QSlider::valueChanged,
[&](int value) { qTimer.setInterval(value); });
QObject::connect(&qBtnStartStop, &QPushButton::clicked,
[&]() {
if (qTimer.isActive()) qTimer.stop();
else qTimer.start();
});
QObject::connect(&qTimer, &QTimer::timeout,
[&]() {
int value = qSliderAnim.value() + qSliderStep.value();
if (value > qSliderAnim.maximum()) {
value -= qSliderAnim.maximum() - qSliderAnim.minimum();
}
qSliderAnim.setValue(value);
});
// runtime loop
return app.exec();
}
Output:
Qt Version: 5.13.0

Related

How to start a loop on a QThread

How can I start a loop on a different thread by pressing a QPushButton?
The main idea is, when pushButtonStart is clicked, start a loop on a QThread, and when pushButtonStop is clicked, stops the loop in the QThread.
The loop can be done by QTimer, for loop or while loop, but he needs a way to stop by pressing a button.
I have this code to create a new timer and set up a connection when it fires. This is in the Start code.
checkTimer = new QTimer(this);
connect( checkTimer, SIGNAL(timeout()), this, SLOT(checkTimerFired()) );
checkTimer->start(3000);
My "stop running" button sets programCreated to false, and checkTimerFired starts with this:
if (!programCreated) {
checkTimer->stop();
return;
}
That should be the Qt-specific things you need. The rest is simple C++.
To do the loop, I use QTimer, which run the slot pressButton multiple times until stop(); function of QTimer is executed. Of course, to use this method I have to recode the program, to improve the algorithm.
QTimer loop:
void MainPrograma::on_pushTurnOn_clicked()
{
tierLoop = new QTimer(this);
timerLoop->setInterval(5000);
timerLoop->setSingleShot(true);
connect(timerLoop, SIGNAL(timeout()), SLOT(on_pushTurnOn_clicked()));
QPushButton *ThirdButton = uimain->QPushButton_3;
Nmb2 = 2
Nmb4 = 4;
if(Busy==1){
ThirdButton->setEnabled(false);
}
if(Busy==0){
timerLoop->start(); //start the loop
StartFunction(Nmb2, Nmb4);
if(X==1){
ThirdButton->setEnabled(false);
}
if(X==0){
ThirdButton->setEnabled(true);
}
}
}
void MainPrograma::on_pushTurnOff_clicked()
{
timerLoop->stop(); //stop the loop
}
QTimer declaration on .h:
private:
QTimer *timerLoop;
I still do not understand how to use QThread... But the loop is already answered!

QT "Tick" widget loop

I'm trying to understand the correct way to update a widget at frame-time.
The specific problem I'm trying to solve is to set the remaining time of a timer on a label.
I created and started the timer
MainTimer = new QTimer(this);
MainTimer->setSingleShot(true);
MainTimer->start(5000);
and on the QML I have a label, UI_MainTimerLabel, that I can access through ui->UI_MainTimerLabel->setNum(int).
Since the QTimer doesn't provide a OnTimerUpdate signal or callback method, I suppose I have to create some kind of loop to read the timer's value and set it to the label.
Should I do it through a QThread?
QThread::create([&]() {
while(true)
{
ui->UI_RemainingTimer->setNum(MainTimer->remainingTime());
}
})->start();
(note: I know that this won't work, but it's not a problem since I'm just trying to understand the concept)
Should I use a 0-timed QTimer?
UpdateTimer = new QTimer(this);
//{binding the UpdateTimer end signal to a ui->UI_RemainingTimer->SetNum(MainTimer->RemainingTimer() function}
UpdateTimer->start(0);
Should I use a QEventLoop (but I have yet to fully understand what is their correct usage)?
Should I use a user-created "MyTimerLabel" widget that self-updates (in which virtual overridden method?)?
Or is there some other correct way to manage a frame-time update, that I couldn't understand? (I'm trying to get the general correct approach, not the solving approach of this specific problem, though)
Thanks in advance
Is it necessary to update the GUI at every moment? No, each frame is updated every 30ms so something appropriate is to update half of that time, that is 15 ms. So the second timer is set to that period by calculating the remaining time showing it in the GUI:
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTimer main_timer;
main_timer.setSingleShot(true);
QTimer update_timer;
QLabel label;
label.setAlignment(Qt::AlignCenter);
label.resize(640, 480);
QObject::connect(&update_timer, &QTimer::timeout, [&main_timer, &update_timer, &label](){
int rem = main_timer.remainingTime();
if(rem <0){
label.setNum(0);
update_timer.stop();
}
else{
label.setNum(rem);
}
});
label.show();
main_timer.start(5000);
update_timer.start(15);
return a.exec();
}

Insert text in while(1) loop to texteditor in QT

Am trying to print "Some text" to QTextBrowser, continuously for "n" time. Where "n" is integer. For this I have used QTimer::SingleShot for timing. Once the timeout is triggered a FLAG is set to false and this "FLAG" is monitored in while loop to break when FLAG is false and it shall insert the text till FLAG is set to FALSE. Initial value for FLAG is true.
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
FLAG = true;
}
void MainWindow::on_pushButton_clicked()
{
ui->pushButton->setEnabled(false);
RunTheTimer();
int counter = 0;
do
{
ui->textBrowser->insertPlainText(QString("Inside While loop %1 \n").arg(counter++));
counter++;
}while(FLAG);
FLAG = true;
}
void MainWindow::RunTheTimer()
{
ui->textBrowser-> insertPlainText("Timer Started");
QTimer::singleShot(60000, this, SLOT(Update()));// One Minute
}
void MainWindow::Update()
{
ui->textBrowser-> insertPlainText("Timeout signal triggered");
ui->pushButton->setEnabled(true);
FLAG = false;
}
MainWindow::~MainWindow()
{
delete ui;
}
Application is getting Hang, When I click Pushbutton.
After debugging I observed, timeout is not triggering once the execution is entered to while(1) loop and application is not able to insert any text inside while(1) loop. Why this behavior? What am I doing wrong?
Thanks.
You are not returning control to the event loop, many things in Qt are not designed to work without an event loop, Have a look at this page from the Qt wiki, in your case:
QTextBrowser won't be able to show the newly added text, since this requires the widget to be able to receive paint events (and this is impossible without an event loop).
The timer that sets your flag to false won't be able to fire, since your program is always busy executing your while loop, and it won't be able to do anything else (unless it gets out from that while loop and this is impossible if it does not set your flag to false. . .).
Instead of using an endless loop, if you want to execute something as repeatedly as possible, you can use a QTimer and set its interval property to 0, this is a special value that causes the timer to timeout as soon as the event loop finishes processing all events in the event queue.
Using the above approach instead of your endless loop, you can use another timer to stop the above timer after a specific amount of time, and you don't have to worry about events not arriving and timers not firing since the event loop is always executing now.
Here is a possible implementation of the above approach:
#include <QtWidgets>
int main(int argc, char* argv[]){
QApplication a(argc, argv);
//set up GUI
QWidget widget;
QVBoxLayout layout(&widget);
QTextBrowser textBrowser;
QPushButton button("Add Text");
layout.addWidget(&textBrowser);
layout.addWidget(&button);
//timer with 0 interval instead of while loop
QTimer workTimer;
workTimer.setInterval(0);
int counter=0;
QObject::connect(&workTimer, &QTimer::timeout, [&]{
//add text to textBrowser whenever the workTimer fires
textBrowser.append(QStringLiteral("Additional Text %1").arg(counter++));
});
//when the button is clicked
QObject::connect(&button, &QPushButton::clicked, [&]{
//start work timer
workTimer.start();
button.setEnabled(false);
//stop work timer after 5 seconds
QTimer::singleShot(5000, [&]{
workTimer.stop();
button.setEnabled(true);
});
});
widget.show();
return a.exec();
}

Qt change picture on timer

I'm trying to change the image on QLabel in Qt.
At first, I did the basic set image as follows:
void MainWindow::setMainDisplayNew(QString imageName){
QPixmap pix7 = imageName;
QPixmap pix8 = pix7.scaled(QSize(720,480), Qt::KeepAspectRatio);
ui->mainDisplay->setStyleSheet(imageName);
ui->mainDisplay->setPixmap(pix8);
}
Now I want to change this so I can pass 2 arrays. List of images and duration they should appear for and I want the display to show them for the indicated duration.
void MainWindow::setMainDisplay(QString imageName[], int size)
{
for(unsigned int i=0; i<size; i++)
{
QTimer * timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, [=](){
setMainDisplayNew(imageName[i]);
timer->deleteLater(); // ensure we cleanup the timer
});
timer->start(3000);
}
}
EDIT
With the help of the responses, I reached the above code. I am sending the 3 images. It is display the final image after 3 seconds and stays as is... Any help?
while(true){
This is your problem. Qt is an event-driven framework. The while(true) prevents events from being processed. Such events include the timeout from the QTimer and updating of the GUI. You need to allow the function to exit.
In addition, you're not cleaning up your timers, so you're leaking memory every time you enter the function (although that's currently only once!). You can clean up with a call to deleteLater.
Using C++ 11, it would be something like this: -
for(int i=0; i<mySize; i++)
{
QTimer * timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, [=](){
setMainDisplayNew(imageName[i]);
timer->deleteLater(); // ensure we cleanup the timer
});
timer->start(duration[i]);
}

Wait for signal while processing other signals

My Qt application talks to a serial device, and occasionally has to wait for this device to send a byte. To accomplish this, I create a new eventloop that exits as soon as there is information available in the serial buffer:
unsigned char MyClass::waitForDevice(int timeout)
{
QEventLoop wait;
connect(d_serial, SIGNAL(readyRead()), &wait, SLOT(quit()));
if (timeout > 0)
QTimer::singleShot(timeout, &wait, SLOT(quit()));
wait.exec();
return static_cast<unsigned char>(d_serial->read(1)[0]);
}
Now the problem is that, while the application is waiting, i.e. while the eventloop is running, I need to be able to communicate to the serial device when a button is pressed in the GUI. Naively, I tried connecting a signal to a slot that does this, but I found that the slot is only executed after the eventloop is terminated.
I tried, without any luck, to have a seperate QThread running that calls qApp->processEvents() in an infinite loop, which is terminated when the eventloop is terminated. This didn't work, and I'm not quite sure why not. What is the canonical way to resolve this?
You're thinking synchronously in a pre-C++1z world. In C++14 (and prior) asynchronous programming, there is mostly no place for a notion of a wait that is implemented as a function that returns when the wait is over (switch-based coroutine hacks excepted). You are also not using the fact that your application is stateful, and the state transitions can be expressed in a state machine.
Instead, you should simply act on data being available. Presumably, your application can be in multiple states. One of the states - the one where you have to wait for input - is simply exited when the input arrives.
The example below uses a simple process-local pipe, but it would work exactly the same if you were using a serial port - both are a QIODevice and emit requisite signals. We start with the project file.
# async-comms-32309737.pro
QT += widgets core-private
TARGET = async-comms-32309737
CONFIG += c++11
TEMPLATE = app
SOURCES += main.cpp
To make things simple, the pipe implementation reuses the QRingBuffer private class from Qt. See this question for more fleshed-out implementation(s).
// main.cpp
#include <QtWidgets>
#include <private/qringbuffer_p.h>
/// A simple point-to-point intra-application pipe. This class is not thread-safe.
class AppPipe : public QIODevice {
Q_OBJECT
AppPipe * m_other { nullptr };
QRingBuffer m_buf;
public:
AppPipe(AppPipe * other, QObject * parent = 0) : QIODevice(parent), m_other(other) {
open(QIODevice::ReadWrite);
}
void setOther(AppPipe * other) { m_other = other; }
qint64 writeData(const char * data, qint64 maxSize) Q_DECL_OVERRIDE {
if (!maxSize) return maxSize;
m_other->m_buf.append(QByteArray(data, maxSize));
emit m_other->readyRead();
return maxSize;
}
qint64 readData(char * data, qint64 maxLength) Q_DECL_OVERRIDE {
return m_buf.read(data, maxLength);
}
qint64 bytesAvailable() const Q_DECL_OVERRIDE {
return m_buf.size() + QIODevice::bytesAvailable();
}
bool isSequential() const Q_DECL_OVERRIDE { return true; }
};
We start with a simple UI, with one button to restart the state machine, another to transmit a single byte that will be received by the client, and a label that indicates the current state of the state machine.
int main(int argc, char *argv[])
{
QApplication a { argc, argv };
QWidget ui;
QGridLayout grid { &ui };
QLabel state;
QPushButton restart { "Restart" }, transmit { "Transmit" };
grid.addWidget(&state, 0, 0, 1, 2);
grid.addWidget(&restart, 1, 0);
grid.addWidget(&transmit, 1, 1);
ui.show();
We now create the simulated device and the client pipe endpoints.
AppPipe device { nullptr };
AppPipe client { &device };
device.setOther(&client);
The state machine has three states. The s_init is the initial state, and is exited after a 1.5s delay. The s_wait state is only exited when we receive some data (a byte or more) from the device in that state. In this example, receiving the data in other states has no effect. The machine is set to restart automatically when stopped.
QStateMachine sm;
QState
s_init { &sm }, // Exited after a delay
s_wait { &sm }, // Waits for data to arrive
s_end { &sm }; // Final state
QTimer timer;
timer.setSingleShot(true);
sm.setInitialState(&s_init);
QObject::connect(&sm, &QStateMachine::stopped, &sm, &QStateMachine::start);
QObject::connect(&s_init, &QState::entered, [&]{ timer.start(1500); });
s_init.addTransition(&timer, SIGNAL(timeout()), &s_wait);
s_wait.addTransition(&client, SIGNAL(readyRead()), &s_end);
To visualize the state machine's progress, we assign the state label's text property in each of the states:
s_init.assignProperty(&state, "text", "Waiting for timeout.");
s_wait.assignProperty(&state, "text", "Waiting for data.");
s_end.assignProperty(&state, "text", "Done.");
Finally, the restart button stops the state machine - it will self-restart then. The transmit button simulates the device sending one byte of data.
QObject::connect(&restart, &QPushButton::clicked, &sm, &QStateMachine::stop);
QObject::connect(&transmit, &QPushButton::clicked, [&]{
device.write("*", 1);
});
We start the machine, enter the event loop, and let Qt follow our directions onwards from here. The main.moc file is included for it contains the metadata for AppPipe.
sm.start();
return a.exec();
}
#include "main.moc"
There are several Types of which Signals and Slots can be connected.
See: http://doc.qt.io/qt-4.8/qt.html#ConnectionType-enum
Have you tried Qt::DirectConnection: connect(d_serial, SIGNAL(readyRead()), &wait, SLOT(quit()),Qt::DirectConnection); ?