Kivy - Access instance created in other Screen - python

I created some instances of a class (Player class) in my first Screen, and would like to access these instances (and their data) in another Screen. The idea is this:
Player class: instance of this class contains players name and their points. (done)
WelcomeWindow(Screen) class: extract name of players via TextInput + create instances of Player class and assign the names. (done)
ThirdRound(Screen) class: Access names and points of the player instances, display the names on a label and change the players points when a +/- Button is pressed. (problem)
I marked in the code below where I tried to access the instances, but got the error:
AttributeError: 'NoneType' object has no attribute 'get_screen'
My question is: How can I access the player points in ThirdRound(Screen) class in order to display their points on a label and to change the points when +/- Button is pressed?
(I searched for similar cases but was not able to apply them to my case.)
.py file:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty, NumericProperty
class Player:
def __init__(self, name):
self.name = name
self.points = 0
def reset_points(self):
self.points = 0
def add_point(self, *args):
self.points += 1
def subtract_point(self, *args):
self.points -= 1
class WelcomeWindow(Screen):
# Introduce names of the 4 players
def __init__(self, **kwargs):
super(WelcomeWindow, self).__init__(**kwargs)
self.name = "welcomewindow"
# Create global layout of HOME screen
global_layout = GridLayout(rows=3)
self.add_widget(global_layout)
# Some code with labels and buttons
# Create button to go to next screen
go_further_button = Button(text="Go to first round")
go_further_button.bind(on_release=self.go_further)
global_layout.add_widget(go_further_button)
def go_further(self, *args):
#Give names to players
self.player1 = Player("name1") # <--- 4 player instances are created here
self.player2 = Player("name2")
self.player3 = Player("name3")
self.player4 = Player("name4")
self.manager.current = "thirdround"
self.manager.transition.direction = "left"
class ThirdRound(Screen):
def __init__(self, **kwargs):
super(ThirdRound, self).__init__(**kwargs)
self.name = "thirdround"
welcome_window = self.manager.get_screen('welcomewindow') # <--- Trying to access player instances here but failed
self.player1_points = welcome_window.player1.points
def change_label(self, add_sub, *args): # <--- method which will change the points of the player instance
label = self.ids['testlabel']
if add_sub == 'add':
label.text = 'should add one point'
elif add_sub == 'sub':
label.text = 'should subtract one point'
kv = Builder.load_file("Kingen.kv")
WindowManager = ScreenManager()
WindowManager.add_widget(WelcomeWindow())
WindowManager.add_widget(ThirdRound())
class KingenApp(App):
def build(self):
return WindowManager
if __name__ == "__main__":
KingenApp().run()
.kv file:
<ThirdRound>:
GridLayout:
rows: 3
Label:
id: testlabel
text: 'shows number of points here'
Button:
text: "+"
on_release: root.change_label('add')
Button:
text: "-"
on_release: root.change_label('sub')

The __init__() method of ThirdRound is called at the line:
WindowManager.add_widget(ThirdRound())
Specifically, the ThirdRound() is executed before the WindowManager.add_widget, so the ThirdRound instance does not have its manager yet set when its __init__() is executed, so it is still None. Try moving the code that accesses the manager till it is needed. Something like this:
class ThirdRound(Screen):
def __init__(self, **kwargs):
super(ThirdRound, self).__init__(**kwargs)
self.name = "thirdround"
def change_label(self, add_sub, *args): # <--- method which will change the points of the player instance
welcome_window = self.manager.get_screen('welcomewindow')
label = self.ids['testlabel']
if add_sub == 'add':
welcome_window.player1.points += 1
label.text = str(welcome_window.player1.points)
elif add_sub == 'sub':
welcome_window.player1.points -= 1
label.text = str(welcome_window.player1.points)

Related

It is necessary to transfer to the SecondScreen class, the text that the button_press function returns from the ScreenMain class

