Cant wrap my head around Classes and Kivy - python

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.

Related

distinguish classes that inherited App with get_running_app()

I got two classes that have two methods that I want to be able to access from different classes, so I inherited from App in both classes to use get_running_app with kivy properties (hope you understand what I'm saying). Now I have a problem because when I try calling App.get_running_app().method(), it only looks for the method in the first class, but the method is in the second class. I know that inheriting from App in two classes isn't really good but that was at the time the easiest way because I didn't have to call get_running_app for both classes. I had like the main class (where the def build is) and one just for main screen and I needed to call a method in main screen class. Now I have that method and the method in the main class.
So my question is, is there any way of distinguishing two classes with get_running_app()? Or could you give me some tips on how to solve this problem in different way if you have some idea?
Here is the code like you asked, just forget what this code does, I simplified it like this just to show the core problem and the error it doesn't matter what does.
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.properties import NumericProperty
class GameWidget(Screen, App):
num = NumericProperty(0)
change_value = ObjectProperty(None)
def __init__(self, **kwargs):
super(Screen, self).__init__(**kwargs)
self.num = 0
self.button = Button(text="go back", size_hint=(0.2, 0.2), pos_hint={'x': 0.4, 'y':0.4},
on_press=lambda x: self.go_main_screen())
self.add_widget(self.button)
self.label = Label(text=str(self.num), size_hint=(0.2, 0.2), pos_hint={'x':0.2, 'y':0.2})
self.add_widget(self.label)
def change_value(self):
self.num += 1
self.label.text = str(self.num)
def go_main_screen(self):
self.manager.transition.direction = 'right'
self.manager.current = 'main'
class Main_screen(Screen):
def __init__(self, **kwargs):
super(Main_screen, self).__init__(**kwargs)
self.button = Button(text='click me', size_hint=(0.2, 0.2), pos_hint={'x': 0.4, 'y': 0.4},
on_release=lambda x: self.call_methods())
self.button2 = Button(text='go to next screen', size_hint=(0.2, 0.2), pos_hint={'x':0.4, 'y':0.6},
on_release=lambda x: self.ch_screen())
self.add_widget(self.button)
self.add_widget(self.button2)
def ch_screen(self):
self.manager.transition.direction = 'left'
self.manager.current = 'game'
def call_methods(self):
App.get_running_app().change_value()
App.get_running_app().print_some_stuff()
class Manager(ScreenManager):
pass
class MyApp(App):
print_some_stuff = ObjectProperty(None)
def build(self):
sm = Manager()
sm.add_widget(Main_screen(name='main'))
sm.add_widget(GameWidget(name='game'))
return sm
def print_some_stuff(self):
print('you clicked')
MyApp().run()
The App.get_running_app() method will always return the same object, which should be the currently running App. Since you can only have one running App, that should always be the MyApp instance in your code. However, I believe you have uncovered a bug in the App code, because App.get_running_app() actually returns the last created App, not the currently running App. So, in your code, it will always return the GameWidget instance. That bug will not affect a correctly structured code.
The following problems/solutions appear in your code:
You should not have more than one App class in your App. Change:
class GameWidget(Screen, App):
to:
class GameWidget(Screen):
You can access Screens using the get_screen() method of the ScreenManager, which is your App root. So you can chaange:
App.get_running_app().change_value()
to:
App.get_running_app().root.get_screen('game').change_value()

How to transfer a list of element from a screen to another using Kivy

I have some trouble to transfert a list of element, that i get from a file (in the first screen), to the second and third screen.
I have 3 Screen managed by ScreenManager, every screen is in a different file.py and have a file.kv.
This is what my ScreenManager looks like (filename : login.py):
from interface1 import Interface1App
from interface2 import Interface2App
class LoginApp(App):
def build(self):
manager = ScreenManager()
#add view login
manager.add_widget(Login(name='login'))
# add view 'interface1'
app = Interface1App()
app.load_kv()
interfacen1 = app.build()
manager.add_widget(interfacen1)
# add view 'interface2'
app2 = Interface2App()
app2.load_kv()
interfacen2 = app2.build()
manager.add_widget(interfacen2)
# add view 'interface3'
app3 = Interface3App()
app3.load_kv()
interfacen3 = app3.build()
manager.add_widget(interfacen3)
manager.transition = SlideTransition(direction="left")
return manager
In the screen manager login i import all the screens (ex: from interface1 import Interface1App)
In the second file, i put in self.list some data, and use it for that interface (filename : interface1.py):
from interface2 import Interface2App
class AllImage(Screen):
CONTAINER_PNG = os.path.join(AllImage_ROOT, 'images')
IMAGES_NAMES = [c[:-4] for c in os.listdir(CONTAINER_PNG)]
def __init__(self, **kwargs):
Screen.__init__(self, **kwargs)
for im in IMAGES_NAMES:
if IMAGES_NAMES != None :
toggleimage = ToggleWithImage(src=im+'.png')
toggleimage.bind(on_press= lambda a, im=im:self.onpress_addpage(self.listim, im))
self.layout.add_widget(toggleimage)
def change_screen(self, x, list):
self.manager.transition = SlideTransition(direction="left")
self.manager.current = 'interface2'
self.manager.get_screen('interface2')
self.manager.list1 = [1,2,3]
def backtologin(self):
self.manager.transition = SlideTransition(direction="right")
self.manager.current = 'login'
self.manager.get_screen('login')
class Interface1App(App):
'''The kivy App that runs the main root. All we do is build a AllImage
widget into the root.'''
def build(self):
screen = AllImage()
screen.name = 'interface1'
return screen
After that i want to use again the data collected in the 2nd interface (self.list) and use it in the 3rd Interface (filename: interace2.py):
from interface1 import AllImage #this isn't working
class Combinaison(Screen):
screen = ObjectProperty(None)
def __init__(self, **kwargs):
# self._previously_parsed_text = ''
super(Combinaison, self).__init__(**kwargs)
# self.list = list(self.manager.get_screen('interface1').list)
print(self.manager.list1) # The problem is here <===========
def backtointerface1(self):
self.manager.transition = SlideTransition(direction="right")
self.manager.current = 'interface1'
self.manager.get_screen('interface1')
class Interface2App(App):
'''The kivy App that runs the main root. All we do is build a Combinaison
widget into the root.'''
def build(self):
screen = Combinaison()
screen.name = 'interface2'
return screen
With this code i have this error : print(self.manager.list1)
AttributeError: 'NoneType' object has no attribute 'list1'
I think i didn't understand how i should use the self.manager.list1=[1, 2, 3]
I hope that i was clear, sorry for the long post.
Thank you for your time :)
You can use Screen.manager.get_screen(name).list = list(self.list)
A complete example:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
class Screen1(Screen):
list = [1,2,3]
def __init__(self,**kwargs):
super(Screen1,self).__init__(**kwargs)
self.switch_button = Button(text="goto 2")
self.switch_button.bind(on_release=self.switch)
self.add_widget(self.switch_button)
def switch(self,*args):
self.list = [4,5,6]
self.manager.get_screen("screen2").list = list(self.list)
self.manager.current = "screen2"
class Screen2(Screen):
def __init__(self,**kwargs):
super(Screen2,self).__init__(**kwargs)
self.switch_button = Button(text="goto 1")
self.switch_button.bind(on_release=self.switch)
self.add_widget(self.switch_button)
def switch(self,*args):
self.manager.current = "screen1"
def on_enter(self):
print self.list
class MainApp(App):
def build(self):
sc1 = Screen1(name="screen1")
sc2 = Screen2(name="screen2")
self.sm = ScreenManager()
self.sm.add_widget(sc1)
self.sm.add_widget(sc2)
return self.sm
MainApp().run()
You can also use the ScreenManager to store the list or lists.
self.manager.list1 = [1,2,3] # dont call this in __init__
self.manager.list2 = [4,5,6] # call it in on_enter instead
Those lists will be available from all screens after they are added to the manager. The on_enter method on the screen would be suitable for your example.

