Making the clock tick
UI frameworks are mostly event-driven, and Kivy is no exception. The distinction from the "usual" procedural code is simple—the event-driven code needs to return to the main loop often; otherwise, it will be unable to process events from a user (such as pointer movement, clicks, or window resize), and the interface will "freeze". If you're a longtime Microsoft Windows user, you are probably familiar with programs that are unresponsive and freeze very often. It is crucial to never let this happen in our apps.
Practically, this means that we can't just code an infinite loop like this in our program:
# Don't do this while True: update_time() # some function that displays time sleep(1)
Technically, it might work, but the application's UI will stay in the "not responding" state until the application gets killed (forcefully stopped) by the user or an operating system. Instead of taking this faulty approach, we need to keep in mind that there is a main loop running inside Kivy, and we need to take advantage of it by utilizing events and timers.
Event-driven architecture also means that in many places, we will listen to events to respond to various conditions, be it user input, network events, or timeouts.
One of the common events that many programs listen to is App.on_start
. A method with this name, if defined on the application class, will be called as soon as the app is fully initialized. Another good example of an event that we will find in many programs is on_press
, which fires when the user clicks, taps, or otherwise interacts with a button.
Speaking of time and timers, we can easily schedule our code to run in the future using a built-in Clock
class. It exposes the following static methods:
Clock.schedule_once
: Runs a function once after a timeoutClock.schedule_interval
: Runs a function periodically
Note
Anyone with a JavaScript background will easily recognize these two functions. They are exactly like window.setTimeout
and window.setInterval
in JS. Indeed, the Kivy programming model is very similar to JavaScript even if the API looks completely different.
It's important to understand that all timed events that originate from Clock
run as a part of Kivy's main event loop. This approach is not synonymous to threading, and scheduling a blocking function like this may prevent other events from being invoked in a timely manner, or at all.
Updating the time on the screen
To access the Label
widget that holds time, we will give it a unique identifier (id
). Later, we can easily look up widgets based on their id
property—again, a concept which is very similar to web development.
Modify clock.kv
by adding the following:
Label: id: time
That's it! Now we can access this Label
widget from our code directly using the root.ids.time
notation (root
in our case is BoxLayout
).
Updates to the ClockApp
class include the addition of a method to display time, update_time
, which looks like this:
def update_time(self, nap): self.root.ids.time.text = strftime('[b]%H[/b]:%M:%S')
Now let's schedule the update function to run once per second after the program starts:
def on_start(self): Clock.schedule_interval(self.update_time, 1)
If we run the application right now, we'll see that the time displayed is being updated every second. To paraphrase Neil Armstrong, that is one small step for mankind, but a sizable leap for a Kivy beginner.
It's worth noting how the argument to strftime
combines Kivy's BBCode-like tags described earlier with the function-specific C-style format directives. For the unfamiliar, here's a quick and incomplete reference on strftime
formatting essentials:
Format string (case-sensitive) |
Resulting output |
---|---|
|
Second as two digits, typically |
|
Minute as two digits, |
|
Hour as per 24-hour clock, |
|
Hour as per 12-hour clock, |
|
Day of the month, |
|
Month (numeric), |
|
Month (string), for example, |
|
Year as four digits, such as |
Tip
For the most complete and up-to-date documentation on displaying time, please refer to the official reference manual—in this case, Python standard library reference, located at https://docs.python.org/.
Binding widgets using properties
Instead of hardcoding an ID for each widget that we need to access from Python code, we can also create a property and assign it in a Kivy language file. The motivation for doing so is mostly the DRY principle and cleaner naming, at a cost of a few more lines of code.
Such a property can be defined as follows:
# In main.py from kivy.properties import ObjectProperty from kivy.uix.boxlayout import BoxLayout class ClockLayout(BoxLayout): time_prop = ObjectProperty(None)
In this code fragment, we make a new root widget class for our application based on BoxLayout
. It has a custom property, time_prop
, which is going to reference Label
we need to address from Python code.
Additionally, in the Kivy language file, clock.kv
, we have to bind this property to a corresponding id
. Custom properties look and behave no different from the default ones and use exactly the same syntax:
ClockLayout: time_prop: time Label: id: time
This code makes the Label
widget accessible from the Python code without knowing the widget's ID, using the newly defined property, root.time_prop.text = "demo"
.
The described approach is more portable than the previously shown one and it eliminates the need to keep widget identifiers from the Kivy language file in sync with the Python code, for example, when refactoring. Otherwise, the choice between relying on properties and accessing widgets from Python via root.ids
is a matter of coding style.
Later in this book, we'll explore more advanced usage of Kivy properties, facilitating nearly effortless data binding.