Please help, I'm trying to make an application for a child to learn the alphabet, I'm just learning programming and working with classes.
I need to pass to the SecondScreen class the text that the button_press function returns from the ScreenMain class.
`
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from itertools import chain
from kivy.uix.screenmanager import ScreenManager, Screen
class ScreenMain(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.alphabet = [chr(i) for i in chain(range(1040, 1046), range(1025, 1026), range(1046, 1069), range(32, 33),
range(1069, 1072))]
gr = GridLayout(cols=5, padding=[35], spacing=3)
for i in self.alphabet:
gr.add_widget(Button(text=i, on_press=self.button_press))
self.add_widget(gr)
def button_press(self, instance):
self.manager.transition.direction = 'left'
self.manager.current = 'second_screen'
print(instance.text) # I output to the console, everything is ok
return instance.text
class SecondScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
get = ScreenMain.button_press # I'm trying to pass a letter from a function to a class button_press
layout = GridLayout(cols=5, rows=5, padding=[35], spacing=10)
layout.add_widget(Button(
text=str(get))) # Trying to create a button on the second page with the text of the letter pressed on the first screen
self.add_widget(layout)
def _on_press_button_new_layout(self, *args):
self.manager.transition.direction = 'right'
self.manager.current = 'main_screen'
class MyApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(ScreenMain(name='main_screen'))
sm.add_widget(SecondScreen(name='second_screen'))
return sm
if __name__ == '__main__':
MyApp().run()
`

Kivy: AttributeError: 'CreateButton' object has no attribute '_disabled_count'

I am trying to create a dashboard of buttons on the Raspberry Pi touchscreen as a home hobby and I want to create some buttons that you press and release, some that latch on and off and some that have multiple states.
I want to change the state of a button when it is pressed. So I can detect that the button is pressed via the CreateButton class, but I want to pass the button state information in at init. When I do so, I get
AttributeError: 'CreateButton' object has no attribute '_disabled_count'
If I remove the init in CreateButton the code runs, which I don't understand. Can you suggest where I'm going wrong?
Many thanks
Main.py
import pickle
from kivy.app import App
from kivy.uix.image import AsyncImage
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from typing import List
# Define the data structure of a button state
class StateDefinition(object):
def __init__(self, Label, Image, Colour, Action):
self.Label = Label
self.Image = Image
self.Colour = Colour
self.Action = Action
# Define the data structure of a button
class ButtonDefinition(object):
def __init__(self, buttonname: str, currentstate: int, buttonstates: List[StateDefinition]):
self.currentstate = currentstate
self.buttonname = buttonname
self.buttonstates = buttonstates
# Define the data structure of a dashboard - a collection of related buttons
class DashboardDefinition(object):
def __init__(self, dashboardname: str, dashboardbuttons: List[ButtonDefinition]):
self.dashboardname = dashboardname
self.dashboardbuttons = dashboardbuttons
# This class creates the kivy button and binds it to the action
class CreateButton(Button):
def __init__(self, **kwargs):
print("Got Here")
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.button == "right":
print(self.id, "right mouse clicked")
elif touch.button == "left":
print(self.id, "left mouse clicked")
else:
print(self.id)
return True
return super(CreateButton, self).on_touch_down(touch)
## This is the class that builds the Carousel of buttons
class BuildLayout(GridLayout):
def __init__(self, **kwargs):
super(BuildLayout, self).__init__(**kwargs)
self.build_dashboard()
def build_dashboard(self):
# Define all the test data
state1 = StateDefinition(Label = 'State 1', Image = 'Image1.png', Colour = '#FF0000', Action = 'A')
state2 = StateDefinition(Label = 'State 2', Image = 'Image2.png', Colour = '#00FF00', Action = 'B')
state3 = StateDefinition(Label = 'State 3', Image = 'Image3.png', Colour = '#0000FF', Action = 'C')
button1 = ButtonDefinition(buttonname = "Button 1", currentstate = 0, buttonstates = [state1])
button2 = ButtonDefinition(buttonname = 'Button 2', currentstate = 0, buttonstates = [state2, state1])
button3 = ButtonDefinition(buttonname = 'Button 3', currentstate = 0, buttonstates = [state3, state2, state1])
dashboard1 = DashboardDefinition(dashboardname = "Test Dashboard", dashboardbuttons = [button1, button2, button3])
for buttonID in range(0, len(dashboard1.dashboardbuttons)):
buttonwidget = CreateButton(id = str(buttonID))
buttonwidget.text = dashboard1.dashboardbuttons[buttonID].buttonname + "\nButton State: " + \
str(dashboard1.dashboardbuttons[buttonID].currentstate)
self.add_widget(buttonwidget)
# This is tha main class that sets up the app
class LayoutApp(App):
def build(self):
return BuildLayout()
if __name__ == '__main__':
LayoutApp().run()
Layout.kv
#:kivy 1.11.0
<CreateButton>:
font_size: 18
on_touch_down: self.on_touch_down
<BuildLayout>:
rows: 3
cols: 3
row_force_default: True
row_default_height: 150
col_force_default: True
col_default_width: 150
padding: [10]
spacing: [10]
When you override the __init__ method for Kivy elements, it won't do the inherited __init__ method unless you manually tell it to. If you don't do that, it won't get set up properly with all the properties Kivy expects (like _disabled_count).
I expect this will work if you just add super().__init__(**kwargs) (or super(<classname>, self).__init__(**kwargs) for Python 2 compatibility) as the first line of your new __init__ methods, like you did in BuildLayout. It's easy to forget!

