How can I add an MDDataTable to a Screen in a class that's not the main app class? - kivy

I'm trying to create an app where after you log in you're shown a table of users. If the login is successful I want the user to be sent to another screen that has an MDDataTable in it.
All of the examples I've found online only show how to display the table using the build method in the main app.
Code example:
class ScreenOne(Screen):
# Displays MDDataTable without the need to press anything to view the table.
class ScreenTwo(Screen):
# When this screen validates user successfully it sends
# me to the other screen that shows an
# MDDataTable and not a blank screen etc...
sm = ScreenManager()
class MainApp(MDApp):
def build(self):
sm.add_widget(ScreenOne(name='screenone'))
sm.add_widget(ScreenTwo(name='screentwo'))
return sm
MainApp().run()
I keep getting either a blank screen or the following error:
ValueError: KivyMD: App object must be initialized before loading root widget. See https://github.com/kivymd/KivyMD/wiki/Modules-Material-App#exceptions
I kept changing the code trying to fix it but I just kept running into all sorts of errors and problems and I just don't understand enough to make my question any clearer.
Here's my .py file code:
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.stacklayout import MDStackLayout
from kivymd.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.datatables import MDDataTable
from kivy.metrics import dp
from kivy.uix.anchorlayout import AnchorLayout
class ClientsTable(Screen):
def load_table(self):
layout = Screen()
data_tables = MDDataTable(
size_hint=(0.9, 0.6),
use_pagination=True,
check=True,
column_data=[
("No.", dp(30)),
("Column 1", dp(30)),
("Column 2", dp(30)),
("Column 3", dp(30)),
("Column 4", dp(30)),
("Column 5", dp(30)),],
row_data=[
(f"{i + 1}", "2.23", "3.65", "44.1", "0.45", "62.5")
for i in range(50)],)
layout.add_widget(data_tables)
return layout
class LoginPage(Screen):
username = ObjectProperty()
password = ObjectProperty()
def validate_user(self):
if self.username.text == "m":
sm.current = "Clientstable"
self.username.text = ""
self.password.text = ""
else:
print("Not here!")
sm = ScreenManager()
class MainWindow(MDApp):
def build(self):
self.title = "EasySport"
sm.add_widget(LoginPage(name='Loginpage'))
sm.add_widget(ClientsTable(name='Clientstable'))
return sm
if __name__ == "__main__":
MainWindow().run()
.kv code:
ScreenManager:
LoginPage:
ClientsTable:
<LoginPage>:
username: User
password: Pass
MDTextField:
id: User
hint_text: "Username"
size_hint: 0.5, 0.09
pos_hint: {"center_x": 0.5, "center_y": 0.7}
MDTextField:
id: Pass
hint_text: "Password"
size_hint: 0.5, 0.09
pos_hint: {"center_x": 0.5, "center_y": 0.6}
MDFillRoundFlatButton:
text: "Login"
size_hint: 0.5, 0.06
pos_hint: {"center_x": 0.5, "center_y": 0.4}
on_release:
root.validate_user()
root.manager.transition.direction = 'left'
<ClientsTable>:
MDFillRoundFlatButton:
text: "Back"
size_hint: 0.5, 0.06
pos_hint: {"center_x": 0.5, "center_y": 0.4}
on_release:
root.manager.transition.direction = 'right'
root.manager.current = 'Loginpage'
MDFillRoundFlatButton:
text: "Load Table"
size_hint: 0.5, 0.06
pos_hint: {"center_x": 0.5, "center_y": 0.3}
on_release:
root.load_table()
Now it doesn't do anything when I press the Load Table button!

