In the last lesson,
we completed the user interface for our Pomodoro timer,
and we've managed to get all the components that we need onto our graphical user
interface. Now, the next step is to actually give it some functionality.
I want to be able to create some sort of countdown mechanism that just does
something really simple. For example,
if it was just able to count down from five, four, three, two,
one going down by one each time, that would be great.
That's what we're going to be working on in this lesson.
And we're going to go and see the countdown mechanism section to do that.
One of the ways that you might think about approaching this is to use our time
module
cause we've seen before that we can say time.sleep and we can tell it to
sleep for a second.
So then we could maybe set up a while loop and while something or other is true,
go ahead and sleep for one second. And then afterwards just,
you know,
subtract one from some sort of counter. So that we could start count at five
and then each time we subtract by one and then each time we just simply update
our label here,
which we created in the canvas to whatever value count might be.
Now, that sounds great in principle.
The only problem is that we're working within a graphical user interface
program.
The reason why that's relevant is because if we think about a command line
program, say for example,
if we were to get our console to do something, print
hello, well,
it's only going to do something when you actually give it an instruction and you
hit enter.
It doesn't really need to keep an eye out for what you might do in between.
But a graphical user interface is a little bit different.
It needs to keep watching the screen to see whether
if a user clicks on a button, for example.
So it's basically going to refresh and keep listening for events.
So every fraction of a second,
it's going to keep checking, did something happene, did
something happen, did something happen. And the moment when it does,
then it's got to react. It's got to react to that event.
In this case, we tend to call these types of GUI programs event-driven.
And the way that it's driven is through our main loop.
So when we set up our window and we start off the main loop,
it's basically looping through and every millisecond it's checking to see did
something happen, did something happen, did something happen?
So that means if we have another loop in our program,
it actually won't be able to reach the main loop. And in this case,
when you actually try to run it, nothing happens.
Our program doesn't even launch.
So we have to rethink this and we have to do it a little bit differently. In
order to create interactive and interesting programs,
you kind of need something to happen on screen, right?
Every so often. You need this timing mechanism. Luckily,
tkinter already thought of this.
And we can in fact use one of the builtin methods to every widget.
So if we tap into our window widget,
we can get hold of a method called after and after is quite simple.
It's a method that takes an amount of time that it should wait
and then after that amount of time,
it simply calls a particular function that you tell it to call passing in any
arguments that you want to give it. Here's how it works.
We call window.after, we first provide the amount of time to wait in
milliseconds. So if we want one second, then that's 1000 milliseconds.
Next we pass in a function to call.
So let's create a function up here. Let's just call it, say something,
and then we'll pass in the thing,
like this. And then all we do is we just print that thing.
So super simple function.
And then we give the name of this function as the function to call after 1000
milliseconds.
Now the final thing in this list of arguments,
if I just go ahead and cut that and show you again, when it gives me the prompt,
the last thing is actually a *args.
This, if you remember, allows us to put in an unlimited number
of positional arguments.
What that means is we can give as many arguments as we want
and those arguments, in this case, is simply going to be passed to the function
that we want to call. So in this case, it's going to be that thing.
So if I put hello here then I run my code,
you can see that after 1000 milliseconds, basically one second,
it calls this function,
say_something, and it passes this hello as the input
to that function. As I said,
you can have an infinite amount of positional arguments.
So let's put in some other arguments which we'll call a,
b, and c,
and then we'll print a, print b, and print c.
And now instead of passing in hello,
we're going to pass in lots of positional parameters. So we'll say 3, 5,
and 8. Now, when I hit run,
you will see it waits for one second
and then it passes all three of these parameters to say something
and it goes ahead and prints all of those out at once.
So this is how the after method works. But what we wanted to do though,
is we want it to repeat itself,
to essentially loop. One way of getting that behavior is to simply put this
method,
call somewhere inside a function and then call itself.
So here's what I mean. Let's create a function called a count_down,
and this is going to take a input in the form of the number to count down by.
And then inside this function, we call window.after.
And we say that after 1000 milliseconds,
call this function count_down and then pass in a count number.
If that count number started out as 5,
then we want to say count - 1.
Now all we have to do is to call this countdown method.
So let's call count_down and passing the starting count,
let's say 5 seconds. So now when I run the code,
it's going to call this method passing in 5 over here,
and then it's going to wait for one second,
and then it's going to call this function count_down passing in five minus one
so it becomes four. And then afterward it repeats again, becomes three,
two, one. So now if we catch that number
which we can print, then we'll be able to see it count down
when we run the code; five, four, three,
two, one. One every second,
and it basically keeps on going and it even continues to the negatives.
So if we don't want it to go to negative time,
then all we have to do is add an if statement.
If count is greater than zero, then go ahead and execute this line of code.
So now it'll go from five, four, three, two,
one, zero, and then it will stop.
This is the kind of behavior that we would need if we want to update our
countdown in our Pomodoro timer.
So how can we instead of printing
the count actually change this text on our canvas? We'll,
the way that we do that is by assigning this text a variable.
So I'm going to call it timer_text
and now that we've got timer_text being a set as the text that was created in
the canvas, then we can access it right here.
And the way that we'd change a piece of text or anything for that matter in a
canvas is slightly different from how we would do for a label.
If it was just the title label that we wanted to change, we would say title_
label.config, and then let's change the text to something new.
But to change a canvas element,
you actually have to tap into the particular canvas you want to change and
Then you call a method called itemconfig. And then in this method,
you pass in the particular item that you actually want to configure,
so in our case it's the timer_text,
and then you pass in the thing about it that you actually want to change in
terms of a kwarg, so this is a keyword argument.
We're going to change the text to the current count.
Now notice how this is not the string count,
because then it would just show that word,
but its actually the live countdown time. At this point in time
if I run the code,
I actually get a error and it tells us that the name canvas is not defined
and that's because I'm calling this method countdown before I actually created
the canvas.
So if I move that to below this line and I run it again,
then you'll see it actually work.
And you see it starts out from five and it counts down to zero.
Now how can we tie that behavior to the start button so that I can press the
start button and then and only then does it start counting down from five,
four, three, two, one? Well, let's go ahead and add
another function and I'm gonna add it in the timer mechanism section and I'm
going to call it start_timer.
Now this function is super simple.
All it's going to do is it's going to be responsible for calling that function
countdown and it's going to count down from five seconds.
So I'll move that inside the start_timer.
And now the start timer is going to be the function that needs to be triggered
when the start button gets pressed.
Do you remember how to tie a function to a button in
tkinter? Pause the video and see if you can solve this challenge so that you'll
be able to run the code, hit start and the timer to start counting down.
So the keyword argument is command and all we have to do is to tie it to the
start_timer function, but without the parentheses.
So now when I hit run and I click start,
it starts the timer setting that text from five, four, three,
two, one.
So the main loop is listening and when the user interacts with the start button
it actually calls the start_timer function which calls the count_down function
and get it to count down from five seconds.
Now we don't actually want to count down from five seconds.
We want to count down in minutes because we're probably not going to be working
for five seconds at a time.
We're going to be working for 25 minutes or having a minute break.
So how can I change this countdown to interpret this instead of as five seconds
to five minutes? Well, let's have a think about that.
If we wanted to count down, let's say one minute,
then that in terms of seconds would be 60 seconds.
All we have to do is to take the number of minutes that we want to count down
by and multiply it by 60. In this case,
if we wanted to count down it by five minutes instead of five seconds,
all we have to do is multiply by 60.
So then when we call this function count_down,
instead of getting five seconds to count down,
we get 300 seconds to count from.
But now if we run our code, you can see it's going to start from 300.
It's going to go down all the way down to zero. Now in terms of time,
that is five minutes, but this is not a very good way to visualize it.
Nobody thinks in terms of 288 seconds remaining, right?
So we have to format this count so that we can display it in the format of
00:00 like the usual kind of time, where for example,
you have one minute and 35 seconds remaining or something like that.
So how can we create something like this? If we have the count in terms of
seconds,
so let's say we have 300, and we wanted to know how many minutes where in that
then all we have to do is take 300 and then divide it by 60 and we would get
5, so that's 5 minutes. But what if the countdown has already been going
and instead we had 245 seconds remaining?
Well,
we can actually get hold of how many minutes and seconds that is equivalent to.
And the way we would do that is by taking that number, say 245,
dividing it by 60 seconds
and we would get a number 245 / 60 is 4.08,
3 recurring.
If we rounded that number down to get rid of all of the decimal places,
then that would be equal to 4 minutes.
And then if we want to get hold of how many seconds there are after we've gotten
hold of the four minutes,
then the way we do that is to use the modular because remember the modular
divides a number by another number, so 245 divided by 60,
and then it will give us the remainder.
How much is left after it's cleanly divided. And in this case,
this would actually be the number of seconds remaining after the four minutes
has been taken away. So let's write this code out.
The count minutes would be the count divided by 60,
but then we have to round it down so that we get rid of all of the remainder.
Now we don't want to round it so that if it was something like 3.6
it becomes 4.
We actually just want to get rid of everything after the decimal place. To do
that,
the easiest way is to import the math module and then use a function called math
.floor and this math.floor,
if I hover over it, is going to return the largest whole number that is less than
or equal to x.
If this was a 4.8, then the largest hole number less than 4.8 is 4.
So that's basically what this is going to do.
And this is going to give us the number of minutes. Now,
the next thing we want to do is to know, well,
how many seconds is left after we've taken away the minutes. To do this,
we're going to do counts
and then we're going to use the modulo to divide it by 60.
So the modulo is going to give us the remainder number of seconds after we've
cleanly divided it by 600. For example,
if we had 100 seconds and we divide that by 60, well,
that's going to be equal to one.
So we can minus 60 from 100 and we get 40 as the remainder.
That is what we will get after doing the modulo.
So that's basically the number of seconds that remains.
So now that we've got the minute and the second, we can actually change this
count to use an f-string and we can format it so that we add the count minute
first,
and then we add a colon and then we add our count in seconds.
Now look at what happens. We're going to try and count down five minutes.
So we get hold of the number of seconds, we pass it over to this function,
we work out what the count is equivalent to in minutes and seconds
and then afterwards we subtract one second each time.
So now when I run the code and I hit start,
notice how it starts from 5 minutes, goes down to 4 minutes,
50, 59,
and then it keeps ongoing like a real timer.
That's pretty cool.
The only thing left to figure out is how can we get it to not display 5:0?
How can we get it to display 5:00
like you would see on a clock? To find out how to do that
we have to learn about Python dynamic typing.
We have to understand how that works and that is what we're going to be talking
about in the next lesson. So I'll see you there.