QT "Tick" widget loop - c++

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();
}

Related

Change interval timeouts of QTimer with slider

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

QDialog closing on it's own, how can i fix it?

My class names is like (what it does)_(type) for example: reg_QDialog
Here is code of an executing dlg and if Accepted creating QMainWindow:
if(log_dlg->exec() == QDialog::Accepted)
{
find_wnd = new find_QMainWindow();
find_wnd->show();
}
log_dlg has 2 btns: "Enter" (here is the accept result) and "Reg" (opens a new dlg)
"Enter" and "Reg" code is here:
void log_QDialog::on_btn_enter_clicked()
{
this->accept();
}
void log_QDialog::on_btn_reg_clicked()
{
reg_QDialog *reg_dlg = new reg_QDialog();
this->hide();
if(reg_wnd->exec() == QDialog::Accepted)
{
//code
}
this->show();
}
So, here is the problem:
Step by step:
1) run the prog //it starts with dlg_log
2) "Reg" //creating dlg_reg
3) accept dlg_reg //returning to dlg_log
4) "Enter" //trying to create QMainWindow
QMainWindow is not created, and the app just closed
After "returning"(it's, actually, just hiding and then showing) from the reg_dlg and pushing btn with accept result it does nothing! It just closes the programm, but it had to show QMainWindow!
All real code of main.cpp:
#include "log_window_root.h"
#include "find_mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
log_window_root * log_wnd;
find_mainwindow * find_wnd;
log_wnd = new log_window_root();
log_wnd->setWindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);
log_wnd->setModal(true);
if(log_wnd->exec() == QDialog::Accepted)
{
find_wnd = new find_mainwindow();
find_wnd->setWindowFlags(Qt::MSWindowsFixedSizeDialogHint);
find_wnd->show();
}
return a.exec();
}
You are a operating a bit beyond the limits of Qt. Looking at the documentation:
Generally, no user interaction can take place before calling exec().
As a special case, modal widgets like QMessageBox can be used before
calling exec(), because modal widgets call exec() to start a local
event loop.
Testing your code on Mac OS will give you the dreaded warning
modalSession has been exited prematurely - check for a reentrant call
to endModalSession
which shows, that you are working on thin ice and your code will break any time.
Consider reworking your code so that the MainWindow comes up and then show your dialogs. If you want to go on with the Dialog sequence, then remove the hide()/show() pair from on_btn_reg_clicked (these calls mess up your event loops).
QApplication exits and close when the last window has been closed.
You can discard this by setting QApplication::quitOnLastWindowClosed property to false.
QApplication a(argc, argv);
a.setQuitOnLastWindowClosed(false);
You may want to revert it to it's previous state when your job is done with those dialogs.
As #Jens mentioned in the other answer to your question, this is will break evenloop at some point and QApplication exits before even a.exec() being called. So, you can also create a zero width/height or off-screen QWidget as a parent of your dialogs. A QSplashScreen is also a good candidate for this. All your dialogs should be your splash screen children. At last, you can finish your splash screen by calling QSplashScreen::finish.
[Re-posting from duplicate-closed question since the top banner on it may cause people to skip that question entirely, and IMHO this one doesn't have a good answer.]
Here's a very simple example of showing a dialog before a QMainWindow. The dialog simply presents the option of starting the main app or not. They key points are that the QDialog portion happens before any other widgets are created (eg. QMainWindow here), and the app (main()) exits before that if needed. No reason to keep the QDialog around after it is used, so I create it on the stack and delete afterwards.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDialog *d = new QDialog();
QPushButton *pbYes = new QPushButton("Would you like to play a game?", d);
QPushButton *pbNo = new QPushButton("Get me out of here!", d);
QObject::connect(pbYes, &QPushButton::clicked, [d]() { d->done(QDialog::Accepted); });
QObject::connect(pbNo, &QPushButton::clicked, [d]() { d->done(QDialog::Rejected); });
d->setLayout(new QVBoxLayout);
d->layout()->addWidget(pbYes);
d->layout()->addWidget(pbNo);
const int ret = d->exec();
delete d;
if (ret == QDialog::Rejected)
return 0;
QMainWindow mw;
mw.setCentralWidget(new QLabel("Welcome to the Game!", &mw));
mw.show();
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();
}