I SOLVED IT!
Here's the .py file code:
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.stacklayout import MDStackLayout
from kivymd.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.datatables import MDDataTable
from kivy.metrics import dp
from kivy.uix.anchorlayout import AnchorLayout
class ClientsTable(Screen):
def load_table(self):
layout = AnchorLayout()
self.data_tables = MDDataTable(
pos_hint={'center_y': 0.5, 'center_x': 0.5},
size_hint=(0.9, 0.6),
use_pagination=True,
check=True,
column_data=[
("No.", dp(30)),
("Column 1", dp(30)),
("Column 2", dp(30)),
("Column 3", dp(30)),
("Column 4", dp(30)),
("Column 5", dp(30)),],
row_data=[
(f"{i + 1}", "2.23", "3.65", "44.1", "0.45", "62.5")
for i in range(50)],)
self.add_widget(self.data_tables)
return layout
def on_enter(self):
self.load_table()
class LoginPage(Screen):
username = ObjectProperty()
password = ObjectProperty()
def validate_user(self):
if self.username.text == "m":
sm.current = "Clientstable"
self.username.text = ""
self.password.text = ""
else:
print("Not here!")
sm = ScreenManager()
class MainWindow(MDApp):
def build(self):
sm.add_widget(LoginPage(name='Loginpage'))
sm.add_widget(ClientsTable(name='Clientstable'))
return sm
if __name__ == "__main__":
MainWindow().run()
And the .kv file code:
ScreenManager:
LoginPage:
ClientsTable:
<LoginPage>:
username: User
password: Pass
MDTextField:
id: User
hint_text: "Username"
size_hint: 0.5, 0.09
pos_hint: {"center_x": 0.5, "center_y": 0.7}
MDTextField:
id: Pass
hint_text: "Password"
size_hint: 0.5, 0.09
pos_hint: {"center_x": 0.5, "center_y": 0.6}
MDFillRoundFlatButton:
text: "Login"
size_hint: 0.5, 0.06
pos_hint: {"center_x": 0.5, "center_y": 0.4}
on_release:
root.validate_user()
I just needed to add the Screen's on_enter() method to my class like it says in the documentation. See here

Related

'NoneType' object is not subscriptable using kv

I'm trying to make a phone app, and want to be able to navigate between multiple screens.
However I don't know what to do with the syntax error that keeps being returned.
I'm new to kivy, and looked at a ton of tutorial stuff to put my code together, help would be much appreciated.
Here is code on main file
from kivy.uix.screenmanager import ScreenManager, Screen #screen manager is a widget dedicated to managing multiple screens for your application.
from kivy.uix.button import Button #creates the button in kivy
from kivy.uix.floatlayout import FloatLayout #means can place elements based on windows size
from kivy.config import Config
from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup
from kivy.uix.widget import Widget
from kivy.uix.label import Label
Config.set('graphics', 'resizable', True)
kv = Builder.load_file("phoneassistant.kv") #links to kv file where graphics are loaded
sm = WindowManager() #sm will control moving between screens using the ScreenManager class
#creating classes for all screen on app help specify methods for individual screens using argument Screen
class LoginWindow(Screen, FloatLayout):
def mainBtn(self):
self.reset()
sm.current = "main"
def createBtn(self):
self.reset()
sm.current = "create"
class CreateAccountWindow(Screen, FloatLayout):
def loginBtn(self):
self.reset()
sm.current = "login"
class MainWindow(Screen, FloatLayout):
def loginBtn(self):
self.reset()
sm.current = "login"
def settingsBtn(self):
self.reset()
sm.current = "settings"
def storageBtn(self):
self.reset()
sm.current = "storage"
class SettingsWindow(Screen, FloatLayout):
pass
class StorageWindow(Screen, FloatLayout):
pass
class AIWindow(Screen, FloatLayout):
pass
class WindowManager(ScreenManager): #this won't be a sceen, ScreenManager is to manage transitions between all screens
pass
#gives the screens names
screens = [LoginWindow(name = "login"), CreateAccountWindow(name = "create"), MainWindow(name="main"),
SettingsWindow(name = "settings"),StorageWindow(name = "storage"), AIWindow(name = "AI")]
#adds the screens to the manager
for screen in screens:
sm.add_widget(screen)
#sends user to login page whenever program is ran
sm.current = "login"
class PhoneAssistantApp(App): #creates the app class
def build(self):
return sm #returns screens when class is ran
if __name__ == "__main__": #runs the app
PhoneAssistantApp().run()
The code on the kv file
<ScreenManagement>:
transition: SlideTransition
LoginWindow:
CreateAccountWindow:
MainWindow:
SettingsWindow:
StorageWindow:
AIWindow:
<LoginWindow>:
name: "login"
    FloatLayout:
Button:
         text: "Don't have an account?"
font_size: 0.2, 0.1
            background_color: 0, 0, 1, 1
size_hint: 0.4, 0.3
pos: {"x":0.5, "y":0.25}
            on_release:
                root.manager.transition.direction = "left"
                root.manager.transition.duration = 1
root.createBtn()
Button:
            text: "Login here"
font_size: 0.1, 0.2
            background_color: 1, 0, 0, 1
size_hint: 0.4, 0.3
pos_hint: {"x":0.5, "y":0.75}
            on_release:
                root.manager.transition.direction = "right"
                root.manager.transition.duration = 1
                root.mainBtn()
  
<CreateAccountWindow>:
name: "create"
    FloatLayout:
        Button:
            text: "Create login"
font_size: 0.25, 0.25
            background_color : 1, 1, 0, 1
size_hint: 0.5, 0.5
pos_hint: {"x":0.5, "y":0.1}
            on_release:
                app.root.current = "login"
                root.manager.transition.direction = "right"
                root.manager.transition.duration = 1
 
<MainWindow>:
name: "main"
    FloatLayout:
        Button:
            text: "Return to login page"
font_size: 0.25, 0.25
            background_color : 1, 1, 0, 1
size_hint: 0.25, 0.35
pos_hint: {"x":0.5, "y":0.1}
            on_release:
                app.root.current = "login"
                root.manager.transition.direction = "left"
                root.manager.transition.duration = 1
        Button:
            text: "Access Settings"
font_size: 0.5, 0.5
            background_color : 1, 0, 1, 1
size_hint: 0.35, 40.5
pos_hint: {"x":0.1, "y":0.9}
            on_release:
                app.root.current = "settings"
                root.manager.transition.direction = "right"
                root.manager.transition.duration = 1
        Button:
            text: "Access Storage"
font_size: 0.75, 0.75
            background_color : 1, 1, 1, 0
size_hint: 0.45, 0.25
pos_hint: {"x":0.9, "y":0.5}
            on_release:
                app.root.current = "storage"
                root.manager.transition.direction = "right"
                root.manager.transition.duration = 1
 
<SettingsWindow>:
name: "settings"
    FloatLayout:
        Button:
            text: "Return to Main Menu"
            background_color: 0, 1, 1, 1
            on_release:
                app.root.current = "main"
                root.manager.transition.direction = "left"
                root.manager.transition.duration = 1
 
<StorageWindow>:
name: "storage"
FloatLayout:
        Button:
            text: "Return to Main Menu"
font_size: 0.25, 0.25
            background_color : 1, 1, 1, 0
size_hint: 0.5, 0.5
pos_hint: {"x":0.1, "y":0.1}
            background_color: 1, 0, 0, 1
            on_release:
                app.root.current = "main"
                root.manager.transition.direction = "left"
root.manager.transition.duration = 1
Button:
text: "Access AI"
font_size: 0.25, 0.25
            background_color : 1, 1, 1, 0
