I am making a timer/stopwatch with Qt Creator. But, my reset function (reset button clicked) is not working as I want it to. I want it to stop the timer and set the display (QLCDNumber) to 0. Instead, the timer is stopped but the display numbers stay the same as if the pause button was clicked. Except that when the timer is started (start button clicked) again, it restarts from the original time (as I want it to do). Here is the code.
I only included the parts that are part of the problem.
void MainWindow::delay()
{
QTime dieTime = QTime::currentTime().addSecs(1);
while (QTime::currentTime() < dieTime && !spause && !sreset)
{
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void MainWindow::on_tstart_clicked()
{
ttime = treset ? 0 : ttime;
tpause = treset = false;
ttime = ttime == 0 ? (ui->hr->value() * 3600 + ui->min->value() * 60 + ui->sec->value()) : ttime;
while (ttime >= 0 && !tpause && !treset)
{
const unsigned short sec = ttime % 3600 % 60, min = ttime % 3600 / 60, hr = ttime / 3600;
ui->tsec2->display(sec % 10);
ui->tsec1->display(sec / 10);
ui->tmin2->display(min % 10);
ui->tmin1->display(min / 10);
ui->thr2->display(hr % 10);
ui->thr1->display(hr / 10);
delay();
if (!tpause && !treset) --ttime;
}
}
void MainWindow::on_tpause_clicked()
{
tpause = true;
}
void MainWindow::on_treset_clicked()
{
treset = true;
ui->ssec2->display(0);
ui->ssec1->display(0);
ui->smin2->display(0);
ui->smin1->display(0);
ui->shr2->display(0);
ui->shr1->display(0);
}
Your click on a button is processed only in the function delay() containing processEvents(). When delay() is executed, it sends message to the application, but on_treset_clicked() is executed after the next loop of while() was started. Use QTimer to avoid this situation.
Here is how to use QTimer in your case.
(mainwindow.h)
#include <QTimer>
...
QTimer timer;
(mainwindow.cpp)
MainWindow::MainWindow()
{
...
connect(&timer,SIGNAL(timeout()),this,SLOT(on_timer()));
}
void MainWindow::on_tstart_clicked()
{
timer.start(1000);
}
void MainWindow::on_timer()
{
if(timer.isActive()) return;
ttime--;
(display LCD values)
if(ttime<=0)
{
ttime=0;
(emit signal for alarm or whatever you want)
timer.stop();
}
}
void MainWindow::on_tpause_clicked()
{
timer.stop();
(display LCD values)
}
void MainWindow::on_treset_clicked()
{
timer.stop();
ttime=0;
(display zeroes)
}
(Posted on behalf of the OP).
Everything is solved! I was resetting the stopwatch display by accident. Thanks #Michael!
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// ttimer was declared in "MainWindow" -> QTimer ttimer;
connect(&ttimer, SIGNAL(timeout()), this, SLOT(on_timer()));
}
void MainWindow::on_tstart_clicked()
{
ttime = ttime == 0 ? (ui->hr->value() * 3600 + ui->min->value() * 60 + ui->sec->value()) : ttime;
ttimer.start(1000);
}
void MainWindow::on_timer()
{
if (!ttimer.isActive()) return;
--ttime;
const unsigned short hr = ttime / 3600, min = ttime % 3600 / 60, sec = ttime % 60;
ui->tsec2->display(sec % 10);
ui->tsec1->display(sec / 10);
ui->tmin2->display(min % 10);
ui->tmin1->display(min / 10);
ui->thr2->display(hr % 10);
ui->thr1->display(hr / 10);
if (ttime <= 0)
{
// (emit signal for alarm or whatever you want)
ttimer.stop();
}
}
void MainWindow::on_tpause_clicked()
{
ttimer.stop();
// (display LCD values)
}
void MainWindow::on_treset_clicked()
{
ttimer.stop();
ttime = 0;
ui->tsec2->display(0);
ui->tsec1->display(0);
ui->tmin2->display(0);
ui->tmin1->display(0);
ui->thr2->display(0);
ui->thr1->display(0);
}
I implemented QTimer in my code, but when I press the "Start" button (on_tstart_clicked) or any other buttons, nothing happens (no change in display).
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// ttimer was declared in "MainWindow" -> QTimer ttimer;
connect(&ttimer, SIGNAL(timeout()), this, SLOT(on_timer()));
}
void MainWindow::on_tstart_clicked()
{
ttime = ttime == 0 ? (ui->hr->value() * 3600 + ui->min->value() * 60 + ui->sec->value()) : ttime;
ttimer.start(1000);
}
void MainWindow::on_timer()
{
if (!ttimer.isActive()) return;
--ttime;
const unsigned short hr = ttime / 3600, min = ttime % 3600 / 60, sec = ttime % 60;
ui->tsec2->display(sec % 10);
ui->tsec1->display(sec / 10);
ui->tmin2->display(min % 10);
ui->tmin1->display(min / 10);
ui->thr2->display(hr % 10);
ui->thr1->display(hr / 10);
if (ttime <= 0)
{
// (emit signal for alarm or whatever you want)
ttimer.stop();
}
}
void MainWindow::on_tpause_clicked()
{
ttimer.stop();
// (display LCD values)
}
void MainWindow::on_treset_clicked()
{
ttimer.stop();
ttime = 0;
ui->ssec2>tsec2->display(0);
ui->ssec1>tsec1->display(0);
ui->smin2>tmin2->display(0);
ui->smin1>tmin1->display(0);
ui->shr2>thr2->display(0);
ui->shr1>thr1->display(0);
}
The issue was simple, but I kept glossing over it. I had to change ttimer.isActive() to !ttimer.isActive().
But, now the the reset (on_treset_clicked) doesn't work. The QLCDNumber's aren't reset to 0.
Related
I'm a first semester software student and for a course I am exploring the world of C++ and Arduino (UNO in my case).
My assignment is to make a timer counting down from 10 minutes (shown on a 4 digit display).
The thing I struggled with is how to program a pause functionality on the same button (btnOne) that also starts the timer. When I finally discovered how, I noticed something buggy: the delay() might be causing some trouble. See my code for implementation.
When clicking on the start/pause button the button doesn't immediately register it. Possibly because the delay might be blocking the communication of user input to the button.
The delay (of 1 second) is used to limit the execution of the if-statement in timer() to once per second; like a timer.
My question is: how can I improve my code so that the clicks on btnOne are immediately registered?
Thanks so much in advance.
const int btnOne = 8;
const int btnTwo = 9;
int btnOn = LOW;
int btnOff = HIGH;
const int clockPin = 10;
const int dataPin = 11;
const int buzzer = 3;
int btnOneState = 0;
int btnTwoState = 0;
long time = 600000;
long timeValueState = time;
int timerDisplay = 1000;
bool pause = true;
void setup()
{
Serial.begin(9600);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(btnOne, INPUT_PULLUP);
pinMode(btnTwo, INPUT_PULLUP);
pinMode(buzzer, OUTPUT);
}
void timer() {
// Count down from 600000 to 0
if (timeValueState >= 600 ) {
timeValueState = timeValueState - 600;
Display.show(--timerDisplay);
// When reached last count, display is set to 0 and buzzer turns on for 5 seconds
if (timeValueState == 600) {
Display.show(timerDisplay == 0);
tone(buzzer, 200);
delay(5000);
noTone(buzzer);
}
Serial.println("time:");
Serial.println(timeValueState);
delay(1000);
}
}
void loop()
{
btnOneState = digitalRead(btnOne);
btnTwoState = digitalRead(btnTwo);
// If button is on, change state of pause (true, false)
if (btnOneState == btnOn) {
Serial.println("btnOne pressed");
pause = !pause;
}
// When button is off and pause is not true, activate the timer
else if (btnOneState == btnOff && pause == false) {
Serial.println("btnOne NOT pressed");
timer();
}
// Reset time and timerDisplay back to 10 minutes.
if (btnTwoState == btnOn) {
Serial.println("btnTwo pressed");
timeValueState = time;
Display.show(timerDisplay = 1000);
}
}
I won't give you code so you learn more.
You can run a loop that checks the return value of millis vs a timestamp stored in a variable, when you started. Every 1000 milliseconds you update your display.
That way you don't block your code for 1 second.
This should serve as a good starting point for further research.
I'm having a QTimeEdit displaying hh:mm.
This widget behavior is very annoying: When the value is 09:59, there is no easy way to move to 10:00. The spinbox controls on the right only applies to minutes or hours. When you put your cursor on minutes, incrementing is not allowed if value is 59.
To move from 09:59 to 10:00 you need to decrement 59 to 00 and then increment 09 to 10...that's really annoying.
Is there no option to make it possible to let user increment minutes by one using the spinbox control and then get value be changed from 09:59 to 10:00 with a single click??
Set wrapping to true.
As for the automatic change of the hours section, reimplement stepBy in a subclass of QTimeEdit like this:
MyTimeEdit.h
#ifndef MYTIMEEDIT_H
#define MYTIMEEDIT_H
#include <QTimeEdit>
class MyTimeEdit : public QTimeEdit
{
Q_OBJECT
public:
explicit MyTimeEdit(QWidget *parent = nullptr);
void stepBy(int steps) override;
};
#endif // MYTIMEEDIT_H
MyTimeEdit.cpp
#include "MyTimeEdit.h"
MyTimeEdit::MyTimeEdit(QWidget *parent) : QTimeEdit(parent)
{
setWrapping(true);
}
void MyTimeEdit::stepBy(int steps)
{
QTime cur = time();
QTimeEdit::stepBy(steps);
if (currentSection() == QDateTimeEdit::MinuteSection) {
int m = cur.minute();
if ((m == 0) && (steps < 0))
setTime(time().addSecs(-3600));
else if ((m == 59) && (steps > 0))
setTime(time().addSecs(3600));
}
else if (currentSection() == QDateTimeEdit::SecondSection) {
int s = cur.second();
if ((s == 0) && (steps < 0))
setTime(time().addSecs(-60));
else if ((s == 59) && (steps > 0))
setTime(time().addSecs(60));
}
}
I noticed that the above answer does not work when using the mouse wheel or Page Up/Page Down instead of the arrow keys, because then the step is larger than 1 (10 in my case).
Here is a version of MyTimeEdit.cpp that also works with the mouse wheel and Page Up/Down keys:
MyTimeEdit.cpp
#include "MyTimeEdit.h"
MyTimeEdit::MyTimeEdit(QWidget *parent) : QTimeEdit(parent)
{
setWrapping(true);
}
void MyTimeEdit::stepBy(int steps)
{
if (currentSection() == QDateTimeEdit::MinuteSection) {
setTime(time().addSecs(steps*60));
}
else if (currentSection() == QDateTimeEdit::SecondSection) {
setTime(time().addSecs(steps));
}
}
I am trying to do a project is creating some graphics on window with Qt GUI C++ 5.6.2.
I have two methods named 'createVerticalSpeedIndicator' and 'createAirSpeedIndicator'. These methods need to create some graphics with a while(1) loop and use qApp->processEvents(); on window and they are doing it perfectly when one of them is working the other one is deactive. But I need to run both of them simultanously and always.
What can I do to run them simultanously and always.
Thank you So much
The solution is to invert the control flow. The while() { ... processEvents() ... } is an anti-pattern in asynchronous code, because it assumes that you have locus of control whereas you really don't. You're lucky that you didn't run out of stack since processEvents could potentially re-enter the createXxx methods.
Here's a complete example of a transformation:
// Input
void Class::createVerticalSpeedIndicator() {
for (int i = 0; i < 100; i ++) {
doStep(i);
QCoreApplication::processEvents();
}
}
// Step 1 - factor out state
void Class::createVerticalSpeedIndicator() {
int i = 0;
while (i < 100) {
doStep(i);
QCoreApplication::processEvents();
i++;
}
};
// Step 2 - convert to continuation form
void Class::createVerticalSpeedIndicator() {
int i = 0;
auto continuation = [=]() mutable {
if (!(i < 100))
return false;
doStep(i);
QCoreApplication::processEvents();
i++;
return true;
};
while (continuation());
};
// Step 3 - execute the continuation asynchronously
auto Class::createVerticalSpeedIndicator() {
int i = 0;
return async(this, [=]() mutable {
if (!(i < 100))
return false;
doStep(i);
i++; // note the removal of processEvents here
return true;
});
};
template <typename F> void async(QObject * parent, F && continuation) {
auto timer = new QTimer(parent);
timer->start(0);
connect(timer, &QTimer::timeout, [timer, c = std::move(continuation)]{
if (!c())
timer->deleteLater();
});
}
At that point you can apply the same transformation to createAirSpeedIndicator and start them in the constructor of your class:
Class::Class(QWidget * parent) : QWidget(parent) {
...
createVerticalSpeedIndicator();
createAirSpeedIndicator();
}
Both tasks will run asynchronously and pseudo-concurrently within the main thread, i.e. each task will alternatively execute a single step.
Suppose we wanted to chain the tasks, i.e. have a task start only after the previous one has finished. The modification in the user code can be simple:
Class::Class(QWidget * parent) : QWidget(parent) {
...
createVerticalSpeedIndicator()
>> createAirSpeedIndicator()
>> someOtherTask();
}
The async function must now return a class that allows such connections to be made:
struct TaskTimer {
QTimer * timer;
TaskTimer & operator>>(const TaskTimer & next) {
next.timer->stop();
connect(timer, &QObject::destroyed, next.timer, [timer = next.timer]{
timer->start(0);
});
timer = next.timer;
return *this;
}
};
template <typename F> TaskTimer async(QObject * parent, F && continuation) {
TaskTimer task{new QTimer(parent)};
task.timer->start(0);
connect(task.timer, &QTimer::timeout,
[timer = task.timer, c = std::move(continuation)]{
if (!c())
timer->deleteLater();
});
return task;
}
I've been studying this thread
Processing 2.0 - Open file dialog
with regard to the use of selectinput().
I'm hoping to import point data to construct some 3d plots.
I can import the data and construct the plot ok but when trying to use
selectinput() to choose a file I run into trouble.
The difficulty I am having is that selectinput() appears to be incompatible with a P3D
window.
Using OS X10.10
for example This code works
void setup() {
size(400, 400,P3D); //3Dgraphics specified
background(0);
stroke(255);
frameRate(20);
}
void draw() {
noFill();
ellipse(mouseX, mouseY, 90, 90);
and this works
String [] myInputFileContents ;
String myFilePath;
void setup() {
selectInput("Select a file : ", "fileSelected");
while (myInputFileContents == null) {//wait
// println("wait"); //If there is nothing inside the curly brackets
//delay(3000); //this doesn't work
size(400, 400 );/// If P3D is added it won't work
background(0);
//smooth();
stroke(255);
frameRate(25);
}
}
void draw() {
box(mouseX, mouseY, 150);
println("Selected at this point " + myFilePath);
}
void mousePressed() {
selectInput("Select a file : ", "fileSelected");
}
void fileSelected(File selection) {
if (selection == null) {
println("no selection so far...");
}
else {
myFilePath = selection.getAbsolutePath();
myInputFileContents = loadStrings(myFilePath) ;// this moves here...
println("User selected " + myFilePath);
}
}
but if
size(400, 400);
is changed to
size(400,400,P3D);
The frame displays but it won't draw.
Could someone point me to the answer?
It seems that this problem is some kind of frame loading problem. This "hackish" approached worked for me under OS X 10.10 Yoesemite. It should also work under windows. Oh and whenever you use selectInput() please use mouseReleased so the function only gets called once :)
Setup() gets called twice for whatever reason, this is why we have the didPreSelect boolean.
You call mousePressed instead of mouseReleased
The loop already had the screen selection in it, this is why i changed the loop.
The delay also has problems, this is why i added the delay inside the while-loop.
Man that was quite the challenge, +1 for asking.
PS: I added an exit() if you press cancel the first time you open the application, which should be more convenient.
String [] myInputFileContents ;
String myFilePath;
boolean didPreSelect = false;
boolean secondPress = false;
void setup() {
if (didPreSelect == false)
{
didPreSelect = true;
selectInput("Select a file : ", "fileSelected");
}
//frame = null;
while (myInputFileContents == null || myInputFileContents.length < 2) {//wait
delay(1000);
}
//this doesn't work
size(400, 400, P3D);
background(0);
//smooth();
stroke(255);
frameRate(25);
}
void draw() {
box(mouseX, mouseY, 150);
//println("Selected at this point " + myFilePath);
}
void mouseReleased() {
if(secondPress == false) secondPress = true;
selectInput("Select a file : ", "fileSelected");
}
void fileSelected(File selection) {
if (frameCount < 50)
{
if (selection == null) {
println("no selection so far...");
if(secondPress == false) exit();
} else {
myFilePath = selection.getAbsolutePath();
myInputFileContents = loadStrings(myFilePath) ;// this moves here..
println("User selected " + myFilePath);
}
}
}
I want to use CCTimer class for the Timer but I can't sort out. After I use to manually create a function for this but it seems not effective .
protected GameLayer(ccColor4B color)
{
super(color);
schedule(new UpdateCallback() {
#Override
public void update(float d) {
countTime(d);
}
},0.99f);
}
public void countTime(float scr) {
if(_label != null){
this.removeChild(_label,true);
}
j=j-(int)scr;
CGSize winSize = CCDirector.sharedDirector().displaySize();
_label = CCLabel.makeLabel("Time Left :" + j, "Verdana", 20);
_label.setColor(ccColor3B.ccGREEN);
_label.setPosition(155f, winSize.height - 15);
addChild(_label);
if(j<=0){
CCDirector.sharedDirector().pause();
}
}
it run from 1 to the the point which i want to stop ... !!!
what should i do to use the CCTimer class to resolve this problem ?
CCTimer is available but I haven't tried this earlier but you can do this thing through Timer class :
Timer timer = new Timer();
timer.scheduleAtFixedRate( new TimerTask(){
public void run() {
updateTimeLabel();
}
public void updateTimeLabel() {
float time += 1;
String string = CCFormatter.format("%02d:%02d", (int)(time /60) , (int)time % 60 );
CCBitmapFontAtlas timerLabel = (CCBitmapFontAtlas) getChildByTag(TIMER_LABEL_TAG) ;
timerLabel.setString(string);
}