Updating Qt Images does not work until exec is called

I need to create a simple GUI which displays images, the images in this example can change and the GUI will need to update it's contents.
I wrote the following update function in my widget class:
void myClass::updatePic() {
QPixmap pix("./pic.png");
int width = ui->picLabel->width();
int height = ui->picLabel->height();
ui->picLabel->setPixmap(pix.scaled(width,height,Qt::KeepAspectRatio));}
I try to use it in the following manner:
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
myClass w;
w.show();
sleep(3);
w.updatePic();
sleep(3);
w.updatePic();
sleep(3);
return a.exec();}
But the window just opens and does not display the images until we get to the a.exec() line, and then it opens the last image. What am I doing wrong?
EDIT:
Clarification, the trigger for changing the images comes from an external program (specifically, the gui will be a node in ros, and will be triggered by another node). Is there a way to push a button not from the gui via an external program? the timer will work but I dislike this "busy wait" style solutions.
Thanks for the suggestions so far
exec runs the QT event loop, which includes rendering widgets.
So move your updatePic call into your widget and activate it by for example a button or in the show event
At first learn more about event loop. In particular, you must know that all events like paintEvent or resizeEvent are usually called on corresponding events handle. The events handle is usually called by the event loop, i.e. inside of exec function.
Let's unite answers of #MohaBou and #RvdK. You need to handle timer shots after the exec call. Use QObject::timerEvent for this.
myClass::myClass()
{
<...>
// This two variables are members of myClass.
_timerId = startTimer(3000);
_updatesCount = 0;
}
myClass::~myClass()
{
<...>
// For any case. As far as I remember, otherwise the late event
// may be handled after the destructor. Maybe it is false, do
// not remember...
if (_timerId >= 0) {
killTimer(_timerId);
_timerId = - 1;
}
}
myClass::timerEvent(QTimerEvent *event)
{
if (event->timerId() == _timerId) {
if (_updatesCount < 2) {
updatePic();
++_updatesCount;
} else {
killTimer(_timerId);
_timerId = - 1;
}
}
}
The startTimer method here adds especial timer event to the event query every 3 seconds. As all events, it may be handled only when the event loop will take control and all earlier events are handled. Because of it you can have a duration if many "heavy" events are handled.
EDIT: sorry, I didn't understand #MohaBou at first read. His answer with explicit QTimer is also good enough (but I still don't understand a part about modality).
The function exec also renders the child widgets. exec() blocks the application flow while show() doesn't. So, exec is mainly used for modal dialogs.
I recommend to update it in your custom witget by using a refresh timer. Use a QTimer to update the image every 3 secs:
QTimer* timer = new QTimer(this);
timer->setInterval(3000);
connect(timer, SINGAL(timeout()), this, SLOT(updatPicture()));
Update your picture in your custom slot:
MainWindow::updatePicture() {
updatePic()
}
If you want, you could use a lambda function:
connect(timer, &QTimer::timeout, this, [&w]() {
updatePic()
});

QObject::moveToThread: Widgets cannot be moved to a new thread

My IDE Qt 5.0.1, platform Linux
i have a problem about set widgets to window.(My opinion)
this is my main.cpp->
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QThread cThread;
MainWindow w;
w.doSetup(cThread);
w.moveToThread(&cThread);
cThread.start();
if(cThread.isRunning())
{
qDebug() << " Thread is Running...";
}
w.show();
return a.exec();
}
this is doSetup() method->
void MainWindow::doSetup(QThread &mainThread)
{
QObject::connect(&mainThread, &QThread::started, this, &MainWindow::activeLoopMainC);
}
i checked my signal-slot mechanism and it works.
slot method->
void MainWindow::activeLoopMainC()
{
qDebug() << " Signal-Slot structure working successfully..";
mainThreadProc((void*)(instAddr));
}
i call a function from my main.c by this slot method.
In debugging there is no problem about working codes. But my window is blank. there is only frame.
i receive an error message: QObject::moveToThread: Widgets cannot be moved to a new thread
How can i solve this problem?
Thank you in advance for your answers.
You can't move widgets into another thread - in order to keep user interface responsive, Qt needs to do all GUI work inside main thread.
If you have background work to do, then move background worker to other thread, and not the user interface.