size_hint: 0.5, 0.5
pos_hint: {"x":0.9, "y":0.9}
background_color: 1, 1, 0, 0
on_release:
app.root.current = "ai"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
<AIWindow>
name: "ai"
FloatLayout:
Button:
text: "Return to Storage"
background_color: 0, 1, 1, 0
on_release:
app.root.current = "storage"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
and the error
Traceback (most recent call last):
line 15, in <module>
kv = Builder.load_file("phoneassistant.kv") #links to kv file where graphics are loaded
line 305, in load_file
return self.load_string(data, **kwargs)
line 372, in load_string
parser = Parser(content=string, filename=fn)
line 483, in __init__
self.parse(content)
line 593, in parse
objects, remaining_lines = self.parse_level(0, lines)
line 756, in parse_level
if current_property[:3] == 'on_':
TypeError: 'NoneType' object is not subscriptable
I have tried changing the modules I use, such as changing to BoxLayout to see if that would work
And changed the on_press, to on_release, and sometimes tried to have both in my kv file
In addition, added transition to screen management class in kv file
Also created the def statements in the login, create, and main, classes on the main program
These were all mostly because I thought the main problem was the on_release button, however it's like likely also the structure of my code.
I didn't see where your code would create that error, but possibly your kivy file has an indentation problem somewhere. Something could be over-indented or else missing such that there is no object present. The code below is runnable but I don't think it has the layout polished as you intended. I took the liberty of adding a few example methods to your LoginWindow that I thought you may want to use.
I was not sure what was meant by reset() as you didn't have matching code for this so i changed this just to something that prints to the console.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from kivy.lang import Builder
# screen manager is a widget dedicated to managing multiple screens for your application.
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button # creates the button in kivy
from kivy.uix.floatlayout import FloatLayout # means can place elements based on windows size
from kivy.config import Config
from kivy.properties import StringProperty, BooleanProperty, ListProperty, NumericProperty
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.app import App
kv = Builder.load_string('''
# <ScreenManagement>:
# transition: SlideTransition
# LoginWindow:
# CreateAccountWindow:
# MainWindow:
# SettingsWindow:
# StorageWindow:
# AIWindow:
<LoginWindow>:
name: "login"
FloatLayout:
Button:
text: "Don't have an account?"
# font_size: 0.2, 0.1
background_color: 0, 0, 1, 1
size_hint: 0.4, 0.3
# pos: {"x":0.5, "y":0.25}
on_release:
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
root.createBtn()
Button:
text: "Login here"
# font_size: 0.1, 0.2
background_color: 1, 0, 0, 1
size_hint: 0.4, 0.3
pos_hint: {"x":0.5, "y":0.75}
on_release:
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
root.mainBtn()
<CreateAccountWindow>:
name: "create"
FloatLayout:
Button:
text: "Create login"
# font_size: 0.25, 0.25
background_color : 1, 1, 0, 1
size_hint: 0.5, 0.5
pos_hint: {"x":0.5, "y":0.1}
on_release:
app.root.current = "login"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
<MainWindow>:
name: "main"
FloatLayout:
Button:
text: "Return to login page"
# font_size: 0.25, 0.25
background_color : 1, 1, 0, 1
size_hint: 0.25, 0.35
pos_hint: {"x":0.5, "y":0.1}
on_release:
app.root.current = "login"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
Button:
text: "Access Settings"
# font_size: 0.5, 0.5
background_color : 1, 0, 1, 1
size_hint: 0.35, 40.5
pos_hint: {"x":0.1, "y":0.9}
on_release:
app.root.current = "settings"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
Button:
text: "Access Storage"
# font_size: 0.75, 0.75
background_color : 1, 1, 1, 0
size_hint: 0.45, 0.25
pos_hint: {"x":0.9, "y":0.5}
on_release:
app.root.current = "storage"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
<SettingsWindow>:
name: "settings"
FloatLayout:
Button:
text: "Return to Main Menu"
background_color: 0, 1, 1, 1
on_release:
app.root.current = "main"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
<StorageWindow>:
name: "storage"
FloatLayout:
Button:
text: "Return to Main Menu"
# font_size: 0.25, 0.25
background_color : 1, 1, 1, 0
size_hint: 0.5, 0.5
pos_hint: {"x":0.1, "y":0.1}
background_color: 1, 0, 0, 1
on_release:
app.root.current = "main"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
Button:
text: "Access AI"
# font_size: 0.25, 0.25
background_color : 1, 1, 1, 0
size_hint: 0.5, 0.5
pos_hint: {"x":0.9, "y":0.9}
background_color: 1, 1, 0, 0
on_release:
app.root.current = "ai"
root.manager.transition.direction = "right"
root.manager.transition.duration = 1
<AIWindow>
name: "ai"
FloatLayout:
Button:
text: "Return to Storage"
background_color: 0, 1, 1, 0
on_release:
app.root.current = "storage"
root.manager.transition.direction = "left"
root.manager.transition.duration = 1
''')
Config.set('graphics', 'resizable', True)
# creating classes for all screen on app help specify methods for individual screens using argument Screen
class LoginWindow(Screen, FloatLayout):
def on_pre_enter(self, *args):
print(" on pre-enter")
def on_enter(self, *args):
print("on enter")
def on_leave(self, *args):
print("login leave")
def on_pre_leave(self, *args):
print("pre-leave")
def reset(self):
print(f"reset {self}")
def mainBtn(self):
self.reset()
App.get_running_app().root.current = "main"
def createBtn(self):
self.reset()
App.get_running_app().root.current = "create"
class CreateAccountWindow(Screen, FloatLayout):
def reset(self):
print(f"{self}")
def loginBtn(self):
self.reset()
App.get_running_app().root.current = "login"
class MainWindow(Screen, FloatLayout):
def reset(self):
print(f"{self}")
def loginBtn(self):
self.reset()
App.get_running_app().root.current = "login"
def settingsBtn(self):
self.reset()
App.get_running_app().root.current = "settings"
def storageBtn(self):
self.reset()
App.get_running_app().root.current = "storage"
class SettingsWindow(Screen, FloatLayout):
pass
class StorageWindow(Screen, FloatLayout):
pass
class AIWindow(Screen, FloatLayout):
pass
class PhoneAssistantApp(App): # creates the app class
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = ScreenManager() # sm will control moving between screens using the ScreenManager class
# gives the screens names
self.screens = (LoginWindow(name="login"), CreateAccountWindow(name="create"), MainWindow(name="main"),
SettingsWindow(name="settings"), StorageWindow(name="storage"), AIWindow(name="AI"))
def build(self) -> ScreenManager:
# adds the screens to the manager
for screen in self.screens:
self.sm.add_widget(screen)
# user to login page whenever program is ran
self.sm.current = "login"
return self.sm # returns screens when class is ran
if __name__ == "__main__":
PhoneAssistantApp().run()