Binding button to function after adding button with Kivy language

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)

Kivy/Python Countdown App project kivy has no attribute 'built' error

Question: What is a 'has no attribute 'built' error, and what do I need to do to correct this code so that it can take in a datetime object and display the count down? Sorry for the long post.
I've provided the code and a link to the .kv file.
I tried to create a countdown clock that takes a datetime object as a parameter and counts down to that date (using python and kivy). It's basically an slight adaptation of Adam Giermanowski's countdown timer tutorial.
Here's my code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.clock import Clock
import datetime
#datetime object
b= datetime.datetime(2016,9,12,3,5)
class Counter_Timer(BoxLayout):
days = StringProperty()
hours = StringProperty()
minutes = StringProperty()
seconds = StringProperty()
def __init__(self, datetimeOBJ):
self.datetimeOBJ = datetimeOBJ
def update(self, dt):
#the difference in time
delta = self.datetimeOBJ - datetime.datetime.now()
self.days = str(delta.days)
hour_string = str(delta).split(', ')[1]
self.hours = hour_string.split(':')[0]
self.minutes = hour_string.split(':')[1]
self.seconds = hour_string.split(':')[2].split('.')[0]
class Counter(App):
#takes a datetime object as a parameter
def __init__(self, datetimeOBJ):
self.datetimeOBJ = datetimeOBJ
def build(self):
Counter = Counter_Timer(self.datetimeOBJ)
Clock.schedule_interval(Counter.update, 1.0)
return Counter
if __name__=='__main__':
Counter(b).run()
Here's the error on the Counter(b).run() line:
AttributeError: 'Counter' object has no attribute 'built'
You have to call the superclasses constructor when you override __init__, so that all of the things that that constructor does in order to have the other methods of the class work gets done. Your init method should be this:
def __init__(self, datetimeOBJ):
App.init(self)
self.datetimeOBJ = datetimeOBJ

Takes no arguments, 2 given: Kivy Button bind to def

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):

Categories

Resources