I'm trying to make an app with kivy and I want to be able to add a changeable amount of buttons with changeable text to a screen. Here's my code:
kv = Builder.load_file('main.kv')
class MyScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
label = Label(text="hello world!")
for i in range(5):
self.ids.FillIn.add_widget(label)
class MainApp(App):
def build(self):
return kv
if __name__ == "__main__":
MainApp().run()
and kivy:
<WindowManager#ScreenManager>:
<MainPage#Screen>:
Button:
on_release: app.root.current = "MyPrayers"
<MyScreen#Screen>:
name: 'MyScreen'
BoxLayout:
orientation: "vertical"
BoxLayout:
orientation: "vertical"
id: FillIn
WindowManager:
MainPage:
MyScreen:
The main problem with your code is that the line of code:
kv = Builder.load_file('main.kv')
is executed before the definition of MyScreen is loaded. So the NyScreen instance that is created by the Builder.load_file() has none of the attributes of the later defined MyScreen class. Other issues involve trying to access ids in the __init__() method (which is executed before ids are assigned) and trying to add the same Label widget multiple times (any widget can have only one parent).
Here is a modified version of your python code that fixes the above issues:
class MyScreen(Screen):
def __init__(self, **kwargs):
super(MyScreen, self).__init__(**kwargs)
Clock.schedule_once(self.add_widgets)
def add_widgets(self, *args):
for i in range(5):
label = Label(text="hello world!")
self.ids.FillIn.add_widget(label)
class MainApp(App):
def build(self):
kv = Builder.load_file('main.kv') # load kv here, after MyScreen is defined
return kv
if __name__ == "__main__":
MainApp().run()
And the slightly modified kv:
<WindowManager#ScreenManager>:
<MainPage#Screen>:
Button:
text: 'Go To MyScreen'
on_release: app.root.current = "MyScreen"
<MyScreen>: # the #Screen is not needed since the MyScreen class is defined in the python code
name: 'MyScreen'
BoxLayout:
orientation: "vertical"
BoxLayout:
orientation: "vertical"
id: FillIn
WindowManager:
MainPage:
MyScreen:
Related
I have an application with multiple screens, and I am trying to call a method of my ScreenOne from within another screen. The code is a minimal example.
I would like to know the proper method(s) of referencing this method of ScreenOne when I am within my second screen.
cart_list = {}
class ScreenOne(Screen):
def update_cart(self):
cart_list.update({'Item one': 1.00})
for key, value in cart_list:
print(key, value)
class ScreenTwo(Screen):
pass
The associated kv code is as follows
ScreenTwo:
Button:
id: item_1
text: "Add to cart"
on_press: MainScreen.update_cart()
Solution
kv file
Add id to ScreenOne.
Each screen has by default a property manager that gives you the instance of the ScreenManager used.
Reference the method using root.manager.ids.screen_one.update_cart()
Below are some minor updates required in the Python script.
py file
Declare cart_list as global in method, update_cart
Append .items() to cart_list e.g. for key, value in cart_list.items():
Example
main.py
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
cart_list = {}
class ScreenOne(Screen):
def update_cart(self):
global cart_list
cart_list.update({'Item one': 1.00})
for key, value in cart_list.items():
print(key, value)
class ScreenTwo(Screen):
pass
class TestApp(App):
def build(self):
return Builder.load_file('main.kv')
if __name__ == '__main__':
TestApp().run()
main.kv
#:kivy 1.11.0
ScreenManager:
ScreenOne:
id: screen_one
name: 'screen1'
ScreenTwo:
id: screen_two
name: 'screen2'
<ScreenOne>:
BoxLayout:
Button:
text: 'Goto Screen Two'
on_press: root.manager.current = 'screen2'
Button:
text: 'Quit'
<ScreenTwo>:
Button:
id: item_1
text: "Add to cart"
on_press: root.manager.ids.screen_one.update_cart()
Output
I believe that my problem is that the label_text tag in the ScreenManager: section is not being updated when the change_text() function is run.Because it just shows the original label_text value, which in this case is nothing.
Does anyone know how get the tag to update? My goal is to be able to pass strings between the 2 Screen classes. So when a user enters something like a zip code on the previous screen i can pass it to the new screen.
#:kivy 1.1.3
ScreenManager:
id: screen_manager
SearchScreen:
id: search_screen
name: 'SearchScreen'
manager: screen_manager
ForecastScreen:
id: forecast_screen
name: 'ForecastScreen'
manager: screen_manager
label_text: search_screen.text
<SearchScreen>:
display: entry
FloatLayout:
TextInput:
id: entry
on_text_validate:
root.change_text()
<ForecastScreen>:
BoxLayout:
FloatLayout:
Label:
text:root.label_text
Then the py code:
class SearchScreen(Screen):
text = StringProperty('')
def change_text(self):
self.text = "show this text"
self.manager.current = "ForecastScreen"
class ForecastScreen(Screen):
label_text = StringProperty()
Builder.load_file('weather.kv')
sm = ScreenManager()
sm.add_widget(SearchScreen(name='SearchScreen'))
sm.add_widget(ForecastScreen(name='ForecastScreen'))
class WeatherApp(App):
def build(self):
return sm
if __name__ == "__main__":
WeatherApp().run()
First, on_text_validate will only be called when you press enter if the TextInput has the multiline property to False, so set it.
On the other hand I see that you do not understand the difference between:
Foo:
and
<Foo>:
In the first case you are creating an instance of Foo (and there can only be one element of this type) and in the second you are implementing a component. When you call Builder.load_file() and having that first element without "<" ">" that instance is returned, that is, there is already a ScreenManager, but in your case you have created another with python code. The ScreenManager instantiated in the .kv already has the Screen where the texts are already linked, and in changes those of Python are not. And when you return the ScreenManager created in python without the linked elements you observe a correct behavior, nothing will be modified.
What you have to do is remove the ScreenManager from the .py and use the .kv:
*.py
class SearchScreen(Screen):
text = StringProperty('')
def change_text(self):
self.text = "show this text"
self.manager.current = "ForecastScreen"
class ForecastScreen(Screen):
label_text = StringProperty("")
sm = Builder.load_file('weather.kv')
class WeatherApp(App):
def build(self):
return sm
if __name__ == "__main__":
WeatherApp().run()
*.kv
ScreenManager:
id: screen_manager
SearchScreen:
id: search_screen
name: 'SearchScreen'
ForecastScreen:
id: forecast_screen
name: 'ForecastScreen'
label_text: search_screen.text
<SearchScreen>:
display: entry
FloatLayout:
TextInput:
id: entry
multiline: False # <----
on_text_validate:
root.change_text()
<ForecastScreen>:
BoxLayout:
FloatLayout:
Label:
text: root.label_text
I'm trying to call a custom widget method from its ids.
But I received an AttributeError: 'LabelBox' object has no attribute 'change_first_text'.
As a simple as possible working example can be found here with the PanelApp.py file:
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_file("panel.kv")
class LabelBox(BoxLayout):
def __init__(self, *args, **kwargs):
super(LabelBox, self).__init__(*args, **kwargs)
def change_first_text(self, text):
self.ids.first.text = text
class ButtonList(BoxLayout):
pass
class Test(TabbedPanel):
pass
class TabbedPanelApp(App):
def build(self):
self.test = Test()
self.btn_list = ButtonList()
self.vbox = BoxLayout(orientation="vertical")
self.vbox.add_widget(self.btn_list)
self.vbox.add_widget(self.test)
return self.vbox
def change_first(self, value):
print("Button clicked and new value is: '{}'".format(value))
self.test.ids.lbs.change_first_text(value)
if __name__ == '__main__':
TabbedPanelApp().run()
and the panel.kv file:
<ButtonList#ButtonList>:
orientation: "horizontal"
Button:
text: "change fisrt to me"
on_press: app.change_first(self.text)
Button:
text: "change two to me"
<LabelBox#BoxLayout>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"
<Test>:
size_hint: .5, .5
pos_hint: {'center_x': .5, 'center_y': .5}
do_default_tab: False
TabbedPanelItem:
text: 'first tab'
LabelBox:
id: lbs
Calling the script cause a runtime error that I can't understand.
Have you any clue on how to manage such event callback through the application?
The problem in your case is that you are creating 2 classes called LabelBox:
1.
class LabelBox(BoxLayout):
def __init__(self, *args, **kwargs):
super(LabelBox, self).__init__(*args, **kwargs)
def change_first_text(self, text):
self.ids.first.text = text
2.
<LabelBox#BoxLayout>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"
I understand that you only want to have a class so it is appropriate to do the creation with the inheritance in the .py and only the implementation of children in the .kv. The solution is to change delete #BoxLayout in the .kv
<LabelBox>:
Label:
id: first
text: "first"
Label:
id: two
text: "two"
I want to call a method add_category_to_tree() from TreeCategory class, when pressing SAVE button in AddCategoryPopup class. However, I have problems how to reference to the TreeCategory instance, which is created in KV file.
I was trying to search solution, but nothing works. Currently i get AttributeError: 'super' object has no attribute '__getattr__' error.
How should I do this properly ? Thank you for help
class TreeCategory(TreeView):
def __init__(self, **kwargs):
super(TreeCategory, self).__init__(**kwargs)
def add_category_to_tree(self, name):
self.add_node(TreeViewLabel(text = name.upper()))
class AddCategoryPopup(Popup):
def save(self):
self.ids.tree.add_category_to_tree(self.ids.entry.text) # ????
db.adding_to_db('kategorie', 'nazwa', self.ids.entry.text)
self.dismiss()
def close(self):
self.dismiss()
class MainScreen(BoxLayout):
tree = ObjectProperty(None)
def add_category_button(self):
popup = AddCategoryPopup(title = 'Dodawanie nowej kategorii')
return popup.open()
class GuiCookBookApp(App):
def build(self):
self.title = "Książka kucharska"
return MainScreen()
if __name__ == "__main__":
db = DatabaseManager("cookbook.sqlite")
GuiCookBookApp().run()
KV file:
<AddCategoryPopup>:
BoxLayout:
orientation: 'vertical'
TextInput:
id: entry
multiline: False
hint_text: 'Podaj nazwę kategorii...'
BoxLayout:
orientation: 'horizontal'
Button:
text: 'SAVE'
on_press: root.save()
Button:
text: 'CANCEL'
on_press: root.close()
<MainScreen>:
orientation: "vertical"
display: entry
tree: tree
BoxLayout:
id: menu
size_hint_y: .1
Button:
text: 'Dodaj kategorię'
on_press: root.add_category_button()
BoxLayout:
id: recipe_view
orientation: "horizontal"
TreeCategory:
id: tree
hide_root: True
size_hint: .25, 1
With self.ids in Python you can only access the ids in KV from that particular class. So self.ids.tree is only possible inside the MainScreen class in Python not in the AddCategoryPopup class.
You could create an ObjectProperty 'topwidget' in your AddCategoryPopup rule and pass in the Main class when you instantiate the popup. Something like:
popup = AddCategoryPopup(topwidget=self)
Then in the 'save' method of your custom popup class you can do something like:
self.topwidget.tree...
You can do it quite a few ways. For instance you can put the .kv in your main .py file.
w = Builder.load_string('''
Widget:
height: self.width / 2. if self.disabled else self.width
x: self.y + 50
''')
https://kivy.org/docs/api-kivy.lang.builder.html
You can simply name the .kv file
guicookbookapp.kv
and leave it in the root directory of your project.
https://kivy.org/docs/examples/gen__application__app_with_kv__py.html
You can also add the following
from kivy.lang import Builder
Builder.load_file('guicookbookapp.kv')
Hopefully I understood your question correctly.
I have a python script like such:
class GuiApp(App):
def build(self):
#for i in range(24):
#Grid.add_widget(Button(text='Test'))
return Gui()
class Gui(BoxLayout):
pass
And I have a .kv file like such:
<Gui>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Top'
GridLayout:
id: Grid
cols: 5
rows: 5
How do I apply a loop to add the 24 buttons to the GridLayout?
I thought that I could call the id Grid like shown in the python comments, but that fails.
How do I go about applying a loop to add buttons to the GridLayout in the kv file with the id Grid?
I seemed to have figured out how to do the loop propery:
py
class GuiApp(App):
def build(self)
g = Gui()
for i in range(24):
g.grid.add_widget(Button(text='test'))
return g
class Gui(BoxLayout):
grid = ObjectProperty(None)
kv
<Gui>:
grid: Grid
BoxLayout:
orientation: 'vertical'
Button:
text: 'Top'
GridLayout:
id: Grid
cols: 5
rows: 5
In order for it to work I needed to reference it _grid: Grid in the .kv file to be found by the ObjectProperty, the grid, when used in python needed to be lowercase
def build(self):
layout = GridLayout()
for i in range(24): layout.add_widget(...)
return layout
I think at least
class GuiApp(App):
def build(self):
g = Gui()
for i in range(24):
g.Grid.add_widget(Button(text='Test'))
return g
class Gui(BoxLayout):
Grid = ObjectProperty(None)