Is there a way to check or print a widgets id if you are not sure what id it has? I have a layout I made, its a simple BoxLayout where my Label has its id(its done in the kv file), than I use that Layout multiple times in a GridLayout. Now I have multiple Labels, but they technically have the same id or maybe kivy changes the id if you use it multiple times? Thats what I wanted to check by printining all their ids.
a3.py
from BareBones import Skeleton
menu = ['espresso', 'latte', 'capuccino', 'nescafe', 'Sahlep', 'Mahlep']
keys = []
class Double(BoxLayout):
pass
class NewLayout(GridLayout):
def set_text(self,text):
print(self.root.ids.Skeleton.ids['label_id'].text)
pass
class MainPage(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
iterator = 1
for i in range(len(menu)):
b = Button(size=(100,50))
self.ids[str(iterator)] = b
self.add_widget(b)
keys.append(str(iterator))
iterator = iterator+1
print(keys)
print(len(menu))
for x, y in zip(menu, keys):
self.ids[y].text = x
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton())
kv = Builder.load_file("b3.kv")
class MainApp(App):
def build(self):
return kv
MainApp().run()
b3.kv
NewLayout:
cols: 2
MainPage:
RightSide:
<MainPage>:
orientation: 'vertical'
<Skeleton>
b1: b1
b2: b2
label_id: label_id
Button:
id: b1
text:'+'
on_press: root.on_button_click_plus()
Label:
id: label_id
Button:
id: b2
text:'-'
on_press: root.on_button_click_minus()
BareBones.py
class Skeleton(BoxLayout):
count = 0
my_text = ''
drink_name = ''
def on_button_click_plus(self):
print("Button clicked")
self.count += 1
self.ids.label_id.text = str(self.count)
self.my_text = str(self.count)
return self.my_text
def on_button_click_minus(self):
print("Button clicked")
self.my_text = str(self.count)
self.ids.label_id.text = str(self.count)
if self.count > 0:
self.count -= 1
self.ids.label_id.text = str(self.count)
else:
pass
def trying(self):
pass
class PlusMinus(BoxLayout):
menu = ['espresso']
keys = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
menu = self.menu
self.add_widget(Skeleton())
So this basically looks like this:
What I basically want to do is have a button that collects all the labels texts (the numbers) So that I can pair them with the menu array to know how many of them were added to our basket. I also want to reset the labels however I dont know how I can get the texts that are so far deep in the code.
If you change the drink_name to a StringProperty in the Skeleton class:
class Skeleton(BoxLayout):
count = 0
my_text = ''
drink_name = StringProperty('')
and assign a value to that property when each Skeleton instance is created:
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton(drink_name=menu[i]))
Then you can get a tally of the number of drinks added with a method in the MainPage class:
def tally(self):
rightside = kv.ids.rightside
for skel in rightside.children:
if isinstance(skel, Skeleton):
print(skel.drink_name, skel.ids.label_id.text)
Plus one other required change. In your kv, add an id for the RightSide instance:
NewLayout:
cols: 2
MainPage:
RightSide:
id: rightside
So this is the error Pycharm shows, the red highlighting under kv.
class MainPage(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
iterator = 1
for i in range(len(menu)):
b = Button(size=(100,50))
self.ids[str(iterator)] = b
self.add_widget(b)
keys.append(str(iterator))
iterator = iterator+1
print(keys)
print(len(menu))
for x, y in zip(menu, keys):
self.ids[y].text = x
def tally(self):
rightside = self.kv.ids.rightside
for skel in rightside.children:
if isinstance(skel, Skeleton):
kv = Builder.load_file("b3.kv")
print(skel.drink_name, skel.ids.label_id.text)
class RightSide(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
for i in range(len(menu)):
self.add_widget(Skeleton(drink_name=menu[i]))
kv = Builder.load_file("b3.kv")
class MainApp(App):
def build(self):
return kv
MainApp().run()
So I changed the Stringproperty in the Skeleton file and the code looks like that with the changes you told me to add. However nothing happens, how do I actually use the tally() method?
Related
My app currently has 10 "players" with a name, offensive rating, and defensive rating. I want to give the user the opportunity to change the names of the players before running my game simulation code, but it appears I can only temporarily change the name inside of the current screen. Here's my current python code for when the user inputs the name from the gui:
class SecondWindow(Screen):
def pg1_name_val(self, widget):
pg1.name = widget.text
If I code in a print(pg1.name) right below that, it will print the correct name, but when the user hits "play" to shift to a results screen, the names go back to the default names.
How do I make pg1.name from my SecondWindow(Screen) become the global pg1.name? Is there a simple line of code I can use to return pg1.name as the global variable?
EDIT ----
So I've came a long way with my app this week, but now that I want to have a separate "results" screen, I've realized that I lost all of my data from the previous screen. It looks like all of the changes I make to my "players" are going back to defaults once the screen changes. I'll paste some code below for reference. Basically, my question now is this: Is there a way to keep the objects from one screen and return them back as the global instance of the object. Not just the local instance of the object.
.py -
class Player():
def __init__(self,name,off,deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
class Team():
def init(self,pg, sg, sf, pf, c):
self.pg = pg
self.sg = sg
self.sf = sf
self.pf = pf
self.c = c
self.score = 0
self.results = None
self.to = 0
On the screen where I'm creating my team, I have a button to run the simulation. This button runs the sim correctly, records all of the game results in a string, prints all of the results to the console, attributes, and score correctly. The button also changes the screen to where I want to display a scroll view of the results string, but the results are empty.
I'd really appreciate any help on this, as I'm at a roadblock. Thanks!
It would kinda look the same. Depending on how exactly you wanna keep the 10 different players.
class Player():
def __init__(self, name, off, deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
player = [Player('', 0, 0)] * 10
class SecondWindow(Screen):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
self.player = player
def name_change(self):
self.player[0].name = self.ids.name.text
player[0].name = self.player[0].name
def next_screen(self):
self.manager.current = 'result'
class ResultWindow(Screen):
def __init__(self, **kwargs):
super(ResultWindow, self).__init__(**kwargs)
def on_enter(self, *args):
print(player[0].name)
class Manager(ScreenManager):
pass
class Player(App):
def build(self):
sm = ScreenManager()
sm.add_widget(SecondWindow(name='second'))
sm.add_widget(ResultWindow(name='result'))
return sm
if __name__ == '__main__':
Player().run()
You would have to work with OOP, here I created a list of you player object for all ten players it is an easy way to keep the code short.
You might like to keep them as 10 different objects the good part of a list it is easy to iterate through in a for loop and then get all the data for a stats board on a screen and also easy to expand later if you need 20 players or more.
I didn't change anything in the .kv file.
.kv
<SecondWindow>:
TextInput:
id: name
hint_text: 'Player name'
size: 260, 32
pos: (root.width / 2) - (self.width / 2), 248
multiline: False
on_text_validate: root.name_change()
Button:
id: create_btn
text: 'Next Screen'
size: 260, 32
pos: (root.width / 2) - (self.width / 2), 206
color: utils.get_color_from_hex('#ffffff')
background_color: utils.get_color_from_hex('#016bf8')
on_press: root.next_screen()
<ResultWindow>:
Label:
id: name
The on_text_validate only works with multiline false and when enter is pressed if you don't want to press enter user on_text instead.
--Edit--
I did some research this morning and thought maybe to update this a bit with my findings.
You could instead of doing it the import to all screens do it in the screenmanager and just call it from the screen like this:
pyhon.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class Player:
def __init__(self, name, off, deff):
self.name = name
self.off = off
self.deff = deff
self.stats = 0
class SecondWindow(Screen):
def __init__(self, **kwargs):
super(SecondWindow, self).__init__(**kwargs)
def name_change(self):
self.manager.player.name = self.ids.name.text
def next_screen(self):
self.manager.current = 'result'
class ResultWindow(Screen):
def __init__(self, **kwargs):
super(ResultWindow, self).__init__(**kwargs)
def on_enter(self):
self.ids.name.text = self.manager.player.name
class Manager(ScreenManager):
player = Player('', 0, 0)
def __init__(self, **kwargs):
super(Manager, self).__init__(**kwargs)
class PlayerApp(App):
def build(self):
return Builder.load_file('test.kv')
if __name__ == '__main__':
PlayerApp().run()
the kv file would look something like this:
.kv
#:import utils kivy.utils
Manager:
SecondWindow:
name: 'second'
ResultWindow:
name: 'result'
<SecondWindow>:
TextInput:
id: name
hint_text: 'Player name'
size: 260, 32
size_hint: None, None
pos: (root.width / 2) - (self.width / 2), 248
multiline: False
on_text_validate: root.name_change()
Button:
id: create_btn
text: 'Next Screen'
size_hint: None, None
size: 260, 32
pos: 0, 0
color: utils.get_color_from_hex('#ffffff')
background_color: utils.get_color_from_hex('#016bf8')
on_press: root.next_screen()
<ResultWindow>:
Label:
id: name
hope it helps
I have a Label and a button that I define in the init of my class. In the init I bind my button to a method that should change the label. However the label does not update on button press even tho the variable does.
Why does my variable change but my label text stays the same even tho the text is an ObjectProperty?
class ReviewLayout(BoxLayout):
Price = Price()
textover = ObjectProperty(None)
ordered_products = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.get_order()
l = Label(text = str(self.textover))
self.add_widget(l)
b = Button(text= 'button')
b.bind(on_press=lambda x: self.get_order())
self.add_widget(b)
def get_order(self):
ordered_products = self.ordered_products
ordered_products.append("%s")
print("this shall be present", ordered_products)
self.textover = ordered_products
When you declare your label you set its value to self.textover value but when self.textover value changes it doesn't update the label.
You need to change the value of your label by storing it as a class property and updating it whenever you want.
Just refer to this Update label's text when pressing a button in Kivy for Python
class ReviewLayout(BoxLayout):
Price = Price()
textover = ObjectProperty(None)
ordered_products = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
# declare label variable as a class property (in self)
self.label = Label(text = str(self.textover))
self.add_widget(self.label)
self.button = Button(text= 'button')
self.add_widget(self.button)
self.button.bind(on_press=lambda x: self.get_order())
def get_order(self):
ordered_products = self.ordered_products
ordered_products.append("%s")
print("this shall be present", ordered_products)
self.textover = ordered_products
# change class variable text property to be the new textover content
self.label.text = str(self.textover)
I think an easier solution is to use kv to allow it to do the updates for you automatically, like this:
Builder.load_string('''
<ReviewLayout>:
Label:
text: str(root.textover)
Button:
text: 'button'
on_press: root.get_order()
''')
class ReviewLayout(BoxLayout):
Price = Price()
textover = ListProperty() # note change to ListProperty
def get_order(self):
self.textover.append("%s")
print("this shall be present", self.textover)
How do I increase the width of the dropdown list within a spinner? My button as you can see in the photo is small, and the values in my drop-down list do not appear completely. I did a little research on the internet and I could see that some people say that I need to create a class spinner and add these features. But I don't know how to do this. Could someone show me a code example of how I do this?
main.kv (simplified code)
...
Spinner:
id: spinnerrpi
size_hint: None, None
width: '30sp'
height: '30sp'
border: 0,0,0,0
background_normal: 'seta1.png'
background_down: 'seta2.png'
values: "Branco Neve","Banco Gelo","Amarelo","Rosa Claro","Bege"
on_text: app.spinner_rpiso(spinnerrpi.text)
...
main.py (simplified code)
...
class PrimeiraJanela(Screen):
pass
class GerenciadorDeJanelas(ScreenManager):
pass
class MainApp(App):
texture = ObjectProperty()
def build(self):
self.title = 'MyApp'
self.texture = Image(source = 'wave.png').texture
sm = ScreenManager()
sm.add_widget(PrimeiraJanela(name = 'primeira'))
sm.current = 'primeira'
return sm
def spinner_rpiso(self, value):
if (value=='Branco Neve'):
self.root.get_screen('primeira').ids.rpi.text = str('0.90')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Banco Gelo'):
self.root.get_screen('primeira').ids.rpi.text = str('0.70')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Amarelo'):
self.root.get_screen('primeira').ids.rpi.text = str('0.70')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Rosa Claro'):
self.root.get_screen('primeira').ids.rpi.text = str('0.60')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Bege'):
self.root.get_screen('primeira').ids.rpi.text = str('0.60')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
def exit(self):
App.get_running_app().stop()
aplicativo = MainApp()
aplicativo.run()
Here is an extension of Spinner that honors an option_width property:
class SpinnerWithOptionWidth(Spinner):
option_width = NumericProperty(0) # the new property
def __init__(self, **kwargs):
self.invisible_attacher = None
super(SpinnerWithOptionWidth, self).__init__(**kwargs)
def on_is_open(self, instance, value):
# This method is modified from Spinner
attacher = self
if value:
if self.option_width > 0:
if self.invisible_attacher is None:
# The DropDown is the same width as the widget it attaches to
# so make an invisible widget with the desired width
self.invisible_attacher = Widget(opacity=0, size_hint=(None, None))
self.add_widget(self.invisible_attacher)
self.invisible_attacher.pos = (self.center_x - self.option_width/2, self.y)
self.invisible_attacher.size = (self.option_width, self.height)
attacher = self.invisible_attacher
# open th DropDown
self._dropdown.open(attacher)
else:
if self._dropdown.attach_to:
if self.invisible_attacher:
self.remove_widget(self.invisible_attacher)
self.invisible_attacher = None
self._dropdown.dismiss()
I am new in Kivy: python. I am trying to do an application using ScreenManager. The idea, that I have a map of maps and keys of maps should generate buttons on several screens. For instance:
map: {user1: {thing1: value1, thing2, value2}, user2: {thing1: value1...}}
The first screen will have two buttons in scrollview: user1 and user2.
Collection of buttons on the next screen will be thing1, thingN depending on what "user_name" user has pressed. This is just "toy" project, here I am studying the Kivy
I have some global variables. In the first screen the global variable "user_name" is initialized depending on what user has pressed on the first screen. Then in the class of the second screen I tried using map_of_maps[user_name].keys() and place these keys as buttons on the second screen.
# kivy_test.py
class MyMainApp(App):
gapi = gAPI("tilit.txt")
gapi.file_download()
gapi.parse_downloaded_file()
global data
global user_name
global account
data = gapi.get_data()
user_name = None
account = None
def build(self):
return kv
class MainWindow(Screen):
f_view = ObjectProperty(None)
def __init__(self,**kwargs):
super(MainWindow, self).__init__(**kwargs)
Clock.schedule_once(self.create_scrollview)
def create_scrollview(self, inst):
base = data.keys()
layout = GridLayout(cols=1)
layout.bind(minimum_height=layout.setter("height"))
for element in base:
button = Button(text=str(element), size_hint=(1, 0.1))
button.bind(on_press=self.on_scrbutton_pressed)
layout.add_widget(button)
scrollview = ScrollView(size=(Window.width, Window.height))
scrollview.add_widget(layout)
self.f_view.add_widget(scrollview)
def on_scrbutton_pressed(self, instance):
user_name = instance.text
print(instance.text)
class SecondWindow(Screen):
s_view = ObjectProperty(None)
def __init__(self,**kwargs):
super(SecondWindow,self).__init__(**kwargs)
Clock.schedule_once(self.create_scrollview)
def create_scrollview(self, inst):
base = data[user_name].keys() # Here I have a problem
layout = GridLayout(cols=1)
layout.bind(minimum_height = layout.setter("height"))
for element in base:
button = Button(text = str(element), size_hint=(1,0.1))
button.bind(on_press=self.on_scrbutton_pressed)
layout.add_widget(button)
scrollview = ScrollView(size=(Window.width, Window.height))
scrollview.add_widget(layout)
self.s_view.add_widget(scrollview)
def on_scrbutton_pressed(self, instance):
print(instance.text)
While running the code I have an error: base = data[user_name].keys(). KeyError: None
# my.kv
# Filename: my.kv
WindowManager:
MainWindow:
SecondWindow:
ThirdWindow:
<MainWindow>:
name: "main"
f_view: f_view
GridLayout:
cols: 1
ScrollView:
id : f_view
# Button:
# text: "User1"
# on_release:
# app.root.current = "second"
# root.manager.transition.direction = "left"
# Button:
# text: "User2"
# on_release:
# app.root.current = "second"
# root.manager.transition.direction = "left"
<SecondWindow>:
name: "second"
s_view : s_view
GridLayout:
cols: 1
ScrollView:
id : s_view
Button:
text: "Add"
<ThirdWindow>:
name: "third"
t_view : t_view
GridLayout:
cols: 1
id : t_view
And one more question: How to perform transitions between screens in this case?
Problem 1 - KeyError
base = data[user_name].keys() # Here I have a problem
KeyError: None
Root Cause
When Kivy process your kv file, it instantiates WindowManager object as the root, and it also instantiates its children, MainWindow and SecondWindow. While instantiating SecondWindow, it invoked the constructor (__init__) and method create_scrollview. It threw a KeyError because the global variable, user_name contains the initial value of None which does not exist in the dictionary type, data.
Solution
Add global user_name in method on_scrbutton_pressed of class MainWindow before populating user_name. Without global user_name, Python creates a new local variable when instance.text is assigned to user_name.
Problem 2
How to perform transitions between screens in this case?
Solution
Each screen has by default a property manager that gives you the
instance of the ScreenManager used. Add the following into method
on_scrbutton_pressed of class MainWindow.
self.manager.current = "second"
self.manager.transition.direction = "left"
In class Secondwindow, remove constructor and replace method create_scrollview(self, inst) with on_pre_enter(self)
In class MainWindow, replace constructor, __init__ with on_pre_enter(self):
Add method on_pre_leave to remove widgets added to prevent duplicates
Snippets
class MainWindow(Screen):
f_view = ObjectProperty(None)
def on_pre_enter(self):
Clock.schedule_once(self.create_scrollview)
...
def on_pre_leave(self):
self.f_view.clear_widgets()
def on_scrbutton_pressed(self, instance):
global user_name
user_name = instance.text
print(instance.text)
self.manager.current = "second"
self.manager.transition.direction = "left"
class SecondWindow(Screen):
s_view = ObjectProperty(None)
def on_pre_enter(self):
base = data[user_name].keys()
...
def on_pre_leave(self):
self.s_view.clear_widgets()
I've implemented almost the same thing in my app too.
I have a list of chapter names as keys and their summaries as the
values.
When the user enters the screen, the keys() are called and
buttons are created.
Depending on the button clicked, the summary is
displayed on the next screen
Here's how I coded it (Snippets)
class chapter_list(Screen):
def on_pre_enter(self):
self.buttons = []
for i in flamingo.keys(): #for loop to generate buttons
button = Button(text=str(i),height=150) #the text on the button is the chapter name
button.bind(on_press=self.pressed) #when the button is clicked, the function pressed on called
self.ids.fgrid.add_widget(button) #added to the flamingogrid
self.buttons.append(button)
Here the fgrid is the id: of the Layout(GridLayout in this case) I choose for this screen in the kv file
The self.buttons = [] is just to collect the buttons so that I can remove them on the screen exit.
Let me know if this was helpful.
The first screen of my app has a small menu (in a gridlayout) of three buttons. Two are supposed to open popups. One for Help and one for About.
The third one changes to another screen.
Only one popup works. The first one called (in the kivy file) works, the second doesn't open the popup. If I switch the order in cdd.kv, then the other one works.
Excerpt from cdd.kv:
CDDMainMenuLayout:
HelpButton:
size_hint: .5,.5
MetadataButton:
size_hint: .5,.5
on_release: app.root.current = 'metadata'
AboutButton:
size_hint: .5,.5
Excerpt from main.py:
class CDDMainMenuLayout(GridLayout):
"""
Provides the layout for the three buttons on the home screen.
"""
def __init__(self, *args, **kwargs):
super(CDDMainMenuLayout, self).__init__(*args, **kwargs)
self.rows = 1
self.cols = 3
self.size_hint = (.5,.5)
...
class CDDButton(Button):
def __init__(self, **kwargs):
super(CDDButton, self).__init__(**kwargs)
self.text = _('Button')
self.background_color = colors.grey2
class AboutButton(CDDButton):
def __init__(self, **kwargs):
super(AboutButton, self).__init__(**kwargs)
self.text = _("About the CDD")
self.background_color = colors.red1
a = Popup()
a.title = _("About Constraint Definition Designer, Version - " + __version__)
a.content = RstDocument(source='about.rst')
a.size_hint_x = .8
a.size_hint_y = .8
self.bind(on_release=a.open)
class HelpButton(CDDButton):
def __init__(self, **kwargs):
super(HelpButton, self).__init__(**kwargs)
self.text = _("Help")
self.background_color = colors.green1
h = Popup()
h.title = _("CDD Help")
h.content = RstDocument(source='help.rst')
h.size_hint_x = .8
h.size_hint_y = .8
self.bind(on_release=h.open)
Does anything change if you add extra lines self.popup = h and self.popup = a? One possibility is that your popups are simply being garbage collected since you don't store any references to them. I'm not sure if/how this would give your particular behaviour, but it's worth a try.