How can a dynamic class access another class' attributes such as root.width/root.height?

Here is a class NewWindow with a GridLayout in a FloatLayout, a dynamic class NewButton inheriting from ToggleButton, and a class in the Python file that receives the GridProperty in order to add_widget NewButton into it.
The overall goal is to have a button that adds a toggle button into the grid layout, and for all button/toggle buttons to be able to dynamically change size for different screen resolutions. The issue I'm having is that I can't simply write (root.width**2 + root.height**2) / 12**4 for NewButton. I think it's because "root" in NewWindow is not the same as "root" in NewButton, but I do not have a solution.
Is there a way for me to access NewWindow's "root" inside of NewButton? I ultimately just want all my buttons to match each other's sizes and for it to be dynamic, so I don't want to simply write a static "30" for font_size.
# File: main.kv
<NewWindow>:
parentGrid: parentGrid
FloatLayout:
Button:
text: "Add New Button"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint: .4, 0.05
font_size: (root.width**2 + root.height**2) / 12**4
on_release:
root.addButton()
GridLayout:
id: parentGrid
cols: 1
pos_hint: {"x": 0.5, "top": 0.7
size_hint: .4, .5
<NewButton#ToggleButton>:
text: "BUTTON"
size_hint_y: ???
#height: ???
font_size: ???
# Cannot simply use root.width or root.height, also size_hint: .4, 0.05 doesn't work
# File: MainApp.py
class NewWindow(Screen):
parentGrid = ObjectProperty(None)
def AddButton(self):
self.parentGrid.add_widget(NewButton())
You are right the two roots are different. One is as you say the size of the new window, but the other would only be the size of GridLayout. The problem you have with the size_hint is because you are using a GridLayout. If you can change to example a BoxLayout that would work if not the size_hint: None, None and then set the size: would work:
To get the the NewWindow Root use app.root.width and app.root.heigth
main.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.togglebutton import ToggleButton
class NewButton(ToggleButton):
pass
class NewWindow(Screen):
parentGrid = ObjectProperty(None)
def __init__(self, **kwargs):
super(NewWindow, self).__init__(**kwargs)
def addButton(self):
print('test')
self.parentGrid.add_widget(NewButton())
class Manager(ScreenManager):
pass
class TestApp(App):
def build(self):
return Builder.load_file('test.kv')
if __name__ == '__main__':
TestApp().run()
.kv:
Manager:
NewWindow:
name: 'new'
<NewButton#ToggleButton>:
text: "BUTTON18"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint_x: None
size_hint: 1, 1
#size: 10, 10
font_size: (app.root.width**2 + app.root.height**2) / 12**4
<NewWindow>:
parentGrid: parentGrid
FloatLayout:
Button:
text: "Add New Button"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint: .4, 0.05
font_size: (root.width**2 + root.height**2) / 12**4
on_release:
root.addButton()
BoxLayout:
id: parentGrid
cols: 1
pos_hint: {"x": 0.5, "top": 0.7}
size_hint: .4, .5
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
size: self.size
pos: self.pos
# Cannot simply use root.width or root.height, also size_hint: .4, 0.05 doesn't work
I did also add the screenmanager to be able to access the width and height
Hope this helps

Kivy: RecycleView not updating

I have two screens in my Kivy-based app, each with a RecycleView to display lists. Both RVs should update when I press a button (add_button_clicked()) on one screen. Currently, the first RV (AddRecipe screen) works mostly as intended. However, the RV on the ViewList screen does not update with new data.
I am new to Python and even newer to Kivy - what am I missing here?
.py:
#! python3
# GroceryList.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.spinner import Spinner
from kivy.properties import ListProperty
from kivy.properties import ObjectProperty
from kivy.uix.recycleview import RecycleView
selectedMeals = []
ingredients = []
class ViewList(Screen):
def updateList(self, portions, recipe):
ingredients.append((portions, recipe))
print(ingredients) # This proves updateList is getting called
##THE FOLLOW RV THINGS DON'T WORK:
self.ids.shoplist.data = [{'text': '%s (%s)' %(ingredients[i][0], ingredients[i][1])}
for i in range(len(ingredients))]
self.ids.shoplist.refresh_from_data()
##
class AddRecipe(Screen):
recipes = ListProperty()
recipes = {'Guacarole':5, 'Salsa':3, 'Chips':1} # Sample dict for demo
def add_one(self):
if self.addportions.text != '':
value = int(self.addportions.text)
self.addportions.text = str(value+1)
def subtract_one(self):
if self.addportions.text != '':
value = int(self.addportions.text)
self.addportions.text = str(value-1)
def add_button_clicked(self, recipe, portions):
if recipe != '':
selectedMeals.append((recipe, portions))
self.ids.mealslist.data = [{'text': '%s (%s)' %(selectedMeals[i][0], selectedMeals[i][1])}
for i in range(len(selectedMeals))]
self.ids.mealslist.refresh_from_data()
ViewList().updateList(portions, recipe)
def spinner_clicked(self, val):
self.addportions.text = str(self.recipes[val])
class WindowManager(ScreenManager):
pass
class GroceryList(App):
mealsRVdata = ListProperty()
shoppingRVdata = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = ScreenManager()
def build(self):
Builder.load_file("grocerylist.kv")
screens = [ViewList(name='viewlist'), AddRecipe(name='addrecipe')]
for screen in screens:
self.sm.add_widget(screen)
self.sm.current = "addrecipe"
return self.sm
if __name__ == '__main__':
GroceryList().run()
And .kv:
#:kivy 1.11.1
# GroceryList.kv
# Used by GroceryList.py
WindowManager:
AddRecipe:
ViewList:
<ViewList>:
name: "viewlist"
shoplist: shoplist
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1, 0.8)
RecycleView:
id: shoplist
data: app.shoppingRVdata
viewclass: 'RVLabel'
RecycleGridLayout:
cols: 1
size_hint: None, None
default_size: sp(200), sp(25)
height: self.minimum_height
width: self.minimum_width
BoxLayout:
size_hint: (1, 0.2)
Button:
text: "View shopping list"
on_release:
app.root.current = "viewlist"
Button:
text: "Add recipes"
on_release:
app.root.current = "addrecipe"
root.manager.transition.direction = "left"
<AddRecipe>:
name: "addrecipe"
addportions: addportions
mealslist: mealslist
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1, 0.08)
Label:
size_hint: (0.64, 1)
font_size: 20
text: "Select a meal to add"
Label:
size_hint: (0.36, 1)
font_size: 20
text: "Select portions"
BoxLayout:
size_hint: (1, 0.08)
Spinner:
id: add_spinner
size_hint: (0.64, 1)
text: ""
values: root.recipes.keys()
on_text:
root.spinner_clicked(add_spinner.text)
Button:
size_hint: (0.12, 1)
font_size: 36
text: "-"
on_release: root.subtract_one()
Label:
id: addportions
size_hint: (0.12, 1)
font_size: 24
text: ''
Button:
size_hint: (0.12, 1)
font_size: 36
text: "+"
on_release: root.add_one()
FloatLayout:
size_hint: (1, 0.08)
Button:
size_hint: (0.4, 1)
pos_hint: {"x": 0.3, "top": 1}
text: "Add to shopping list"
on_release:
root.add_button_clicked(add_spinner.text, addportions.text)
BoxLayout:
size_hint: (1, 0.68)
RecycleView:
id: mealslist
data: app.mealsRVdata
viewclass: 'RVLabel'
RecycleGridLayout:
cols: 1
size_hint: None, None
default_size: sp(200), sp(25)
height: self.minimum_height
width: self.minimum_width
BoxLayout:
size_hint: (1, 0.08)
Button:
text: "View shopping list"
on_release:
app.root.current = "viewlist"
root.manager.transition.direction = "right"
Button:
text: "Add recipes"
on_release:
app.root.current = "addrecipe"
<RVLabel#Label>:
text_size: self.size
I stumbled across the fix while browsing other questions - specify the RV more fully:
App.get_running_app().root.get_screen('viewlist').ids.shoplist.data = ....
I'm going to leave the question up, though, because now I want to know why this RV needs to be specified so completely, while the one in the AddRecipe screen doesn't.

