Python kivy code from book Kivy Blueprints - python

I am trying to follow the book "Kivy Blueprints" from Mark Vasilkov. On page 21 he introduces a function that updates the text of a label.
There are two files inside the project folder (see code below). Inside the class ClockApp the function update_time(self, nap) is defined. I am using Intellij Idea Community with the python plugin and the integrated development environment (IDE) tells me that nap is an unused parameter. If I remove nap as parameter I get an error update_time() takes 1 positional argument but 2 were given. How can I get rid of this dummy parameter?
# Source: Chapter 1 of Kivy Blueprints
# File: main.py
from time import strftime
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.utils import get_color_from_hex
Window.clearcolor = get_color_from_hex("#101216")
# from kivy.core.text import LabelBase
class ClockApp(App):
def update_time(self, nap):
self.root.ids.time.text = strftime("[b]%H[/b]:%M:%S")
def on_start(self):
Clock.schedule_interval(self.update_time, 1)
if __name__ == "__main__":
ClockApp().run()
There is also an additional clock.kv file
# File: clock.kv
BoxLayout:
orientation: "vertical"
Label:
id: time
text: "[b]00[/b]:00:00"
font_name: "Roboto"
font_size: 60
markup: True

the binding always passes additional information, for example in this case it sends us the exact time period in which the function was called. If you do not want to use it you can use a lambda method:
class ClockApp(App):
def update_time(self):
self.root.ids.time.text = strftime("[b]%H[/b]:%M:%S")
def on_start(self):
Clock.schedule_interval(lambda *args: self.update_time(), 1)
if you only want to silence the warning: "unused parameter" you could use the _:
class ClockApp(App):
def update_time(self, _):
self.root.ids.time.text = strftime("[b]%H[/b]:%M:%S")
def on_start(self):
Clock.schedule_interval(self.update_time, 1)

Related

Python Kivy - Call methods across different class

