Related
I'm trying to make a simple Python Kivy app (using a kv file for the layout) that I will use to keep inventory of my parts for work. I am very new to python and kivy, so I need a little bit of direction on this.
One of the basic functions will be to allow the user to input a new part on the "Add Part to Inventory" screen (one of four screens). The user input consists of four items (part name, serial number, on-hand quantity, and the minimum amount needed).
Once the user enters this info and presses the 'Submit' button, I would like that data to be appended to a new row in a google sheet that I'm using as a backend database using the Google Drive/Sheets API (which I was able to get working).
Most of the example apps that I've found online only have one screen, and are only dealing with one type of user input (usually text, whereas my input will be text and integers).
I'm confused as to where I need to put my ids in the kv file (I'm assuming under the AddPartWindow layout), how to call a function when a button is pressed that is not in the root class, how to store the user input into a list since I'm not sure what the ObjectProperty function is really doing, and finally, how to append that list to a new row in my google sheet.
I'm not looking for a cut and paste answer, just some direction would be nice as I have not been able to find a good reference for exactly what I'm trying to do. My main confusion lies in having multiple screens, and how to transfer data between the different screens and their respective classes. Should data only be funneled through a single root class? Or can each class be used to handle data coming from their respective screens (windows) in the app? What exactly is the ObjectProperty function doing? I find the kivy website to be a bit convoluted when describing the properties class.
Any help/direction woud be greatly appreciated.
Here is the main.py code:
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
import gspread
from oauth2client.service_account import ServiceAccountCredentials
scope = ['https://www.googleapis.com/auth/drive']
creds = ServiceAccountCredentials.from_json_keyfile_name('My_First_Project-3d753d98320e.json', scope)
client = gspread.authorize(creds)
iQue_sheet = client.open("InventoryBackend").sheet1
#root layout
class InventoryWindow(Screen):
pass
#Layout in question
class AddPartWindow(Screen):
#Is there anything else I need to do with these before saving into a list or dictionary?
part_name = ObjectProperty(None)
serial_number = ObjectProperty(None)
on_hand_cnt = ObjectProperty(None)
min_needed = ObjectProperty(None)
#This function should save the user input into a list, np, and then append to the google sheet iQue_sheet
#Wasn't sure if it should be a list or a dictionary.
#I'm assuming .text is type-casting each object to a string. Can this be used for numerical inputs?
def new_part(self):
np = [self.part_name.text, self.serial_number.text, self.on_hand_cnt.text, self.min_needed.text]
iQue_sheet.append(np)
class OnOrderWindow(Screen):
pass
class OrderFormWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
class InventoryApp(App):
def build(self):
#These are used to enable going back and forth between screens using buttons
sm = ScreenManager()
sm.add_widget(InventoryWindow(name='inv_window'))
sm.add_widget(OnOrderWindow(name='on_order_window'))
sm.add_widget(AddPartWindow(name='add_part_window'))
sm.add_widget(OrderFormWindow(name='order_form_window'))
return sm
if __name__ == "__main__":
InventoryApp().run()
Here is the my .kv file for the layout, the Add Part window is the last layout towards the end:
WindowManager:
InventoryWindow:
OnOrderWindow:
OrderFormWindow:
AddPartWindow:
<ItemLabel#Label>
font_size: '15sp'
halign: 'left'
valign: 'middle'
text_size: self.size
<ItemButton#Button>
pos_hint: {'center_x':0.5, 'center_y':0.5}
size_hint: 0.65, 0.2
<ItemButton2#Button>
pos_hint: {'center_x':0.5, 'center_y':0.5}
size_hint: 0.65, 0.2
on_release:
app.root.current = "order_form_window"
<InventoryWindow>:
name: "inv_window"
add_probe: add_probe
FloatLayout:
Label:
text: 'Inventory'
font_size: '25sp'
size_hint: (1, 0.17)
pos_hint: {'x': 0, 'y': 0.87}
GridLayout:
cols: 4
padding:[10, 65, 10, 10]
spacing: 5
BoxLayout:
orientation: 'vertical'
ItemLabel:
text:'iQue3: Probe/Tubing'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Add'
on_release:
root.new_part()
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Sub'
BoxLayout:
orientation: 'vertical'
ItemButton2:
text:'Order'
BoxLayout:
orientation: 'vertical'
ItemLabel:
text:'Gen2: Probe/Tubing'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Add'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Sub'
BoxLayout:
orientation: 'vertical'
ItemButton2:
text:'Order'
BoxLayout:
orientation: 'vertical'
ItemLabel:
text:'Beads'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Add'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Sub'
BoxLayout:
orientation: 'vertical'
ItemButton2:
text:'Order'
BoxLayout:
orientation: 'vertical'
ItemLabel:
text:'iQue3 Fluid Maint. Kit'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Add'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Sub'
BoxLayout:
orientation: 'vertical'
ItemButton2:
text:'Order'
BoxLayout:
orientation: 'vertical'
ItemLabel:
text:'Screener Fluid Maint. Kit'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Add'
BoxLayout:
orientation: 'vertical'
ItemButton:
text:'Sub'
BoxLayout:
orientation: 'vertical'
ItemButton2:
text:'Order'
BoxLayout:
orientation: 'vertical'
GridLayout:
cols: 2
size_hint: 1, 0.15
padding: [10, 0, 10, 10]
BoxLayout:
Button:
text: 'Add Part to Inventory'
font_size: '18sp'
size_hint: (1, 0.75)
on_release:
app.root.current = "add_part_window"
root.manager.transition.direction = "right"
Button:
text: 'On Order'
font_size: '18sp'
size_hint: (1, 0.75)
on_release:
app.root.current = "on_order_window"
root.manager.transition.direction = "left"
<OnOrderWindow>:
name: "on_order_window"
FloatLayout:
Label:
text: 'On Order'
font_size: '25sp'
size_hint: (1, 0.17)
pos_hint: {'x': 0, 'y': 0.87}
GridLayout:
cols: 2
size_int: 1, .15
padding:[10, 510, 10, 10]
Button:
text: "Inventory"
font_size: '18sp'
size_hint: (1, 0.6)
on_release:
app.root.current = "inv_window"
root.manager.transition.direction = "right"
Button:
text:"Add Part to Inventory"
size_hint: (1, 0.6)
font_size: '18sp'
on_release:
app.root.current = "add_part_window"
root.manager.transition.direction = "left"
<OrderFormWindow>
name: "order_form_window"
FloatLayout:
Label:
text: 'Order Form'
font_size: '25sp'
size_hint: (1, 0.17)
pos_hint: {'x': 0, 'y': 0.87}
GridLayout:
cols:2
padding: [50, 50, 50, 120]
ItemLabel:
text: "Part: "
ItemLabel:
text: "Populate Part Here"
ItemLabel:
text: "Serial Number: "
ItemLabel:
text: "Populate SN Here"
ItemLabel:
text: "On Hand: "
ItemLabel:
text: "Populate On Hand Count Here"
ItemLabel:
text: "Minimum Needed: "
ItemLabel:
text: "Populate Min Needed Here"
ItemLabel:
text: "Order Quantity"
TextInput:
halign: 'left'
valign: 'middle'
input_type: 'number'
input_filter: 'int'
multiline:False
GridLayout:
cols:2
size_hint: 1, 0.15
padding: [10, 0, 10, 10]
Button:
text:"Cancel"
on_release: app.root.current = "inv_window"
Button:
text:"Submit Order"
on_release: app.root.current = "on_order_window"
#This is the add part screen layout I'm referring to
<AddPartWindow>
name: "add_part_window"
#These are the id's I was referring to:
part_name: part_name
serial_number: serial_number
on_hand_cnt: on_hand_cnt
min_needed: min_needed
FloatLayout:
Label:
text: 'Add Part to Inventory'
font_size: '25sp'
size_hint: (1, 0.17)
pos_hint: {'x': 0, 'y': 0.86}
GridLayout:
cols:2
padding: [50, 100, 50, 120]
spacing: 5
ItemLabel:
text: "Name of Part: "
TextInput:
id: part_name
halign: 'left'
valign: 'middle'
multinline:False
ItemLabel:
text: "Serial Number: "
TextInput:
id: serial_number
halign: 'left'
valign: 'middle'
multiline:False
ItemLabel:
text: "How Many On Hand?: "
TextInput:
id: on_hand_cnt
halign: 'left'
valign: 'middle'
multinline:False
ItemLabel:
text: "Minimum Needed?: "
TextInput:
id: min_needed
halign: 'left'
valign: 'middle'
multiline:False
GridLayout:
cols:2
size_hint: 1, 0.15
padding: [10, 0, 10, 10]
Button:
text:"Cancel"
on_release:
app.root.current = "inv_window"
root.manager.transition.direction = "left"
#Here is the button I'm referring to, I realize that putting "root." in front of new_part() is not
#the correct thing to put, so it's a placeholder for now:
Button:
text:"Submit"
on_release:
root.new_part()
app.root.current = "inv_window"
root.manager.transition.direction = "left"
Here is a screen shot of the Add Part Screen
The current error I'm getting when I enter input into the four fields and click on the Submit button is this:
File "C:\Users\edr27\kivy_venv\lib\site-packages\kivy\uix\behaviors\button.py", line 179, in on_touch_up
self.dispatch('on_release')
File "kivy\_event.pyx", line 705, in kivy._event.EventDispatcher.dispatch
File "kivy\_event.pyx", line 1248, in kivy._event.EventObservers.dispatch
File "kivy\_event.pyx", line 1132, in kivy._event.EventObservers._dispatch
File "C:\Users\edr27\kivy_venv\lib\site-packages\kivy\lang\builder.py", line 57, in custom_callback
exec(__kvlang__.co_value, idmap)
File "C:\Users\edr27\PycharmProjects\pythonProject\inventory.kv", line 345, in <module>
root.new_part()
File "C:\Users\edr27\PycharmProjects\pythonProject\main.py", line 35, in new_part
np = [self.part_name.text, self.serial_number.text, self.on_hand_cnt.text, self.min_needed.text]
AttributeError: 'NoneType' object has no attribute 'text'
Edit:
Here is how I implemented Oussama's answer in order to get this to work, all I had to do was update the AddPartWindow class:
class AddPartWindow(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.np = []
def submit(self):
self.part_name = self.ids.part_name.text
self.serial_number = self.ids.serial_number.text
self.on_hand_cnt = self.ids.on_hand_cnt.text
self.min_needed = self.ids.min_needed.text
self.np = [self.part_name, self.serial_number, self.on_hand_cnt, self.min_needed]
iQue_sheet.append_row(self.np)
I also made this change in order to append to sheet 3 of the workbook instead of sheet 1:
iQue_sheet = client.open("InventoryBackend").get_worksheet(2)
this main.py feel free to import what u need. this a simple example of what u want to do
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.app import App
Builder.load_file('the.kv')
class fscreen(widget):
def __init__(self, **kwargs):
supper().__init__(**kwargs)
self.np = []
def submit(self):
self.part_name = self.ids.Pname.text
self.serial_number = self.ids.Snumber.text
self.on_hand_cnt = self.ids.onhabdcnt.text
self.min_needed = self.ids.minneeded.text
self.np = [self.part_name, self.serial_number, self.on_hand_cnt, self.min_needed]
iQue_sheet.append(self.np)
class secscreen(widget):
def __init__(self, **kwargs):
supper().__init__(**kwargs)
pass
class thscreen(widget):
def __init__(self, **kwargs):
supper().__init__(**kwargs)
pass
class theapp(App):
def build(self):
self.screenm = ScreenManager()
self.screen = Screen(name = "first screen")
screen.add_widget(self.fscreen)
self.screenm.add_widget(screen)
if __name__ = "__main__":
theapp.run()
this is the.kv file
<fscreen>
TextInput:
id: Pname
hint_text: 'partname'
TextInput:
id: Snumber
hint_text: 'serialnumber'
TextInput:
id: onhandcnt
hint_text: 'on hand cnt'
TextInput:
id: minneeded
hint_text: 'min nedded'
Button:
text: 'Submit'
on_press: root.submit()
I created a GUI based off this question while trying to teach myself how to use jsonstore. I don't have the reputation points to add a comment so I'm asking my question here. I think I have the basic idea down but for some reason I can't save the data to a json file. I get the following error:
AttributeError: 'NoneType' object has no attribute 'text'
I've tried following the documentation but I can't see anywhere where it would explain what I'm doing wrong.
main.py
from kivy.storage.jsonstore import JsonStore
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty
Window.clearcolor = (0.02745098, 0.074509804, 0.121568627, 1)
Window.size = (2000, 900)
class TitleScreen(Screen):
pass
class MainScreen(Screen):
pass
class CreateProfile(Screen):
First = ObjectProperty()
Middle = ObjectProperty()
Last = ObjectProperty()
def __init__(self, **kwargs):
super(CreateProfile, self).__init__(**kwargs)
self.store = JsonStore("bco.json")
self.load()
def save(self):
self.store.put('profile', first = self.label.text)
self.store.put('profile', middle = self.label.text)
self.store.put('profile', last = self.label.text)
def load(self):
try:
self.Last.text = self.store.get('profile')['score']
except KeyError:
pass
class CreatePacket(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("customwidget.kv")
class CustomWidgetApp(App):
def build(self):
return presentation
if __name__ == "__main__":
CustomWidgetApp().run()
customwidget.kv
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import hex kivy.utils.get_color_from_hex
#: import FocusBehaviors kivy.uix.behaviors.focus
ScreenManagement:
transition: FadeTransition()
TitleScreen:
MainScreen:
CreateProfile:
CreatePacket:
<MainLabel#Label>:
font_size:50
bold: True
size_hint_x: 1
size_hint_y: 1.85
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
<SubLabel#Label>:
font_size: 35
bold: True
halign: "center"
size_hint_x: 1
size_hint_y: 1.5
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
<OtherLabel#Label>:
font_size: 12
bold: True
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
text_size: self.size
<Button#Button>:
font_size: 20
bold: True
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
background_color: 0.02745098, 0.074509804, 0.121568627, .01
canvas.before:
Color:
rgba: 0.396078431, 0.803921569, 0.807843137, 1
Line:
width: 2
rectangle: self.x, self.y, self.width, self.height
on_press: self.background_color = (0.396078431, 0.803921569, 0.807843137, 1)
on_release: self.background_color = (0.02745098, 0.074509804, 0.121568627, .01)
# TODO: Create a focus behavior to "Tab" between widgets
<TextInput#TextInput>:
font_size: 12
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
padding_x: 10
padding_y: 10
focus_next: None
focus_previous: None
unfocus_on_touch: True
background_color: 0.02745098, 0.074509804, 0.121568627, .01
canvas.before:
Color:
rgba: 0.396078431, 0.803921569, 0.807843137, 1
Line:
width: 1
rectangle: self.x, self.y, self.width, self.height
<TitleScreen>:
id: "title"
FloatLayout:
MainLabel:
text: "Easy Button"
size_hint_x: 1
size_hint_y: 1.25
SubLabel:
text: 'Test'
size_hint_x: 1
size_hint_y: 1
Button:
text: "Click Here To Continue"
on_release: app.root.current = "main"
size_hint: (.75, .15)
pos_hint: {'x': .12, 'y': .2}
<MainScreen>:
name: "main"
MainLabel:
text: "Home Page"
BoxLayout:
Button:
on_release: app.root.current = "create_profile"
text: "Create Profile"
size_hint: (.5, .15)
Button:
on_release: app.root.current = "create_packet"
text: "Create Packet"
size_hint: (.5, .15)
<CreateProfile>:
name: "create_profile"
AnchorLayout:
anchor_x: 'center'
anchor_y: 'top'
MainLabel:
text: "Create Profile"
size_hint: (1, .15)
BoxLayout:
size_hint: (.95, .2)
pos_hint: {'x': 0, 'y': .85}
spacing: 10
padding: 10
halign: "left"
OtherLabel:
text: "First"
OtherLabel:
text: "Middle"
OtherLabel:
text: "Last"
BoxLayout:
size_hint: (.95, .07)
pos_hint: {'x': 0, 'y': .8}
spacing: 20
padding: 10
halign: "right"
text_size: self.size
TextInput:
id: 'First'
TextInput:
id: "Middle"
TextInput:
id: 'Last'
BoxLayout:
Button:
on_release: app.root.current = "main"
text: "back Home"
size_hint: (.5, .15)
Button:
on_release: root.save()
text: "Save Profile"
size_hint: (.5, .15)
<CreatePacket>:
name: "create_packet"
MainLabel:
text: "Select Packet"
FloatLayout:
Button:
on_release: app.root.current = "main"
text: "back Home"
size_hint: (1, .15)
Your code has several problems but the main one is that you do not understand how to expose any .kv widget to .py, one of the simplest ways is to use an ObjectProperty as you try to do but that property is not linked to the widget, I prefer to do the creation in the .kv for its simplicity.
On the other hand I recommend you avoid the abuse of try-except since it hides errors, the best thing is to verify.
Another error is that you are overwriting the value of profile in the .json, the idea is to save everything in one.
Considering the above, the solution is:
*.py
from kivy.app import App
from kivy.storage.jsonstore import JsonStore
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.core.window import Window
from kivy.clock import Clock
Window.clearcolor = (0.02745098, 0.074509804, 0.121568627, 1)
Window.size = (2000, 900)
class TitleScreen(Screen):
pass
class MainScreen(Screen):
pass
class CreateProfile(Screen):
def __init__(self, **kwargs):
super(CreateProfile, self).__init__(**kwargs)
self.store = JsonStore("bco.json")
Clock.schedule_once(lambda *args: self.load())
def save(self):
self.store.put('profile',
first = self.first.text,
middle = self.middle.text,
last = self.last.text)
def load(self):
if self.store.exists('profile'):
profile = self.store.get('profile')
v = [("first", self.first), ("middle", self.middle), ("last", self.last)]
for key, ti in v:
val = profile.get(key)
if val:
ti.text = val
class CreatePacket(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("customwidget.kv")
class CustomWidgetApp(App):
def build(self):
return presentation
if __name__ == "__main__":
CustomWidgetApp().run()
*.kv
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import hex kivy.utils.get_color_from_hex
#: import FocusBehaviors kivy.uix.behaviors.focus
ScreenManagement:
transition: FadeTransition()
TitleScreen:
MainScreen:
CreateProfile:
CreatePacket:
<MainLabel#Label>:
font_size:50
bold: True
size_hint_x: 1
size_hint_y: 1.85
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
<SubLabel#Label>:
font_size: 35
bold: True
halign: "center"
size_hint_x: 1
size_hint_y: 1.5
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
<OtherLabel#Label>:
font_size: 12
bold: True
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
text_size: self.size
<Button#Button>:
font_size: 20
bold: True
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
background_color: 0.02745098, 0.074509804, 0.121568627, .01
canvas.before:
Color:
rgba: 0.396078431, 0.803921569, 0.807843137, 1
Line:
width: 2
rectangle: self.x, self.y, self.width, self.height
on_press: self.background_color = (0.396078431, 0.803921569, 0.807843137, 1)
on_release: self.background_color = (0.02745098, 0.074509804, 0.121568627, .01)
# TODO: Create a focus behavior to "Tab" between widgets
<TextInput#TextInput>:
font_size: 12
color: 0.396078431, 0.803921569, 0.807843137, 1
font_name: '/home/jarren/PycharmProjects/BCO_Form_Filler/practice/pirulen rg.ttf'
padding_x: 10
padding_y: 10
focus_next: None
focus_previous: None
unfocus_on_touch: True
background_color: 0.02745098, 0.074509804, 0.121568627, .01
canvas.before:
Color:
rgba: 0.396078431, 0.803921569, 0.807843137, 1
Line:
width: 1
rectangle: self.x, self.y, self.width, self.height
<TitleScreen>:
id: "title"
FloatLayout:
MainLabel:
text: "Easy Button"
size_hint_x: 1
size_hint_y: 1.25
SubLabel:
text: 'Test'
size_hint_x: 1
size_hint_y: 1
Button:
text: "Click Here To Continue"
on_release: app.root.current = "main"
size_hint: (.75, .15)
pos_hint: {'x': .12, 'y': .2}
<MainScreen>:
name: "main"
MainLabel:
text: "Home Page"
BoxLayout:
Button:
on_release: app.root.current = "create_profile"
text: "Create Profile"
size_hint: (.5, .15)
Button:
on_release: app.root.current = "create_packet"
text: "Create Packet"
size_hint: (.5, .15)
<CreateProfile>:
name: "create_profile"
first: first
middle: middle
last: last
AnchorLayout:
anchor_x: 'center'
anchor_y: 'top'
MainLabel:
text: "Create Profile"
size_hint: (1, .15)
BoxLayout:
size_hint: (.95, .2)
pos_hint: {'x': 0, 'y': .85}
spacing: 10
padding: 10
halign: "left"
OtherLabel:
text: "First"
OtherLabel:
text: "Middle"
OtherLabel:
text: "Last"
BoxLayout:
size_hint: (.95, .07)
pos_hint: {'x': 0, 'y': .8}
spacing: 20
padding: 10
halign: "right"
text_size: self.size
TextInput:
id: first
TextInput:
id: middle
TextInput:
id: last
BoxLayout:
Button:
on_release: app.root.current = "main"
text: "back Home"
size_hint: (.5, .15)
Button:
on_release: root.save()
text: "Save Profile"
size_hint: (.5, .15)
<CreatePacket>:
name: "create_packet"
MainLabel:
text: "Select Packet"
FloatLayout:
Button:
on_release: app.root.current = "main"
text: "back Home"
size_hint: (1, .15)
I found a work around that still allows me to append information into a JSON by using TinyDB. Here is the updated code:
from kivy.app import App
from kivy.storage.jsonstore import JsonStore
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.core.window import Window
from tinydb import TinyDB, Query
from kivy.uix.listview import ListItemButton
Window.clearcolor = (0.02745098, 0.074509804, 0.121568627, 1)
Window.size = (2000, 900)
db = TinyDB('bcodb.json')
class ProfileListButton(ListItemButton):
pass
class TitleScreen(Screen):
pass
class MainScreen(Screen):
pass
class CreateProfile(Screen):
def __init__(self, **kwargs):
super(CreateProfile, self).__init__(**kwargs)
self.store = JsonStore("bcodb.json")
def save(self):
db.insert({'first': self.first.text, 'middle': self.middle.text, 'last': self.last.text})
def update(self):
db.update({'first': self.first.text, 'middle': self.middle.text, 'last': self.last.text})
class CreatePacket(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("customwidget2.kv")
class CustomWidgetApp(App):
def build(self):
return presentation
if __name__ == "__main__":
CustomWidgetApp().run()
It's pretty easy to figure out with a simple database-like program I'm trying to develop. The .kv file is the same.
I have two file test.py and test.kv .
i run test.py then shows show button.
When i click on show button then def abc call.Can someone tell me how to show array in dynamic label and value(Item1=5000.Item2=1000).
Item1 5000
Item2 1000
I am using array
arr = ({'Item1': 5000},{'Item2': 1000})
test.py
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty, NumericProperty
Window.clearcolor = (0.5, 0.5, 0.5, 1)
Window.size = (600, 600)
class Invoice(Screen):
def __init__(self, **kwargs):
super(Invoice, self).__init__(**kwargs)
def abc(self):
#fetching from database
arr = ({'Item1': 5000},{'Item2': 1000})
print(arr)
class Test(App):
def build(self):
self.root = Builder.load_file('test.kv')
return self.root
if __name__ == '__main__':
Test().run()
test.kv
<Button#Button>:
font_size: 15
font_name: 'Verdana'
size_hint_y:None
height: 30
<Label#Label>:
font_size: 15
font_name: 'Verdana'
size_hint_y:None
height: 30
Invoice:
BoxLayout:
orientation: "vertical"
padding : 15, 15
BoxLayout:
orientation: "vertical"
padding : 5, 5
size_hint: .6, None
pos_hint: {'x': .18,}
BoxLayout:
orientation: "horizontal"
padding : 5, 5
spacing: 10, 10
size: 800, 40
size_hint: 1, None
Button:
text: "Show"
size_hint_x: .05
spacing_x: 30
on_press:root.abc()
BoxLayout:
orientation: "horizontal"
size_hint: 1, 1
BoxLayout:
orientation: "vertical"
size_hint: .5, 1
padding : 0, 15
spacing: 10, 10
size: 500, 30
Button:
text: "Invoice"
text_size: self.size
halign: 'center'
valign: 'middle'
GridLayout:
cols: 2
#orientation: "horizontal"
padding : 5, 0
spacing: 10, 0
#size: 500, 30
size_hint: 1, 1
pos: self.pos
size: self.size
Label:
size_hint_x: .35
text: "Item1"
text_size: self.size
halign: 'left'
valign: 'middle'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
pos: self.pos
size: self.size
Label:
size_hint_x: .15
text: "5000"
text_size: self.size
halign: 'right'
valign: 'middle'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
pos: self.pos
size: self.size
In your abc() method you can create labels and add them to your layout. In order to do that, I made a few change to your code. I added an id to your GridLayout and changed your custom label class to MyLabel and added it to the py file, so that I could create them in Python. Here is the modified Python file:
from kivy.uix.label import Label
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
Window.clearcolor = (0.5, 0.5, 0.5, 1)
Window.size = (600, 600)
class MyLabel(Label):
pass
class Invoice(Screen):
def __init__(self, **kwargs):
super(Invoice, self).__init__(**kwargs)
def abc(self):
#fetching from database
arr = ({'Item1': 5000},{'Item2': 1000})
layout = self.ids['invoices']
for invoice in arr:
for key,val in invoice.items():
lab1 = MyLabel(text=str(key),size_hint_x=.35, halign='left' )
lab2 = MyLabel(text=str(val),size_hint_x=.15, halign='right' )
layout.add_widget(lab1)
layout.add_widget(lab2)
class Test(App):
def build(self):
self.root = Builder.load_file('test.kv')
return self.root
And changes to the kv file included changing Label to MyLabel, moving as much as possible to the MyLabel class, and removing your example labels:
<Button#Button>:
font_size: 15
font_name: 'Verdana'
size_hint_y:None
height: 30
<MyLabel>:
font_size: 15
font_name: 'Verdana'
size_hint_y:None
height: 30
text_size: self.size
valign: 'middle'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
pos: self.pos
size: self.size
Invoice:
BoxLayout:
orientation: "vertical"
padding : 15, 15
BoxLayout:
orientation: "vertical"
padding : 5, 5
size_hint: .6, None
pos_hint: {'x': .18,}
BoxLayout:
orientation: "horizontal"
padding : 5, 5
spacing: 10, 10
size: 800, 40
size_hint: 1, None
Button:
text: "Show"
size_hint_x: .05
spacing_x: 30
on_press:root.abc()
BoxLayout:
orientation: "horizontal"
size_hint: 1, 1
BoxLayout:
orientation: "vertical"
size_hint: .5, 1
padding : 0, 15
spacing: 10, 10
size: 500, 30
Button:
text: "Invoice"
text_size: self.size
halign: 'center'
valign: 'middle'
GridLayout:
id: invoices
cols: 2
#orientation: "horizontal"
padding : 5, 0
spacing: 10, 0
#size: 500, 30
size_hint: 1, 1
pos: self.pos
size: self.size
Although the option to iterate over the data and generate the widget dynamically is an option, the truth is that it is unbeatable in the long term. If you have structured information it is appropriate to use a design pattern and kivy offers to use a RecycleView for these cases, this implements the MVC pattern, so we just need to pass the data and establish a view where an appropriate adapter can be provided.
In your case it is enough to design a widget that is what is shown in each row:
<Item#GridLayout>:
cols: 2
text: "" # new property
value: 0 # new property
padding : 5, 0
spacing: 10, 0
Label:
size_hint_x: .35
text: root.text
halign: 'left'
valign: 'middle'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
pos: self.pos
size: self.size
Label:
size_hint_x: .15
text: str(root.value)
halign: 'right'
valign: 'middle'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
pos: self.pos
size: self.size
And then replace the GridLayout with the RecycleView:
RecycleView:
id: rv
viewclass: 'Item'
RecycleBoxLayout:
default_size: None, dp(30)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
And in the event of the button assign the data, in this case you must convert your data to a list of dictionaries where the fields will be the text and value attribute of Item:
def convert_data(data):
l = []
for item in data:
for key, value in item.items():
l.append({'text': key, 'value': value})
return l
class Invoice(Screen):
def abc(self):
#fetching from database
arr = ({'Item1': 5000},{'Item2': 1000})
# convert to [{'text': 'Item1', 'value': 5000}, {'text': 'Item2', 'value': 1000}]
self.rv.data = convert_data(arr)
Complete Code:
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
def convert_data(data):
l = []
for item in data:
for key, value in item.items():
l.append({'text': key, 'value': value})
return l
class Invoice(Screen):
def abc(self):
#fetching from database
arr = ({'Item1': 5000},{'Item2': 1000})
# convert to [{'text': 'Item1', 'value': 5000}, {'text': 'Item2', 'value': 1000}]
self.rv.data = convert_data(arr)
class MyApp(App):
def build(self):
return Builder.load_file('test.kv')
if __name__ == '__main__':
MyApp().run()
test.kv
<Button#Button>:
font_size: 15
size_hint_y:None
height: 30
<Label#Label>:
font_size: 15
size_hint_y:None
height: 30
<Item#GridLayout>:
cols: 2
text: ""
value: 0
padding : 5, 0
spacing: 10, 0
Label:
size_hint_x: .35
text: root.text
halign: 'left'
valign: 'middle'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
pos: self.pos
size: self.size
Label:
size_hint_x: .15
text: str(root.value)
halign: 'right'
valign: 'middle'
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
pos: self.pos
size: self.size
Invoice:
rv: rv
BoxLayout:
orientation: "vertical"
padding : 15, 15
BoxLayout:
orientation: "vertical"
padding : 5, 5
size_hint: .6, None
pos_hint: {'x': .18,}
BoxLayout:
orientation: "horizontal"
padding : 5, 5
spacing: 10, 10
size: 800, 40
size_hint: 1, None
Button:
text: "Show"
size_hint_x: .05
spacing_x: 30
on_press:root.abc()
BoxLayout:
orientation: "horizontal"
size_hint: 1, 1
BoxLayout:
orientation: "vertical"
size_hint: .5, 1
padding : 0, 15
spacing: 10, 10
size: 500, 30
Button:
text: "Invoice"
text_size: self.size
halign: 'center'
valign: 'middle'
BoxLayout:
RecycleView:
id: rv
viewclass: 'Item'
RecycleBoxLayout:
default_size: None, dp(30)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
I have two files demo.py and demo.kv
I have a button +Add More which add row dynamic.I am trying to add vertical scrollbar in dynamic row using ScrollView:.But its not working properly.
its mean when i add row in scrollview that row having extra space in scrollview i want add rows without any spacing.
ScrollView:
BoxLayout:
orientation: "horizontal"
size_hint_y: None
height: 500
Rows:
id: rows
demo.py
import kivy
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
Window.clearcolor = (0.5, 0.5, 0.5, 1)
Window.size = (500, 400)
class user(Screen):
def add_more(self):
self.ids.rows.add_row()
class Row(BoxLayout):
button_text = StringProperty("")
class Rows(BoxLayout):
orientation = "vertical"
row_count = 0
def __init__(self, **kwargs):
super(Rows, self).__init__(**kwargs)
self.add_row()
def add_row(self):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
class Test(App):
def build(self):
self.root = Builder.load_file('demo.kv')
return self.root
if __name__ == '__main__':
Test().run()
demo.kv
<Button#Button>:
font_size: 15
font_name: 'Verdana'
<Label#Label>:
font_size: 15
font_name: 'Verdana'
<TextInput#TextInput>:
font_size: 15
font_name: 'Verdana'
padding_y: 3
<Row>:
GridLayout:
cols: 2
row_force_default: True
row_default_height: 40
Button:
text: root.button_text
size_hint_x: None
top: 200
Button:
text: 'World 1'
width: 300
user:
BoxLayout:
orientation: "vertical"
padding : 20, 5
BoxLayout:
orientation: "horizontal"
#padding : 10, 10
spacing: 10, 10
size: 450, 40
size_hint: None, None
Label:
size_hint_x: .2
text: "Test 1"
text_size: self.size
valign: 'bottom'
halign: 'center'
Label:
size_hint_x: .8
text: "Test 2"
text_size: self.size
valign: 'bottom'
halign: 'center'
ScrollView:
BoxLayout:
orientation: "horizontal"
size_hint_y: None
height: 500
Rows:
id: rows
BoxLayout:
orientation: "horizontal"
size_hint_x: .2
size_hint_y: .2
Button:
text: "+Add More"
on_press: root.add_more()
BoxLayout:
orientation: "horizontal"
padding : 10, 5
spacing: 10, 10
size_hint: .5, .35
pos_hint: {'x': .25, 'y':.25}
Button:
text: 'Ok'
Button:
text: 'Cancel'
any help would be greatly appreciated.
If I have understood what you want, you have too many nested Layouts, that are unnecessary. Rows should be the main layout of your ScrollView.
On the other hand, Rows should always have the lowest possible height to contain their widgets (minimun_height property), not a fixed size.
Demo.py:
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
Window.clearcolor = (0.5, 0.5, 0.5, 1)
Window.size = (500, 400)
class User(Screen):
def add_more(self):
self.ids.rows.add_row()
class Row(BoxLayout):
button_text = StringProperty("")
class Rows(BoxLayout):
row_count = 0
def __init__(self, **kwargs):
super(Rows, self).__init__(**kwargs)
self.add_row()
def add_row(self):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
class Test(App):
def build(self):
self.root = Builder.load_file('Demo.kv')
return self.root
if __name__ == '__main__':
Test().run()
Demo.kv:
<Button#Button>:
font_size: 15
font_name: 'Verdana'
<Label#Label>:
font_size: 15
font_name: 'Verdana'
<TextInput#TextInput>:
font_size: 15
font_name: 'Verdana'
padding_y: 3
<Row>:
size_hint_y: None
height: self.minimum_height
height: 40
Button:
text: root.button_text
size_hint_x: None
top: 200
Button:
text: 'World 1'
width: 300
<Rows>:
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
User:
BoxLayout:
orientation: "vertical"
padding : 20, 5
BoxLayout:
orientation: "horizontal"
#padding : 10, 10
spacing: 10, 10
size: 450, 40
size_hint: None, None
Label:
size_hint_x: .2
text: "Test 1"
text_size: self.size
valign: 'bottom'
halign: 'center'
Label:
size_hint_x: .8
text: "Test 2"
text_size: self.size
valign: 'bottom'
halign: 'center'
ScrollView:
Rows:
id: rows
BoxLayout:
orientation: "horizontal"
size_hint_x: .2
size_hint_y: .2
Button:
text: "+Add More"
on_press: root.add_more()
BoxLayout:
orientation: "horizontal"
padding : 10, 5
spacing: 10, 10
size_hint: .5, .35
pos_hint: {'x': .25, 'y':.25}
Button:
text: 'Ok'
Button:
text: 'Cancel'
Can anyone tell me?
I have two file test.py and test.kv.
I have a textBox which id : state . How to pass state.text value in root.get_value(state.text) and get value in function of def get_value
test.py
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty, NumericProperty
Window.size = (450, 525)
class display(Screen):
state = ObjectProperty(None)
def add_more(self):
self.ids.rows.add_row()
def get_value(self,arg1):
print(arg1)
class Row(BoxLayout):
button_text = StringProperty("")
state = ObjectProperty(None)
class Rows(BoxLayout):
orientation = "vertical"
row_count = 0
def __init__(self, **kwargs):
super(Rows, self).__init__(**kwargs)
self.add_row()
def add_row(self):
self.row_count += 1
self.add_widget(Row(button_text=str(self.row_count)))
class test(App):
def build(self):
self.root = Builder.load_file('test.kv')
return self.root
if __name__ == '__main__':
test().run()
test.kv
<Row>:
state : state
orientation: "horizontal"
spacing: 0, 5
Button:
text: root.button_text
size_hint_x: .2
TextInput:
text : "Test1"
size_hint_x: .8
id : state
display:
BoxLayout:
orientation: "vertical"
padding : 20, 20
BoxLayout:
orientation: "horizontal"
Button:
size_hint_x: .2
text: "+Add More"
valign: 'bottom'
on_press: root.add_more()
BoxLayout:
orientation: "horizontal"
Label:
size_hint_x: .2
text: "SN"
valign: 'bottom'
Label:
size_hint_x: .8
text: "Value"
valign: 'bottom'
Rows:
id: rows
BoxLayout:
orientation: "horizontal"
padding : 10, 0
spacing: 10, 10
size_hint: .5, .7
pos_hint: {'x': .25, 'y':.25}
Button:
text: 'Ok'
on_release:
root.get_value(state.text)
Button:
text: 'Cancel'
on_release: root.dismiss()