Kivy: how to update a label when a button is clicked

I use a button to retrieve the paths of some folders selected with the filechooser. When the button is clicked I would like to update the text of the label so that it dispays the selected paths.
In my Kv:
Button:
text:'OK'
on_press: root.selected(filechooser.path, filechooser.selection)
Label:
id: Lb_ListViewFolder
text: root.Lb_ListViewFolder_text
color: 0, 0, 0, 1
size_hint_x: .75
In .py:
class MyWidget(BoxLayout):
Lb_ListViewFolder_text = ObjectProperty("Text")
def selected(self, a, b):
global Lb_ListViewFolder_text
Lb_ListViewFolder_text = b
print(a,b)
This doesn't give me any error but the label text isn't changed.
I also tried self.ListViewFolder.text = b like recommended here but I get this error: MyWidget' object has no attribute 'Lb_ListViewFolder'.
I have seen this answer, but I have trouble applying in my code
I use python 3.6 and Kivy 1.9.2.dev0
In case, this is my entire code:
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.event import EventDispatcher
from kivy.lang import Builder
root = Builder.load_string('''
<MyWidget>
id: BL_Main
orientation: "horizontal"
padding: 10
spacing: 10
BoxLayout:
id: BL_folder
orientation: "vertical"
Button:
id:ok
text:'OK'
background_color: 0,0,1,1
height: 5
size_hint: 0.1, 0.1
on_press: root.selected(filechooser.path, filechooser.selection)
BoxLayout:
orientation:"horizontal"
size_hint: None, 0.9
width:150
canvas.before:
Color:
rgb: .4,.5,.5
Rectangle:
pos: self.pos
size: self.size
## multiple select folder not possible with FileChooserListView
FileChooserIconView:
id: filechooser
pos:self.pos
multiselect: True
dirselect: True
Label:
id: Lb_ListViewFolder
text: root.Lb_ListViewFolder_text
color: 0, 0, 0, 1
size_hint_x: .75
''')
class MyWidget(BoxLayout):
Lb_ListViewFolder_text = ObjectProperty("Text")
def selected(self, a, b):
global Lb_ListViewFolder_text
Lb_ListViewFolder_text = b
print(a,b)
class MyApp(App):
def build(self):
Window.clearcolor = (1, 1, 1, 1)
return MyWidget()
MyApp().run()
You can use StringProperty here:
from kivy.app import App
from kivy.uix.filechooser import FileChooserListView
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import StringProperty
Builder.load_string('''
<MyLayout>:
orientation: "vertical"
Label:
text: root.label_text
Button:
id:ok
text:'OK'
on_press: root.selected(filechooser.path, filechooser.selection)
FileChooserIconView:
id: filechooser
pos:self.pos
multiselect: True
dirselect: True
''')
class MyLayout(BoxLayout):
label_text = StringProperty("File name")
def selected(self, a, b):
self.label_text = b[0]
class MyApp(App):
def build(self):
return MyLayout()
MyApp().run()
Or you can change it directly in kvlang:
<MyLayout>:
orientation: "vertical"
Label:
id: dirlabel
text: root.label_text
Button:
id:ok
text:'OK'
on_press: dirlabel.text = filechooser.selection[0]
FileChooserIconView:
id: filechooser
pos:self.pos
multiselect: True
dirselect: True