I am new to coding, a few months with Python and trying to wrap my head around Kivy. I think there is a simple solution to this but I am struggling when it comes to calling a method in one class from another. Not even sure if this is possible, my OOP wouldn't be very strong!!
Would appreciate if someone could explain this to me! I've looked online but still struggling to understand what I need to do.
i have a simple code that had a label and 3 toggle buttons, the label text changes to show how many toggle buttons are pressed. Below is the original code.
What I am trying to do create the toggle buttons using a loop so that the number of toggle buttons can be easily altered. i have achieved this but when I try and bind the method to the toggle button the code fails with. I also tried defining a method within the "Tbtn" class to call the Main.Counter() but this didn't work either.
The line
self.bind(on_state = Main.counter())
in the init of the toggle button is where i am going wrong I think.
Any help and even an explanation would be great. Not the first time I have been stuck on this!! Thanks
Original Code:
from kivy.app import App
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
class Tbtn(ToggleButton):
pass
class Header_Box(BoxLayout):
pass
class Counter(BoxLayout):
pass
class Main(BoxLayout):
count = NumericProperty()
def counter(self,widget):
toggles = []
for child in self.ids.Seat_Box.children:
if isinstance(child, ToggleButton):
if child.state == 'down':
toggles.append(child.text)
self.count = len(toggles)
print(self.count)
class TestApp(App):
def build(self):
return Main()
TestApp().run()
The KV file:
<Main>:
name: "main"
BoxLayout:
orientation: "vertical"
Header_Box:
Label:
text: str(root.count)
Counter:
id: Seat_Box
Tbtn:
id: btn1
on_state: root.counter(self)
Tbtn:
id: btn2
on_state: root.counter(self)
Tbtn:
id: btn2
on_state: root.counter(self)
Code with for Loop:
from kivy.app import App
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
class Tbtn(ToggleButton):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(on_state = Main().counter())
class Header_Box(BoxLayout):
pass
class Counter(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for x in range(3):
btn = Tbtn()
self.add_widget(btn)
class Main(BoxLayout):
count = NumericProperty()
def counter(self,widget):
toggles = []
for child in self.ids.Seat_Box.children:
if isinstance(child, ToggleButton):
if child.state == 'down':
toggles.append(child.text)
self.count = len(toggles)
print(self.count)
class TestApp(App):
def build(self):
return Main()
TestApp().run()
KV file:
<Main>:
name: "main"
BoxLayout:
orientation: "vertical"
Header_Box:
Label:
text: str(root.count)
Counter:
id: Seat_Box
Firstly, Remove self.bind(on_state = Main().counter()).
I suggest you to solve this in .kv side.
Way 1-.kv side:
Add this below your .kv file:
<Tbtn>:
on_state: app.get_running_app().root.counter(self)
Way 2-.py side: Add this in Tbtn class.
def on_release(self):
App.get_running_app().root.counter(self)
Although the other answer already solved your issue, the following made me post this one.
Any help and even an explanation would be great...
Technically the following line,
self.bind(on_state = Main().counter())
is wrong for various reasons. Let's try to figure this out.
The method on_state is kind of generic one not a default event (like on_press etc.). That's why bind(on_state = some_callback) won't work.
Again you did Main().counter() which actually creates a new instance of Main (which may or may not be related to the root, and here it's of course not) and assigned to its method.
It seems you want to just access one of Main widget's (which happens to be the root widget here) method.
Since you used kvlang, this could be done more efficiently as follows,
<Tbtn>:
on_state: app.root.counter()
You can find more about this in the kvlang doc.
Now in .py you just define the class along with some other changes,
class Tbtn(ToggleButton):
pass
.
.
.
class Main(BoxLayout):
count = NumericProperty()
def counter(self): # Pass no extra args as you haven't done in 'on_state' method.
toggles = []
.
.
.

How do I fix 'ValueError: callback must be a callable, got 18:19:46'

So I'm trying to get a good grasp on kivy and I want to create a basic clock app and I keep running into a problem when I go to update the clock label. I've looked at other threads on here and still cant find the right solution. I believe I am doing something wrong at Clock.schedule_interval but again, I'm trying to get a grasp on kivy and I'm not sure. Any help would be greatly appriciated
Python code
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.clock import Clock
import time
Builder.load_file('myApp.kv')
class ClockScreen(Widget):
def updateClock(self):
hour = time.strftime("%H")
minute = time.strftime("%M")
second = time.strftime("%S")
Clocktime = self.root.ids.clock.text
print(Clocktime)
newTime = self.root.ids.clock.text = f'{hour}:{minute}:{second}'
return newTime
class FirstApp(App):
def build(self):
return ClockScreen()
def on_start(self):
Clock.schedule_interval(ClockScreen.updateClock(self), 1)
FirstApp().run()
```
KV File
:
FloatLayout:
size: root.width, root.height
Label:
id: clock
text: "12:00"
Try
def on_start(self):
Clock.schedule_interval(ClockScreen.updateClock, 1)
Reason:
ClockScreen.updateClock is a callable (a function handle). This is also explained in the docs.
ClockScreen.updateClock(self) is actually calling the function that returns the time, and you cannot schedule a string (18:19:46, as per your error).
However, this doesn't say which instance of the ClockScreen gets updated (you are referencing the function via a class rather than a specific instance), so this may be more accurate for what you want
class FirstApp(App):
def build(self):
return ClockScreen()
def on_start(self):
cs = self.build()
Clock.schedule_interval(cs.updateClock, 1)

How to Pass args without affecting "self"

I've been working with this code for a days now.
I cant make it work, my goal is to pass an arguments from a function from a different CLASS to another function in a different CLASS without affecting the 'self'
Please see sample code.
PY CODE:
`
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.uix.button import Button
#Custom Button
class PassdataButton(Button):
def on_release(self):
ThisScreen.getters(datas=['5','3'])
class ThisScreen(Screen):
def getters(self,datas):
self.ids.gettersBox.text = f"There is {datas[0]} Apple's and {datas[0]} Banana's"
class SM(ScreenManager):
pass
kv_file = Builder.load_file('getData.kv')
class projectApps(App):
def build(self):
return kv_file
if __name__ == "__main__":
projectApps().run()
`
KV CODE:
SM:
ThisScreen:
name: 'ThisScreen'
<ThisScreen>:
canvas.before:
canvas:
Rectangle:
size: self.size
source: 'im-502378444.jpg'
BoxLayout:
padding: 10
orientation: 'vertical'
TextInput:
id: gettersBox
BoxLayout:
size_hint_y: .3
orientation: 'horizontal'
Button:
text: 'GetterButton'
on_release: root.getters(datas = 'this data is from GetterButton')
#CustomButton
PassdataButton:
text: "Apple's & Banana's"
Everytime I run the code and click the Custom Button I created, it's getting me an error
ThisScreen.getters(datas=['5','3'])
TypeError: getters() missing 1 required positional argument: 'self'
When I add a value for the missing positional argument for self like ThisScreen.getters(self=True, datas=['5','3']) its giving me different error and I cant access/call anymore other elements under the the getters() function.
self.ids.gettersBox.text = f"There is {datas[0]} Apple's and {datas[0]} Banana's"
AttributeError: 'bool' object has no attribute 'ids'
Hope you can help me with this simple code.
Thanks a bunch!
You have two options.
1. You can pass it direct from your kv file. Since you're already on the ThisScreen screen, you can just delete your on_relase method in python and call the function inside .kv from root like this:
PassdataButton:
text: "Apple's & Banana's"
on_release: root.getters(datas=['5', '3'])
2. Or if you want to use python for this, you will have to get your running app, the screen manager, then jump to the screen with the desired function, and only there you can call your getter:
class PassdataButton(Button):
def on_release(self):
App.get_running_app().root.get_screen('ThisScreen').getters(datas=['5', '3'])
# App -> App instance
# get_running_app() -> current running
# root -> Screen Manager
# get_screen('ThisScreen') -> Desired screen

Why isn't kv binding of the screen change working?

I've defined two buttons: one in kv and one in Python. They are located in different screens and are used to navigate between them. What I found strange is that the button that was defined in Python successfully switched the screen, while the one defined in kv did not. Perhaps I'm not accessing the App class method properly?
Here is the code of the issue:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
Builder.load_string('''
<MyScreen1>:
Button:
id: my_bt
text: "back"
on_release: app.back
''')
class MyScreen1(Screen):
pass
class TestApp(App):
def here(self, btn):
self.sm.current = "back"
def back(self, btn):
self.sm.current = "here"
def build(self):
self.sm = ScreenManager()
s1 = Screen(name = "here")
bt = Button(text = "here",
on_release = self.here)
s2 = MyScreen1(name = "back")
#s2.ids['my_bt'].bind(on_release = self.back)
self.sm.add_widget(s1)
s1.add_widget(bt)
self.sm.add_widget(s2)
return self.sm
TestApp().run()
So if I define the switching function in kv (on_release), I can't go to the "here" screen. But if I uncomment that line in Python and comment the on_release: app.back instead, everything works fine.
I'm pretty sure that this is the correct way to access the current app, since it doesn't give me any errors (which means that the method was successfully located)
That's a subtle difference between kv and python: In kv you actually have to write the callback as a function call (a python expression), in this case:
on_release: app.back(self)

Update widgets after specified time

I cannot understand how to update kivy screen.
This is my python file:
import kivy
kivy.require('1.1.1')
from kivy.app import App
from kivy.uix.widget import Widget
import time
class PongGame(Widget):
labe = ObjectProperty(None)
def settext(self,x):
self.labe.text = str(x)
print "DONE"
class PongApp(App):
def build(self):
game = PongGame()
game.settext(1)
time.sleep(3)
game.settext(5)
time.sleep(3)
game.settext(87)
time.sleep(3)
game.settext(5)
return game
if __name__ == '__main__':
PongApp().run()
this is my kv file
#:kivy 1.0.9
<PongGame>:
labe:lab
Label:
id: lab
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: str("Hello")
When I run it all it freezes. Then all I see is a 5.
How can i get the others to show up?
def build(self):
game = PongGame()
game.settext(1)
time.sleep(3)
game.settext(5)
time.sleep(3)
game.settext(87)
time.sleep(3)
game.settext(5)
return game
Your problem is that sleep blocks the entire thread - your whole program just stops doing anything for the duration of the call. That includes any graphical interface that has been drawn, it can't update or even receive input because it's running in the same thread and doesn't get run again until your blocking function call has stopped.
Actually, you also have the problem that all these changes take place before the gui is even drawn (the game is displayed in the kivy window only after it's returned from build).
You have to instead think in terms of kivy's main loop and clock - in the background kivy is trying to run certain functions like touch detection and graphical updates as frequently as possible. If you run some code in this loop that takes a long time, none of the rest of kivy can work until your code terminates. This is normal in gui programming, you always have to be careful not to block the main program loop.
The easiest way in kivy to schedule something regularly is to create a function and hook into this event loop, telling the clock to run your function after some interval or after every repeat of some time period. In your case, you want to change the text every 3 seconds.
Here is a short example of how to achieve exactly what you want:
from kivy.app import App
from kivy.uix.widget import Widget
import time
from kivy.properties import ObjectProperty
from functools import partial
from kivy.lang import Builder
from kivy.clock import Clock
Builder.load_string('''
<PongGame>:
labe:lab
Label:
id: lab
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: str("Hello")
''')
class PongGame(Widget):
labe = ObjectProperty(None)
def settext(self, x, *args):
self.labe.text = str(x)
print "DONE"
class PongApp(App):
def build(self):
game = PongGame()
game.settext(1)
Clock.schedule_once(partial(game.settext, 5), 3)
Clock.schedule_once(partial(game.settext, 87), 6)
Clock.schedule_once(partial(game.settext, 5), 9)
return game
if __name__ == '__main__':
PongApp().run()
There are a few important notes. One is that I used functools.partial() because you need to pass a function to Clock.schedule_once, and partial creates a function from an existing function (here game.settext) and some default arguments to use (here the numbers for the label). It was also important to add *args to PongGame.settext because the clock automatically passes some extra arguments that we don't care about.
If the meaning of that is not clear to you, experiment with these parameters to see what happens.

Categories