Kivy: is it possible to trigger events with class level (not instance) Properties?

Consider following code. I would like to update multiple widget instances when prefix changes. As it is the same for all the instances it seems efficient to store/update it only once on class level (so that when instance does not have its own self.prefix, it will automatically refer to class level prefix attribute)
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import StringProperty
import random
kivy_lang = '''
<MainWidget>:
Button:
id: my_button
text: 'increase indice'
<MyLabel>:
on_prefix: self.text = self.prefix +':'+ self.indice
on_indice: self.text = self.prefix +':'+ self.indice
'''
class MyLabel(Label):
prefix = StringProperty('1')
indice = StringProperty('0')
pass
class MainWidget(BoxLayout):
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.my_label1 = MyLabel()
self.my_label2 = MyLabel()
self.add_widget(self.my_label1)
self.add_widget(self.my_label2)
self.ids.my_button.bind(on_press=self.my_method)
def my_method(self,*args,**kwargs):
MyLabel.prefix = str(random.randint(0,9))
self.my_label1.indice = str(int(self.my_label1.indice) + 1)
# my_label2 would also be updated if its 'indice' got changed as below
# self.my_label2.indice = str(int(self.my_label2.indice) + 2)
class MyApp(App):
def build(self):
Builder.load_string(kivy_lang)
return MainWidget()
if __name__ == '__main__':
MyApp().run()
As from the python side this seems right, from Kivy side it looks like kivy has problem recognising when prefix got changed (my_label1 only gets updated because indice was also updated and on_indice is triggered).
Is there a way to get 'class level Property' prefix change to trigger on_prefix ?
I don't think this is possible directly, but you could mimic that functionality with AliasProperty and another property stored, say, on App. As long as the instance of MyLabel hasn't changed prefix, the value set for App is used (and automatically updated). Once prefix is set on an instance, _my_prefix is not None, and will be used to retrieve the value for prefix.
Change the <MyLabel> rule to
<MyLabel>:
_prefix: app.prefix
text: self.prefix +':'+ self.indice
And change the python code to
class MyLabel(Label):
indice = StringProperty('0')
_prefix = StringProperty('')
_my_prefix = StringProperty(None)
def get_prefix(self):
if self._my_prefix is None:
return self._prefix
else:
return self._my_prefix
def set_prefix(self, value):
self._my_prefix = value
prefix = AliasProperty(get_prefix, set_prefix, bind=('_prefix', '_my_prefix'))
[...]
def my_method(self,*args,**kwargs):
App.get_running_app().prefix = str(random.randint(0,9))
self.my_label1.indice = str(int(self.my_label1.indice) + 1)
if int(self.my_label1.indice) == 2:
self.my_label2.prefix = 'changed'
[...]
class MyApp(App):
prefix = StringProperty('1')

Kivy binding to on_property doesn't seem to work