Kivy app with .kv file doesn't display

I am trying to modify this example: https://github.com/inclement/kivycrashcourse/blob/master/video14-using_a_screenmanager/after.py to make it work with a .kv file. This is my myscreenmanager.py file:
from kivy.app import App
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
import time
import random
class FirstScreen(Screen):
pass
class SecondScreen(Screen):
pass
class ColourScreen(Screen):
colour = ListProperty([1., 0., 0., 1.])
class MyScreenManager(ScreenManager):
def new_colour_screen(self):
name = str(time.time())
s = ColourScreen(name=name,
colour=[random.random() for _ in range(3)] + [1])
self.add_widget(s)
self.current = name
class MyScreenManagerApp(App):
def build(self):
return MyScreenManager()
if __name__ == "__main__":
MyScreenManagerApp().run()
And this is my myscreenmanager.kv file:
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
MyScreenManager:
transition: FadeTransition()
FirstScreen:
SecondScreen:
<FirstScreen>:
name: 'first'
BoxLayout:
orientation: 'vertical'
Label:
text: 'first screen!'
font_size: 30
BoxLayout:
Button:
text: 'goto second screen'
font_size: 30
on_release: app.root.current = 'second'
Button:
text: 'get random colour screen'
font_size: 30
on_release: app.root.new_colour_screen()
<SecondScreen>:
name: 'second'
BoxLayout:
orientation: 'vertical'
Label:
text: 'second screen!'
font_size: 30
BoxLayout:
Button:
text: 'goto first screen'
font_size: 30
on_release: app.root.current = 'first'
Button:
text: 'get random colour screen'
font_size: 30
on_release: app.root.new_colour_screen()
<ColourScreen>:
BoxLayout:
orientation: 'vertical'
Label:
text: 'colour {:.2},{:.2},{:.2} screen'.format(*root.colour[:3])
font_size: 30
Widget:
canvas:
Color:
rgba: root.colour
Ellipse:
pos: self.pos
size: self.size
BoxLayout:
Button:
text: 'goto first screen'
font_size: 30
on_release: app.root.current = 'first'
Button:
text: 'get random colour screen'
font_size: 30
on_release: app.root.new_colour_screen()
After running the app nothing is displayed on the screen. No errors in console. Switching back to Builder.load_string displays the app as expected.
Found my mistake: when using a .kv file the root widget needs to be surrounded in <>, like this:
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
<MyScreenManager>:
transition: FadeTransition()
FirstScreen:
SecondScreen:
Not sure why the discrepancy between load_string and .kv files, but it works now.

Resources