I'm trying to figure out how to bind a button that's been laid out using Kivy language to a function. I've seen plenty of answers when laying out buttons in Python language. But what about once everything is in place and you're now referencing via a custom class that inherits from Button?
On press, the code below throws the error TypeError: show() takes 1 positional argument but 2 were given and crashes the program.
class TimerButton(ButtonBehavior, Image):
timer_container = ObjectProperty(None)
client_scoreboard = ObjectProperty(None)
def __init__(self, **kwargs):
super(TimerButton, self).__init__(**kwargs)
self.bind(on_press=self.show)
self.bind(on_release=self.changeImage)
def show(self):
print('hi there')
self.source = 'assets/mainViewTimerButtonPressed.png'
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.right - self.timer_container.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.unbind(on_press=self.show)
self.bind(on_press=self.hide)
def changeImage(self):
self.source = 'assets/mainViewTimerButton.png'
def hide(self):
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.unbind(on_press=self.hide)
self.bind(on_press=self.show)
The kivy code that calls the function you set in .bind() is passing an argument which your function isn't prepared for. It's been a while since I last used kivy, so I can't be sure, but I think the event details are passed to the function.
As such, your definitions for event handlers should look like this:
def show(self, event):
...
def hide(self, event):
...
If you're curious, you could print(event) within those functions, to see what's being sent in.
The answer is to include the class name, in this case TimerButton, in the function definitions. This is a concept I don't completely understand since the function is defined within the scope of the TimerButton class. But hey, it works.
class TimerButton(ButtonBehavior, Image):
timer_container = ObjectProperty(None)
client_scoreboard = ObjectProperty(None)
def __init__(self, **kwargs):
super(TimerButton, self).__init__(**kwargs)
self.bind(on_press=self.show)
self.bind(on_release=self.changeImage)
def show(self):
print('hi there')
self.source = 'assets/mainViewTimerButtonPressed.png'
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.right - self.timer_container.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.bind(on_press=self.hide)
def changeImage(self):
self.source = 'assets/mainViewTimerButton.png'
def hide(self):
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.bind(on_press=self.show)
Related
#This class is to be used with other classes of widgets, not with the class of kivy.app.
class TempatureFloatLayout(FloatLayout):
tempature = NumericProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.update, 1)
self.btn = Button()
self.btn.bind(on_press=self.do_something)
self.add_widget(self.btn)
def do_something(self, self_button):
pass
def update(self, dt):
self.btn.size = self.size # I can only resize and reposition the button here.
self.btn.pos = self.pos # pos and size are assigned with the pos and size
# values of the widget used
... the other codes
class TempatureFloatLayout(FloatLayout) can be used successfully in other classes. The code works correctly as it is. But, When every update, the position and size of the button is to be resized and repositioned. This doesn't feel right to me. How to bind a widget that is used and a button that is used in a different class. Where did I do wrong or is this usage correct?
In Kivy, if you have a ScreenManager with multiple screens already added, you can not change screens with the switch_to function, as that also tries to first add the Screen to the Screenmanager, before switching to it.
Instead, you switch screens simply by setting the current property, like this:
screenmanager.current = "new_screen_name"
The problem is, I wish my SplashScreen to automatically transition to MainScreen after a short delay, using Clock.schedule_once(). But that can only take a function as a parameter, so I I have to write a another function just to change screen, like this:
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MainScreenManager, self).__init__(**kwargs)
Clock.schedule_once(switch_to_main_screen, 3)
def switch_to_main_screen(self, *args):
self.current = "main_screen
I'm just wondering if there is a more efficient way to do this? e.g can I somhow directly set the "self.current" property in Clock.schedule_once? Or, generally speaking, is there a better way to do this simple task? (i.e have a parameter be set a few seconds in the future)
You can use the setattr() python built-in. Try replacing:
Clock.schedule_once(switch_to_main_screen, 3)
with:
Clock.schedule_once(lambda dt: setattr(self, 'current', 'main_screen'), 3)
See the documentation.
Consider this, its my ready sheet in kivy:
import #what u need
Builder.load_file('the.kv')
class fscreen(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class secscreen(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
class thscreen(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
class theapp(App):
def build(self):
self.screenm = ScreenManager()
self.fscreen = fscreen() ## as default this is my
screen = Screen(name = "first screen") # my first screen
screen.add_widget(self.fscreen) # if i want second screen
self.screenm.add_widget(screen) # as first i move this below
## secscreen its waay
self.secscreen = secscreen() ### efficient
screen = Screen(name = "secondscreen")
screen.add_widget(self.secscreen)
self.screenm.add_widget(screen)
self.thscreen = thscreen()
screen = Screen(name = "thirdscreen")
screen.add_widget(self.thscreen)
self.screenm.add_widget(screen)
return self.screenm
if __name__ == "__main__":
theapp = theapp()
theapp.run()
This way the fscreen is by default the first screen after the presplash
I am trying to use kivy.clock object more specifically Clock.schedule_interval to take temperature readings every couple seconds.
I create a method in my main screen class (MyTerrLayout(Screen)) called clockAction. now when I type in Clock.schedule_interval(clockAction, 2) I get:
NameError: clockAction is not defined
So I tired to do self.clockAction but that didn't work either. I tried various methods to get it going like moving Clock.schedule_interval(clockAction, 2) to its own class but I get other errors. Do i have to create an instance of the method clockAction? Or since Clock.schedule_interval(clockAction, 2) might be a class attribute it needs to be called a different way.
Or is it because Clock.schedule_interval(clockAction, 2) is a class attribute that I need to call clockAction a different way? I can't seem to make the connection.
If someone could point me in the right direction that would be awesome. Also does anyone know where I can practice more complex examples of OOP and class manipulation? Or does someone have some old homework regarding class manipulation and OOP concepts?
Here is my code
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window
from kivy.properties import ObjectProperty
from random import randint
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock
import threading
Window.size = (600,250)
tep = 75
hum = 85
# Window Manager Class
class WindowManager(ScreenManager):
pass
# Main home menu Class
class MyTerrLayout(Screen):
internal_read = ObjectProperty(None)
external_read = ObjectProperty(None)
target_temp = ObjectProperty(None)
target_hum = ObjectProperty(None)
Clock.schedule_interval(clockAction, 2) #Clock Schedule
def set_temp(self):
global tep
self.ids._Target_Temp.text = str(tep) + " F"
def set_hum(self):
global hum
self.ids._Target_Hum.text = str(hum) + "%"
def clockAction(self, dt=0): ####method I am trying to run
self.ids.external_reading.text = str(randint(1,10))
print('test')
# Temprature Screen Class / Window
class TempWindow(Screen):
def sub_temp(self):
global tep
tep = tep - 1
self.temp_menu.text = str(tep)
def add_temp(self):
global tep
tep = tep + 1
self.temp_menu.text = str(tep)
def set_temp(self):
terrlayout = self.manager.get_screen('main')
terrlayout.set_temp()
class HumWindow(Screen):
def sub_hum(self):
global hum
hum = hum - 1
self.ids.set_hum_menu.text = str(hum)
def add_hum(self):
global hum
hum = hum + 1
self.ids.set_hum_menu.text = str(hum)
def set_temp(self):
terrlayout = self.manager.get_screen('main')
terrlayout.set_hum()
class AC(Screen):
def build(self):
Clock.schedule_interval(self.Callback_Clock, 2)
def Callback_Clock(self, dt):
terrlayout = self.manager.get_screen('main')
terrlayout.clockAction()
# Builder Section
kv = Builder.load_file('terrlayoutV1.kv')
class TerrLayout(App):
def build(self):
return kv
if __name__=="__main__":
TerrLayout().run()
The problem is here:
class MyTerrLayout(Screen):
[...]
Clock.schedule_interval(clockAction, 2)
[...]
That code is in the class part of MyTerrLayout. In other languages, we call that static. clockAction has self as a parameter, which means it is not static.
In a static context, you can't access members of an object, because there is no object to refer to.
IMHO this should be done when the object is created:
def __init__(self, **kw):
super().__init__(**kw)
Clock.schedule_interval(self.clockAction, 2) # Clock Schedule
Note that the __init__ method has self and you can refer to self.clockAction.
I have a dynamic Screen which is generated based on a button you clicked on another screen. Issue is dat every time I enter the Screen, the buttons are regenerated and added to the existing buttons.
The reason is that I use the on_enter method, but I don't know how I can use on_kv_post for example, as these events happen on starting the app.
How can I initialise the screen every time I return to this screen?
class ClientEnvsGrid(Screen):
envProp = StringProperty('')
def __init__(self, **kwargs):
super(ClientEnvsGrid, self).__init__(**kwargs)
def on_enter(self, *args):
clientProp = self.manager.get_screen('clientlist').clientProp
try:
client_filepath = os.path.join('clients', clientProp, "environments.json")
client_file = open(client_filepath)
clientdata = json.loads(client_file.read())
print(clientdata)
self.ids.clientlabel.text = clientdata["clientname"]
for envs in clientdata["environments"]:
print(envs["name"])
envbutton = Button(text=envs["name"])
envbutton.bind(on_press=lambda *args: self.pressed('envbtn', *args))
self.ids.environments.add_widget(envbutton)
except:
print("No client data found")
self.manager.current = 'clientlist'
def pressed(self, instance, *args):
self.envProp = args[0].text
I've managed to fix it to include clear_widgets in the environments GridLayout in the on_leave event.
def on_leave(self, *args):
self.ids.environments.clear_widgets()
Error:
TypeError: changetxt() takes no arguments but 2 given, or global name play_btn not defined.
I tried adding instance, self as well into the def args but still have the same error.
import kivy
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.lang import Builder
class launchScreenMenu(FloatLayout):
def __init__(self, **kwargs):
super(launchScreenMenu, self).__init__(**kwargs)
menuanchor = AnchorLayout(anchor_x='left',anchor_y='bottom')
menu = StackLayout(orientation='bt-lr',size_hint=(0.5,1))
about_btn = Button(text='About',size_hint=(0.3,0.1))
help_btn = Button(text='Settings',size_hint=(0.3,0.1))
settings_btn = Button(text='Help',size_hint=(0.3,0.1))
menu.add_widget(about_btn)
menu.add_widget(help_btn)
menu.add_widget(settings_btn)
menuanchor.add_widget(menu)
return self.add_widget(menuanchor)
class launchScreenBtn(AnchorLayout):
def __init__(self, **kwargs):
super(launchScreenBtn, self).__init__(**kwargs)
play_btn = Button(text="Play")
self.anchor_x = 'center'
self.anchor_y = 'center'
self.size_hint = 0.2,0.2
self.add_widget(play_btn)
play_btn.bind(on_press=self.changetxt)
def changetxt():
play_btn.text = 'Game Over'
class GameApp(App):
def build(self):
root = AnchorLayout()
root.add_widget(launchScreenMenu())
root.add_widget(launchScreenBtn())
return root
if __name__=='__main__':
GameApp().run()
All instance methods should always has self as their first argument:
def changetxt(self, *args):
self.play_btn = 'Game Over'
*args is just to be secure, in case it wasn't you who pass the argument.
Also, change all play_btn inside the class to self.play_btn:
self.play_btn = Button(text="Play")
Well, hope this helps!
The first argument of instance methods (think any function in a class, but there are exceptions) is a link to the instance itself. By convention this is called self. Reference
I don't know with 100% certainty what your second argument is. It may be play_btn since that's what's calling the function. That would be convenient since you're trying to reference that anyway, and it otherwise would be undefined.
How you want your def to read is like this:
def changetxt(self, play_btn):