Essentially, I have a grid with squares, and I keep track of which squares are occupied with a BooleanProperty on each square. Here's a simplified version of all the places in my code I declare the "occupied" property:
class Board(GridLayout):
def __init__(self):
super().__init__()
self.cols = 4
self.grid = []
self.create_slots()
def create_slots(self):
for i in range(10):
self.grid.append([])
for j in range(4):
temp = Square(i,j, "sideboard")
self.grid[i].append(temp)
self.add_widget(temp)
temp.bind(on_occupied = self.do_a_thing)
def do_a_thing(self):
for square in self.children:
#do a thing
class Square(Button):
def __init__(self, row, col, type):
self.row = row
self.col = col
self.id = str(self.row) + "," + str(self.col)
self.type = type
self.occupied = BooleanProperty(False)
super().__init__()
My goal is to bind the "do_a_thing" method to be called each time the value of the square's "occupied" property changes. Because the Square class is used elsewhere in my app, I don't want to set the callback for on_occupied in the kivy language, and I was hoping to avoid creating a Square sub-class just to change the one binding.
When I run my code, it throws no errors, and I've verified that the "occupied" property does actually change. But the "do_a_thing" method never gets fired. Can anyone tell me what I'm doing wrong?
Note that for a property my_property, the change event is called my_property as well. The callback receives two arguments: instance that fired the event, and new value of the property, as shown in the docs. Also, if the class has a method called on_propertyname, this will be called as well.
Here is a self-contained example that works for me:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.properties import BooleanProperty
class Board(GridLayout):
def __init__(self, **kwargs):
super(Board, self).__init__(**kwargs)
for i in range(10):
self.add_widget(Square())
for square in self.children:
print square
square.bind(occupied=self.do_a_thing)
def do_a_thing(self, *args):
print "hello from {}, new state: {}".format(*args)
for square in self.children:
pass
#do a thing
class Square(Button):
occupied = BooleanProperty(False)
def on_occupied(self, *args):
print "Callback defined in class: from {} state {}".format(*args)
class mApp(App):
def build(self):
return Builder.load_string("""
Board:
cols: 4
rows: 3
<Square>:
on_press: self.occupied = ~self.occupied
""")
mApp().run()

Inheritance in Variable Value - Python

Well, I'm having trouble in a specific part of my code. It uses kivy, however I'm pretty sure there is a python solution. Here is the thing: I'll have a button, when pressed will take me to another screen, when it's pressed, calls method vai, that changes, or should change the string variable value that was created in init method. Afterwards, when another screen shows up, its button receives that CHANGED variable at text parameter. But the real issue is that by the time second screen appears, the button text does not change, remaining the value I set up on init once, not the changed value on vai method.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
class Principal(App):
def build(self):
return SM()
class SM(ScreenManager):
def __init__(self):
super(SM, self).__init__()
self.add_widget(Um())
self.current = 'TelaUm'
class Um(Screen):
def __init__(self):
super(Um, self).__init__()
self.name = 'TelaUm'
self.add_widget(UmC())
class UmC(FloatLayout):
def __init__(self):
super(UmC, self).__init__()
self.btnSelecionado = 'qualquer_merda'
self.btn = Button(id = 'Bosta!', text = 'Bosta!', pos_hint = { 'center_x' : .5, 'center_y' : .5 }, size_hint = (None, None))
self.btn.bind(on_press = self.vai)
self.add_widget(self.btn)
def vai(self, instance):
self.parent.parent.add_widget(Dois())
self.parent.parent.current = 'TelaDois'
self.btnSelecionado = instance.id
class Dois(Screen):
def __init__(self):
super(Dois, self).__init__()
self.name = 'TelaDois'
self.add_widget(DoisC())
class DoisC(UmC, FloatLayout):
def __init__(self):
super(DoisC, self).__init__()
self.btn2 = Button(text = self.btnSelecionado, pos_hint = { 'center_x' : .5, 'center_y' : .5 }, size_hint = (None, None) )
self.add_widget(self.btn2)
Principal().run()
I don't completely understand what you are trying to do. But it looks to me like the second screen is a new instance of UmC and therefore has its own value of btnSelecionado. So inevitably this new instance has the value from init since it's only the old instance that has been changed.
def vai(self, instance):
self.parent.parent.add_widget(Dois())
self.parent.parent.current = 'TelaDois'
self.btnSelecionado = instance.id
Line 2 creates a new instance and line 4 sets the value in the old instance.

Categories