Inheritance in Variable Value - Python - 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.

Related

Kivy - Access instance created in other Screen

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)

Kivy KV File Custom Property

I cannot for the life of me figure out how to pass a custom property on a custom widget via the KV file. My application is a simple grid that contains a Button() and TestWidget(). TestWidget() has a StringProperty() test_property that doesn't seem to get the data from the KV file as seen by the print statement on init. Here's some quick straight forward code as an example.
Thanks.
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.properties import StringProperty
Builder.load_string("""
<TestWidget>:
<TestGrid>:
Button:
TestWidget:
test_property: 'Test Property'
""")
class TestWidget(Widget):
test_property = StringProperty()
def __init__(self, **kwargs):
super(TestWidget, self).__init__(**kwargs)
print('Test OUTPUT:', self.test_property)
class TestGrid(GridLayout):
pass
class MyApp(App):
def build(self):
return TestGrid()
MyApp().run()
I think I figured it out. Kivy doesn't pass anything to the objects. I learned this at https://kivy.org/docs/api-kivy.properties.html.
I use the on_ to do what needs to be done. There is a big difference between Kivy Objects and Python Objects.
Here's an example of a custom BoxLayout;
class KivyInput(BoxLayout):
text_test = StringProperty()
def __init__(self, **kwargs):
super(KivyInput, self).__init__(**kwargs)
self.orientation = 'horizontal'
self.label = Label()
self.text_input = TextInput(multiline=False)
self.add_widget(self.label)
self.add_widget(self.text_input)
def on_text_test(self, instance, value):
self.label.text = value
def remove(self):
self.clear_widgets()
Try printing it on the upcoming frame, instead of in the initiation of the object.
After the object is created, you can access the properties.
You do that with Clock.
Like this:
from kivy.clock import Clock
class TestWidget(Widget):
test_property = StringProperty()
def __init__(self, **kwargs):
super(TestWidget, self).__init__(**kwargs)
Clock.schedule_once(self.after_init) # run method on next frame
def after_init(self,dt):
print('Test OUTPUT:', self.test_property)

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

Change screens with python logic (Kivy Screen manager)

I can't find the syntax for setting on_press in python code to change the screen anywhere. I keep getting errors for anything like Button(text = 'hi', on_press = self.current = 'start_menu. Here's the code and it works as is.
class LoadMenu(Screen):
def __init__(self, **kwargs):
super(LoadMenu, self).__init__(**kwargs)
Clock.schedule_once(self.update)
def update(self, dt):
L = [x for x in range(len(os.listdir('saves')))]
for x in L:
x = self.add_widget(Button(text = os.listdir('saves')[x]))
I haven't positioned the buttons so they just are on top of each other but I can fix that later. What I need to happen is for each button to change to the play screen on press so that will be the same for each button but I also need each one to load the Shelve file they a referencing.(I know I'll need another function for that) Can I have an on_press trigger two events at once, and again how do I set it in the python code?
Consider the following programme:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.properties import StringProperty
dirlist = ['aaa', 'bbb', 'ccc', 'ddd']
class MyButton(Button):
prop = StringProperty('')
def on_press(self):
print "Class-defined on_press handler (I'm {})".format(self.text)
def other_on_press_handler(sender):
print "other_on_press_handler, from {}".format(sender.text)
def some_func(text):
print "yeah: " + text
class LoadMenu(Screen):
def __init__(self, **kwargs):
super(LoadMenu, self).__init__(**kwargs)
Clock.schedule_once(self.update)
def on_press_handler(self, sender):
print "on_press_handler, from {}".format(sender.text)
self.parent.current = 'sc2'
def yet_another_on_press_handler(self, sender):
print "yet_another_on_press_handler, from {}".format(sender.text)
self.parent.current = 'sc2'
def update(self, dt):
for x in range(len(dirlist)):
my_b = Button(text = dirlist[x], on_press=self.on_press_handler)
self.parent.ids.button_container.add_widget(my_b)
if x > 1:
my_b.bind(on_press=other_on_press_handler)
if x == 3:
my_b.bind(on_press=lambda sender: some_func("Whoa, lambda was here ({})".format(sender.text)))
for x in range(len(dirlist)):
my_b = MyButton(text = 'my '+ dirlist[x], prop="{} {}".format(dirlist[x], x))
self.parent.ids.button_container.add_widget(my_b)
my_b.bind(on_press=self.yet_another_on_press_handler)
root = Builder.load_string("""
ScreenManager:
LoadMenu:
name: 'sc1'
GridLayout:
cols: 4
id: button_container
Screen:
name: 'sc2'
BoxLayout:
Button:
text: "Go back"
on_press: root.current = 'sc1'
""")
class MyApp(App):
def build(self):
return root
if __name__ == '__main__':
a = MyApp()
a.run()
Let's start by looking at the update method in LoadMenu: In the first loop, a bunch of buttons is generated, and each receives an on_press callback at creation. The last two buttons in the loop get bound to another callback, and the last example shows how to use a lambda expression to generate a callback.
In the second for loop, we instantiate object of class MyButton, a child of Button. Note that we also define an on_press handler in the class definition; this gets called in addition to other functions we may have bound.
But really, this is actually all pretty nicely explained in the kivy Events and Properties docs.

Categories