How to refresh GridLayout in Kivy with kv file - python

I need help.
I created a small mobile application with Kivy.
I have two screens: ScreenList and ScreenDetail.
However the screen(ScreenList) containing GridLayout does not refresh
ScreenList: contains a list of items
ScreenDetail: Contains the details of a single item.
How the app works:
When I click on the first item on button 1
I go to the details of the item.
I modify the second field. I replace the text: Firt element for First and update data
After recording, I redirect the application to the screens which contain (ScreenList) the list of elements.
But the list of elements remains unchanged then the data has been modified in the database.
6.And when I return to the screen (ScreenDetail) which contains the details, there I see that the data is updated.
How can I refresh the item list in ScreenList?
Here are the pictures as an example
List before update
before update
after update
List after update
Here is the python code:
import kivy
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.properties import ObjectProperty, StringProperty
from kivy.lang import Builder
from kivymd.uix.picker import MDTimePicker
from kivymd.uix.picker import MDDatePicker
from kivymd.app import MDApp
import sqlite3
import os.path
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
db_path = os.path.join(BASE_DIR, "donnee/ProjetMaison.db")
def donnee_dic(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
cur_id = None
class ScreenList(Screen):
data_grid = ObjectProperty()
def go_to_detail(self, instance):
global cur_id
cur_id = int(instance.text)
self.manager.current = 'screen_detail'
def __init__(self, **kwargs):
super(ScreenList, self).__init__(**kwargs)
con_list_course = sqlite3.connect(db_path)
con_list_course.row_factory = donnee_dic
curss_list_course = con_list_course.cursor()
data_list_course = curss_list_course.execute("select * FROM courses")
self.data_grid.bind(minimum_height=self.data_grid.setter('height'))
for row in data_list_course:
template_screen = GridLayout(cols=2, size_hint_y=None, height=40)
template_screen.add_widget(Label(text=row['nom']))
template_screen.add_widget(Button(text=str(row['id']), on_press=self.go_to_detail))
self.data_grid.add_widget(template_screen)
con_list_course.close()
def Maj_colonne(id, nom):
try:
sqliteConnection = sqlite3.connect(db_path)
cursor = sqliteConnection.cursor()
sqlite_update_query = """Update courses set nom = ? where id = ?"""
columnValues = (nom, id)
cursor.execute(sqlite_update_query, columnValues)
sqliteConnection.commit()
sqliteConnection.commit()
cursor.close()
except sqlite3.Error as error:
print("Erreur de connexion", error)
finally:
if sqliteConnection:
sqliteConnection.close()
class ScreenDetail(Screen):
label_id = ObjectProperty()
label_nom = ObjectProperty()
def __init__(self, **kwargs):
super(ScreenDetail, self).__init__(**kwargs)
def on_enter(self):
global cur_id
conn = sqlite3.connect(db_path)
cursor = conn.execute("select * FROM courses where id=?",str(cur_id))
for row in cursor:
self.label_id.text = str(row[0])
self.label_nom.text = str(row[1])
cursor.close()
def update_course(self):
id_pk = self.ids['label_id'].text
nom = self.ids['label_nom'].text
if id_pk:
Maj_colonne(str(id_pk), str(nom))
self.ids['label_id'].text = ''
self.ids['label_nom'].text = ''
self.manager.current = 'screen_list'
class Listapp(MDApp):
def build(self):
screenmanager = ScreenManager()
screenmanager.add_widget(ScreenList(name='screen_list'))
screenmanager.add_widget(ScreenDetail(name='screen_detail'))
return screenmanager
if __name__ == '__main__':
Listapp().run()
Here is the kv code:
#:import utils kivy.utils
<ScreenList>:
data_grid: data_grid
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.primary_color
radius: [25, 0, 0, 0]
ScrollView:
MDGridLayout:
id: data_grid
cols: 1
spacing:10
size_hint_y:None
<ScreenDetail>:
label_id: label_id
label_nom: label_nom
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.primary_color
ScrollView:
GridLayout:
id: detail_grid
cols:2
Label:
text: 'Numéro:'
bold: True
TextInput:
id: label_id
text: ''
Label:
text: 'Nom:'
bold: True
TextInput:
id: label_nom
text: ''
Button:
text: 'OK'
size_hint_y: None
on_press: root.update_course()
Thank you

Few notes to take, in general, when working with Kivy
When you're trying to share data in between screens, it's often useful to use app methods instead of specific methods of screens.
And when you need to create lots of buttons, maybe inside a loop, and bind methods on its events( on_press, on_release), it's often bad to create button instances on the fly and bind methods on its events because you'll need to do extra work to make sure that those bound methods are called with right parameters when events are fired. Rather create a custom class template and use that instead.
Working solution for your problem (only showing sections that has been added/updated
Created custom GridLayout:
class MyGrid(GridLayout):
pass
Updated __init__ method inside ScreenList:
def __init__(self, **kwargs):
#...
app = MDApp.get_running_app()
for row in data_list_course:
template_screen = MyGrid()
template_screen.ids.lbl.text = row['nom']
template_screen.ids.btn.text = str(row['id'])
self.data_grid.add_widget(template_screen)
Added methods inside app class:
class Listapp(MDApp):
def build(self):
self.screenmanager = ScreenManager()
self.screenmanager.add_widget(ScreenList(name='screen_list'))
self.screenmanager.add_widget(ScreenDetail(name='screen_detail'))
return self.screenmanager
def go_to_detail_(self, inst):
self.inst = inst
self.screenmanager.current = 'screen_detail'
def update_course_(self, label_id, label_nom):
c = self.inst.children[::-1]
c[0].text = label_nom.text
c[1].text = label_id.text
print(label_id.text, label_nom.text)
if label_id.text:
Maj_colonne(str(id_pk), str(nom))
label_nom.text = ''
label_id.text = ''
self.screenmanager.current = 'screen_list'
Here's the updated kv code:
#:import utils kivy.utils
<ScreenList>:
data_grid: data_grid
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.primary_color
radius: [25, 0, 0, 0]
ScrollView:
MDGridLayout:
id: data_grid
cols: 1
spacing:10
size_hint_y:None
<ScreenDetail>:
label_id: label_id
label_nom: label_nom
MDBoxLayout:
orientation: 'vertical'
md_bg_color: app.theme_cls.primary_color
ScrollView:
GridLayout:
id: detail_grid
cols:2
Label:
text: 'Numéro:'
bold: True
TextInput:
id: label_id
text: ''
Label:
text: 'Nom:'
bold: True
TextInput:
id: label_nom
text: ''
Button:
text: 'OK'
size_hint_y: None
on_press: app.update_course_(label_id, label_nom)
<MyGrid>:
cols:2
size_hint_y:None
height:40
Label:
id: lbl
text: ''
Button:
id: btn
text: ''
on_press:
app.go_to_detail_(root)

Related

How to access a specific content with ids on kivy

my problem is that I am doing recycle view of async Images that have a special url. I am loading them thanks to a recycle view. My problem is that I want to print the source of the async image thanks to an app function, on another screen. All I did, didn"t work. The idea is only to recup the source of the async image and to show it on UserInfo screen. Only that. Nb : (The images are taken on an API server.)
Thanks !
The code :
class ReviewinApp(MDApp):
new_dialog = None
notdialog = None
dialog =None
dialoge = None
asyncimage = None
doc = None
data = ListProperty()
id_ = ListProperty()
def build(self):
dialog = None
self.title = "ReviewinApp"
global sm
sm = ScreenManager()
sm.add_widget(Builder.load_file("splash.kv"))
sm.add_widget(Builder.load_file("accueil.kv"))
sm.add_widget(Builder.load_file('conditions.kv'))
sm.add_widget(Builder.load_file("register.kv"))
sm.add_widget(Builder.load_file("faq.kv"))
sm.add_widget(Builder.load_file("login.kv"))
sm.add_widget(Builder.load_file("User2.kv"))
sm.add_widget(Builder.load_file("rules.kv"))
sm.add_widget(Builder.load_file("commentinput.kv"))
sm.add_widget(Builder.load_file("UserInfo.kv"))
sm.add_widget(Builder.load_file('test.kv'))
return sm
def load_products(self):
token = self.token
json = {"token":token}
res = requests.post('http://127.0.0.1:2223/products/list', json=json)
self.doc = res.json()
self.data = [{'source': 'http://127.0.0.1:2223/products/' + str(self.doc[i].replace('.png', ''))} for i in range(len(self.doc))]
def en(self):
app = App.get_running_app()
print(app.data)
```
MDScreen:
name: 'test'
md_bg_color: 41/255, 34/255, 34/255, 1
on_enter: app.load_products()
image1: ""
RecycleView:
viewclass: "OwnWidget"
data: app.data
RecycleBoxLayout:
default_size: None, dp(300)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(20)
<OwnWidget#BoxLayout>:
id: recycle
orientation: 'vertical'
text: ''
source:''
MDIconButton:
icon: 'arrow-left'
icon_color: 'white'
theme_icon_color: 'Custom'
pos_hint: {'center_x':0.3, 'center_y': 0.1}
size_hint_x: 0.3
height: dp(40)
on_press:
app.reload_datas()
app.root.current = "UserInfo"
print(product.source)
AsyncImage:
id: product
source: root.source
```
MDScreen:
name: "UserInfo"
on_enter: app.en()
MDFloatLayout:
md_bg_color: 41/255, 34/255, 34/255, 1
MDLabel:
id: test_text
text: ""
```
All you need to access objects in a different screen is the .get screen() method, the name of the screen, object id, and object property.
This family of question comes up a lot with kivy, so I have put together a single-file runnable example that you can hopefully get some ideas from.
Regarding the specific question about linking an id; the ids in the .kv code are not available directly in Python but one can make a cross-reference at the top of the kivy object. Here are 4 kivy object ids being connected to Python objects of the same name
<MainScreen>:
# python names on the left: kivy id on the right
box_layout_a: box_layout_a
box_layout_b: box_layout_b
my_button: my_button
my_text_input: my_text_input
and here is the reference to the Python objects in the class definition. I like using the type-hints because the IDE can help with auto completion and type checking.
class MainScreen(BoxLayout):
# python names on the left: kivy id on the right
box_layout_a: BoxLayout
box_layout_b: BoxLayout
my_button: Button
my_string_property = StringProperty()
Runnable code:
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from functools import partial
Builder.load_string('''
<MainScreen>:
# python names on the left: kivy id on the right
box_layout_a: box_layout_a
box_layout_b: box_layout_b
my_button: my_button
my_text_input: my_text_input
orientation: "vertical"
BoxLayout:
id: box_layout_a
orientation: "vertical"
Button:
text: "button connected to widget"
on_press: root.kv_button(1)
Button:
text: "button connected to app, increments a counter"
on_press: app.kv_app_button("method in the top level app")
BoxLayout:
id: box_layout_b
orientation: "horizontal"
Button:
id: my_button
Label:
id: my_text_input
text: root.my_string_property
Label:
text: app.string_property_bound_in_app
'''
)
class MainScreen(BoxLayout):
# python names on the left: kivy id on the right
box_layout_a: BoxLayout
box_layout_b: BoxLayout
my_button: Button
my_string_property = StringProperty()
def function_to_bind(self, my_argument, *args, **kwargs):
print(f"{self}bound function {my_argument} {args}")
def __init__(self, **kwargs):
super().__init__(**kwargs)
# often you can define the buttons and on_press method in the kivy file
# but sometimes there are a lot of buttons, and it could makse sense to create them dynamically
_late_button = Button()
_late_button.text = "added programatically"
_late_button.bind(on_press=partial(self.function_to_bind, "custom parameter sent"))
self.box_layout_a.add_widget(_late_button)
def kv_button(self, _input, *args) -> None:
print(f"{self} input {_input}, {args}")
# This is a little strange, but down here in the widget, we don't have a reference to the App object,
# however, it can be obtained
App.get_running_app().string_property_bound_in_app = "hi"
class KivyDemo(App):
string_property_bound_in_app = StringProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.count = 0
# keep a reference to the main screen object
self.main_box_layout: MainScreen = MainScreen()
def kv_app_button(self, _input, *args) -> None:
self.count += 1
self.main_box_layout.my_button.text = f"{self.count}"
self.main_box_layout.my_string_property = f"bound string property {self.count + 1}"
print(f"{self} input: {_input}, {args}")
def build(self) -> MainScreen:
return self.main_box_layout
def on_start(self):
self.main_box_layout.my_string_property = "initial text"
self.string_property_bound_in_app = "!"
test: KivyDemo = KivyDemo()
test.run()

How can display data from the database to kivy table

I want to display a table in one of the screen and the displayed data in the table should come from the loan database, loan table.
I have created the database and also there is no error which is being shown. The table is created but there is no data displayed on it.Its empty table.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
import json
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.popup import Popup
from loandatabase import Database
database = Database()
Builder.load_file('design.kv')
class MainScreen(Screen):
def goto_admin_page(self):
self.manager.current = "adminfirst_screen"
def goto_agent_page(self):
pass
def goto_customer_page(self):
pass
class AdminScreenFirst(Screen):
def go_to_adminsecond(self,uname,pword):
with open("users.json") as file:
users =json.load(file)
if uname in users and users[uname]['password'] == pword:
self.manager.current = "adminsecond_screen"
else:
self.ids.login_wrong.text = "Invalid Credentials.Please Contact the administrator"
class AdminScreenSecond(Screen):
def go_to_adminpending(self):
self.manager.current = "adminPending_screen"
def go_to_adminapproved(self):
self.manager.current = "adminsecond_screen"
def go_to_adminrejected(self):
self.manager.current = "adminsecond_screen"
class AdminPendingScreen(Screen):
def display(self):
self.manager.current = "rv_screen"
class RV(Screen):
data_items = ListProperty([])
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
for row in database.view():
for col in row:
self.data_items.append(col)
class TextInputPopup(Popup):
obj = ObjectProperty(None)
obj_text = StringProperty("")
def __init__(self, obj, **kwargs):
super(TextInputPopup, self).__init__(**kwargs)
self.obj = obj
self.obj_text = obj.text
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableButton(RecycleDataViewBehavior, Button):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableButton, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableButton, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
def on_press(self):
popup = TextInputPopup(self)
popup.open()
def update_changes(self, txt):
self.text = txt
class RootWidget(ScreenManager):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == "__main__":
MainApp().run()
my kivy code :
<MainScreen>:
GridLayout:
cols:1
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Login"
font_size:"20sp"
Button:
text:"Admin"
on_press : root.goto_admin_page()
Button:
text:"Agent"
on_press : root.goto_agent_page()
Button:
text: "Customer"
on_press:root.goto_customer_page()
<AdminScreenFirst>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Admin Login"
font_size:"20sp"
TextInput:
id:username
hint_text:"Username"
TextInput:
id:password
password:True
hint_text:"Password"
RelativeLayout:
Button:
text:"Login"
on_press : root.go_to_adminsecond(root.ids.username.text,root.ids.password.text)
size_hint :0.3,0.5
pos_hint: {'center_x' : 0.5 , 'center_y' : 0.6}
Label:
id:login_wrong
text:""
<AdminScreenSecond>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Loan"
font_size:"20sp"
Button:
text:"Pending Request"
on_press:root.go_to_adminpending()
Button:
text:"Approved"
on_press: root.go_to_adminapproved()
Button:
text:"Rejected"
on_press: root.go_to_adminrejected()
<AdminPendingScreen>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Loans"
Button:
text:"Display"
on_press: root.display()
Label:
id:display
text:""
<TextInputPopup>:
title: "Popup"
size_hint: None, None
size: 400, 400
auto_dismiss: False
BoxLayout:
orientation: "vertical"
TextInput:
id: txtinput
text: root.obj_text
Button:
size_hint: 1, 0.2
text: "Save Changes"
on_release:
root.obj.update_changes(txtinput.text)
root.dismiss()
Button:
size_hint: 1, 0.2
text: "Cancel Changes"
on_release: root.dismiss()
<RV>:
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 11
Label:
text: "LoanID"
Label:
text: "Name"
Label:
text:"Tenure"
Label:
text:"Balance"
Label:
text:"LoanType"
Label:
text:"InterestType"
Label:
text:"Interest % p.a."
Label:
text:"Security"
Label:
text:"Total"
Label:
text:"EMI"
Label:
text:"Request"
BoxLayout:
RecycleView:
viewclass: 'SelectableButton'
data: [{'text': str(x)} for x in root.data_items]
SelectableRecycleGridLayout:
cols: 11
default_size: None, dp(26)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
#orientation: 'vertical'
multiselect: True
touch_multiselect: True
<RootWidget>:
MainScreen:
name:"mainfirst_screen"
AdminScreenFirst:
name:"adminfirst_screen"
AdminScreenSecond:
name:"adminsecond_screen"
AdminPendingScreen:
name:"adminPending_screen"
RV:
name:"rv_screen"
my database code:
import sqlite3
class Database:
def __init__(self):
self.conn = sqlite3.connect("loandatabase.db")
self.cur = self.conn.cursor()
self.cur.execute("CREATE TABLE IF NOT EXISTS loan (id INTEGER PRIMARY KEY, customer_name TEXT, tenure integer, balance integer, loantype TEXT, interesttype TEXT,interest integer,security TEXT,totalpayment integer,emi integer,instruction TEXT)")
self.conn.commit()
def insert(self,cname,tenure,blance,ltype,itype,interest,security,tpay,emi,instr):
self.cur.execute("INSERT INTO loan VALUES (NULL,?,?,?,?,?,?,?,?,?,?)",(cname,tenure,blance,ltype,itype,interest,security,tpay,emi,instr))
self.conn.commit()
def view(self):
self.cur.execute("SELECT * FROM loan")
rows = self.cur.fetchall()
return rows
def __del__(self):
self.conn.close()
[![enter image description here][1]][1]
I would request you to help me with it. Thank You in Advance
I am using Kivy,Python,sqlite3
This is displayed:
[1]: https://i.stack.imgur.com/fkUvS.png
Try this:
class MainApp(App):
title = 'Title of the app'
def build(self):
sm = ScreenManager('''You can add transition here''')
sm.add_widget(MainScreen(name='main'))
sm.add_widget(AdminScreenFirst(name='admin1'))
sm.add_widget(AdminScreenSecond(name='admin2'))
sm.add_widget(AdminPendingScreen(name='pending'))
sm.add_widget(RV(name='rv'))
return sm

Kivy: How to add widgets dinamically in kv, and how to refer to them from py script?

In a previous question, I asked how to have a row of text inputs which is dinamically added on press of a button, all from a py script.
I am now attempting at moving all my layout code to a kv file. While this was pretty straightforward for the widgets which appear on screen by default, I am not really sure of how to define the dinamically added text inputs from the kv file.
My solution, at present, is to create the 'default' widgets in the kv file, and to add the other ones from the py script through the addIngredient method. Below a minimal working version.
The kv file:
WMan:
AddWindow:
<AddWindow>:
name: 'add'
ingsGrid: ingsGrid
ing1: ing1
quant1: quant1
addIng: addIng
saveButton: saveButton
StackLayout:
id: ingsGrid
size_hint: .9, None
height: self.minimum_height
orientation: 'lr-tb'
spacing: '5sp', '5sp'
TextInput:
id: ing1
multiline: False
size_hint: .65, None
height: self.minimum_height
TextInput:
id: quant1
multiline: False
size_hint: .25, None
height: self.minimum_height
Button:
id: addIng
text: "+"
size_hint: .1, None
height: ing1.height
on_release: root.addIngredient(self)
Button:
id: saveButton
text: "Save"
size_hint: .3, None
on_release:
root.saveRec(self)
The py script reads:
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
recipeName = ObjectProperty(None)
ingsGrid = ObjectProperty(None)
ing1 = ObjectProperty(None)
quant1 = ObjectProperty(None)
self.i = 1
self.ingsList = {}
self.ingsList[0] = ing1
self.quants = {}
self.quants[0] = quant1
def addIngredient(self, instance):
tmp_children_list = self.ingsGrid.children[:]
self.ingsGrid.clear_widgets()
# range(start,stop[, step])
for index in range(len(tmp_children_list)-1, -1, -1):
# the last item, then last-1, etc
child = tmp_children_list[index]
# add the last first (new ones will be added on top)
self.ingsGrid.add_widget(child)
# if child is the pressed button
if child == instance:
self.ing = TextInput(
size_hint=(.65, None),
height='30sp')
self.ingsGrid.add_widget(self.ing)
self.ingsList[self.i] = self.ing
self.quant = TextInput(
size_hint=(0.25, None),
height='30sp')
self.ingsGrid.add_widget(self.quant)
self.quants[self.i] = self.quant
self.i += 1
self.addNext = Button(
text="+",
size_hint=(0.1, None),
height='30sp')
self.addNext.bind(on_press=self.addIngredient)
self.ingsGrid.add_widget(self.addNext)
def saveRec(self, instance): # grab all inputs and send to SQLite db
print(self.ingsList)
print(self.ingsList[0].text)
print(self.ingsList[1].text)
class WMan(ScreenManager):
pass
kv = Builder.load_file("test.kv")
class TestApp(App):
def build(self):
return kv
if __name__ == "__main__":
TestApp().run()
My problem here is twofold: first, while this way of dinamically adding rows works as it should, it is to me a bit messy to have half of the layout defined on the kv file, and the other half defined in the py script. So my first question is:
1. Is there a way to move the entire layout to the kv file?
Second questions is:
2. How do I access the content of textInput 'ing1' (the one created in the kv file)?
when I run print(self.ingsList), I get:
{0: <ObjectProperty name=>, 1: <kivy.uix.textinput.TextInput object at 0x000002077FB89C88>}
So while I can easily do print(self.ingsList[1].text), running print(self.ingsList[0].text) will give error:
AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
Here is a modified version of your code that does what I think you want:
from kivy.app import App
from kivy.properties import ObjectProperty, NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
recipeName = ObjectProperty(None)
ingsGrid = ObjectProperty(None)
ing1 = ObjectProperty(None)
quant1 = ObjectProperty(None)
self.i = 1
self.ingsList = {}
self.ingsList[0] = ing1
self.quants = {}
self.quants[0] = quant1
def saveRec(self, instance): # grab all inputs and send to SQLite db
for child in self.ids.ingsGrid.children:
if isinstance(child, OneRow):
print('ingedient name:', child.ids.ing1.text)
print('quantity:', child.ids.quant1.text)
print('')
class WMan(ScreenManager):
pass
class OneRow(BoxLayout):
inst_count = NumericProperty(0)
count = 0
def __init__(self, **kwargs):
OneRow.count += 1
self.inst_count = OneRow.count
super(OneRow, self).__init__(**kwargs)
def get_index(self):
par = self.parent
if par is None:
return None
index = 0
for index in range(len(par.children) - 1, -1, -1):
child = par.children[index]
if child == self:
return index
kv_str = '''
#:import Factory kivy.factory.Factory
WMan:
AddWindow:
id: add
#:set row_height 30
<OneRow>:
orientation: 'horizontal'
size_hint_y: None
height: row_height
TextInput:
id: ing1
multiline: False
size_hint: .65, None
height: row_height
TextInput:
id: quant1
multiline: False
text: str(root.inst_count)
size_hint: .25, None
height: row_height
Button:
id: addIng
text: "+"
size_hint: .1, None
height: row_height
on_release: app.root.ids.add.ids.ingsGrid.add_widget(Factory.OneRow(), index=root.get_index())
<AddWindow>:
name: 'add'
ingsGrid: ingsGrid
saveButton: saveButton
StackLayout:
id: ingsGrid
size_hint: .9, None
height: self.minimum_height
orientation: 'lr-tb'
spacing: '5sp', '5sp'
OneRow:
Button:
id: saveButton
text: "Save"
size_hint: .3, None
on_release:
root.saveRec(self)
'''
# kv = Builder.load_file("test.kv")
kv = Builder.load_string(kv_str)
class TestApp(App):
def build(self):
return kv
if __name__ == "__main__":
TestApp().run()
I used Builder.load_string() instead of load_file() just for my own convenience.
I have created a class named OneRow that holds a single row from the ingredients list, and the + Button now just adds an instance of that class. The get_index() method of that class is only used to position new ingredients below the row with the Button that was pressed. And the other code in that class is just to add some identifying info. If those things are not important to you, you can eliminate the OneRow class definition from the python, and just replace <OneRow>: in the kv with <OneRow#BoxLayout>: and where the OneRow is added you can just set index=1.

Changing widget from another screen in Kivy

I'm trying to update one widget in through a button in another view at Kivy.
Kivy code:
<MainScreen>:
relatorios : relatorios
despesas : despesas
GridLayout:
size: root.width, root.height
cols : 1
Button:
id: relatorios
text: 'Relatorios'
on_press: root.change_screen(rep = 'Report')
Button:
id: despesas
text: 'Despesa'
on_press: root.change_screen(rep = 'Expend')
<ReportScreen>:
GridLayout:
size: root.width, root.height
cols : 1
Button:
id: semanal
text: 'Semanal'
Button:
id: mensal
text: 'Mensal'
Button:
id: anual
text: 'Anual'
Button:
id: voltar
text: 'Voltar'
on_press: root.change_screen(rep = 'Main')
<ExpendScreen>:
kind_selec1 : kind_select
GridLayout:
size: root.width, root.height
cols : 2
Label:
id: kind
text: 'Tipo de Despesa'
Button:
id: kind_select
text: 'Selecionar'
on_press: root.change_screen(rep = 'Kind')
Label:
id: way
text: 'Meio de Pagamento'
Button:
id: selec_way
text: 'Selecionar'
on_press: root.change_screen(rep = 'Way')
Label:
id: parcelas
text: 'Quantidade de Parcelas'
TextInput:
id: qtdy
Label:
id: value
text: 'Valor Total'
TextInput:
id: qtdy
Button:
id: back
text: 'Voltar'
on_press: root.change_screen(rep = 'Main')
Button:
id: submit
text: 'Registrar'
on_press: root.change_name()
<DropDown1>:
cols : 1
orientation: 'vertical'
<DropDown2>:
cols : 1
orientation: 'vertical'
Python code:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from functools import partial
from functions import Expense
from kivy.properties import ObjectProperty
import kivy
import time
class MainScreen(Widget):
relatorios = ObjectProperty(None)
despesas = ObjectProperty(None)
def change_screen(self,rep):
app_finan_con.screen_manager.current = rep
return
class ReportScreen(Widget):
def change_screen(self,rep):
app_finan_con.screen_manager.current = rep
return
class ExpendScreen(Widget):
kind_selec1 = ObjectProperty()
def __init__(self, **kwargs):
super(ExpendScreen, self).__init__(**kwargs)
def change_screen(self,rep):
app_finan_con.screen_manager.current = rep
def change_name(self):
self.kind_selec1.text= 'porra'
class Dropdown1(BoxLayout):
def __init__(self, **kwargs):
super(Dropdown1, self).__init__(**kwargs)
list = ['Alimentação', 'Transporte', 'Entretenimento','Moradia','Doação']
for item in list:
self.but = Button(text=item)
self.add_widget(self.but)
self.but.bind(on_release=partial(self.It,arg = item))
def It(self,instance,arg):
app_finan_con.screen_manager.current = 'Expend'
ExpendScreen.change_name(ExpendScreen)
class Dropdown2(BoxLayout):
def __init__(self, **kwargs):
super(Dropdown2, self).__init__(**kwargs)
list = ['Crédito', 'Débito', 'Dinheiro']
for item in list:
self.but = Button(text=item)
self.add_widget(self.but)
self.but.bind(on_release=partial(self.It, arg=item))
def It(self,instance,arg):
app_finan_con.screen_manager.current = 'Expend'
class MyApp(App):
def build(self):
self.screen_manager = ScreenManager()
self.mainscreen = MainScreen()
screen = Screen(name='Main')
screen.add_widget(self.mainscreen)
self.screen_manager.add_widget(screen)
self.reportscreen = ReportScreen()
screen = Screen(name='Report')
screen.add_widget(self.reportscreen)
self.screen_manager.add_widget(screen)
self.expendscreen = ExpendScreen()
screen = Screen(name='Expend')
screen.add_widget(self.expendscreen)
self.screen_manager.add_widget(screen)
self.dropdown1 = Dropdown1()
screen = Screen(name='Kind')
screen.add_widget(self.dropdown1)
self.screen_manager.add_widget(screen)
self.dropdown2 = Dropdown2()
screen = Screen(name='Way')
screen.add_widget(self.dropdown2)
self.screen_manager.add_widget(screen)
return self.screen_manager
def change_screen():
app_finan_con.screen_manager.current ='Report'
return
if __name__ == '__main__':
app_finan_con = MyApp()
app_finan_con.run()
The idea is to use screen Dropdown1 to modify screen ExpendScreen through this code:
class Dropdown1(BoxLayout):
def __init__(self, **kwargs):
super(Dropdown1, self).__init__(**kwargs)
list = ['Alimentação', 'Transporte', 'Entretenimento','Moradia','Doação']
for item in list:
self.but = Button(text=item)
self.add_widget(self.but)
self.but.bind(on_release=partial(self.It,arg = item)) (this line activates It method)
def It(self,instance,arg):
app_finan_con.screen_manager.current = 'Expend'
ExpendScreen.change_name(ExpendScreen) (this line activates method in ExpendScreen):
class ExpendScreen(Widget):
kind_selec1 = ObjectProperty()
def __init__(self, **kwargs):
super(ExpendScreen, self).__init__(**kwargs)
def change_screen(self,rep):
app_finan_con.screen_manager.current = rep
def change_name(self):
self.kind_selec1.text= 'porra'
but I get the following error: AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
but when I call the same method from a button in the same screen (ExpendScreen) it works as expected, kivy code:
Button:
id: submit
text: 'Registrar'
on_press: root.change_name()
Image examples:
Can anyone help me?
Your line of code:
ExpendScreen.change_name(ExpendScreen)
is calling an instance method of ExpendScreen without an instance. That won't work. You need to get the instance of ExpendScreen that is part of your gui, and call its change_name method. I haven't tested this, but I think replacing the above line with:
app_finan_con.expendscreen.change_name()
will do what you want.

python 3.x kivy: dynamically adding widgets to scrollview

I´m new to kivy and trying to create a scrollview that gets filled with several widgets live. That kind of works..
BUT the line items themselves often loose their own widgets and I often get this error:
[CRITICAL] [Clock ] Warning, too much iteration done before the next frame. Check your code, or increase the Clock.max_iteration attribute
I read a lot of "clock", blocking the mainthread etc.. I tried to resolve it by using a different thread.. but still widgets are missing.
Attached the code and a picture. help is very much appreciated!! thx!
widgets missing..
my controller.kv
#:kivy 1.0
<Controller>:
size_hint: 1., 1.
pos_hint: {'center_x': .5, 'center_y': .5}
do_default_tab: False
tab1_pgbar: tab1_pgbar
layout_content: layout_content
tab1_refresh_btn: tab1_refresh_btn
TabbedPanelItem:
id: tab1
text: 'Browse'
BoxLayout:
id: bl
orientation: 'vertical'
ScrollView:
size_hint: 1.0,0.7
GridLayout:
id: layout_content
size_hint_y: None
height: self.minimum_height
cols: 1
row_default_height: '100dp'
row_force_default: True
spacing: 0, 5
BoxLayout:
size_hint: 1.0,None
height: 25
ProgressBar:
size_hint: 1.0,1.0
id: tab1_pgbar
max: 1000
Button:
id: tab1_refresh_btn
text: 'Refresh'
size: 100,25
on_release: root.refresh()
my kivyMain.py
import kivy
kivy.require('1.10.0')
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.clock import mainthread
import time
import threading
class myJobEntry(BoxLayout):
def __init__(self):
super(myJobEntry, self).__init__()
def addStuff(self,runindex,program):
b1 = Button(text=runindex,size_hint=(None,1.0),width=100)
b2 = TextInput(text=program,height=80)
hbox1 = BoxLayout(orientation='horizontal')
for i in range(10):
hbox1.add_widget(Button(text='{}'.format(i)))
vbox1 = BoxLayout(orientation='vertical')
vbox1.add_widget(hbox1)
vbox1.add_widget(b2)
self.add_widget(b1)
self.add_widget(vbox1)
class Controller(TabbedPanel):
'''Create a controller that receives a custom widget from the kv lang file.
Add an action to be called from the kv lang file.
'''
layout_content = ObjectProperty()
tab1_refresh_btn = ObjectProperty()
tab1_pgbar = ObjectProperty()
text_input = ObjectProperty()
def addSeveralObjects(self):
self.tab1_pgbar.value = 0
self.layout_content.enabled=False
for i in range(100):
myObj = myJobEntry()
myObj.addStuff('{}'.format(i),'i')
self.layout_content.add_widget(myObj)
self.updateBar()
def refresh(self):
self.tab1_refresh_btn.enabled = False
self.tab1_pgbar.value = 1
mythread = threading.Thread(target=self.addSeveralObjects)
mythread.start()
self.resetRefreshButton()
def resetRefreshButton(self):
self.tab1_refresh_btn.text = 'Last Refresh: {}'.format(time.ctime())
self.tab1_refresh_btn.enabled = True
def updateBar(self):
self.tab1_pgbar.value += 1
class ControllerApp(App):
def build(self):
return Controller()
if __name__ == '__main__':
ControllerApp().run()
I guess the problem is your thread, a better option is to use the kivy Clock object:
...
from kivy.clock import Clock
from kivy.properties import NumericProperty
from kivy.metrics import dp
...
class Controller(TabbedPanel):
'''Create a controller that receives a custom widget from the kv lang file.
Add an action to be called from the kv lang file.
'''
layout_content = ObjectProperty()
tab1_refresh_btn = ObjectProperty()
tab1_pgbar = ObjectProperty()
text_input = ObjectProperty()
i = 0
h = NumericProperty(0)
def addSeveralObjects(self, *args):
self.layout_content.enabled = False
myObj = myJobEntry()
myObj.addStuff('{}'.format(self.i), '{}'.format(self.i))
self.layout_content.add_widget(myObj)
if self.i % 4 == 0:
self.h += dp(420)
self.updateBar()
self.i += 1
if self.i >= 100:
self.clock.cancel()
def refresh(self):
self.tab1_refresh_btn.enabled = False
self.clock =Clock.schedule_interval(self.addSeveralObjects, .05)
self.resetRefreshButton()
def resetRefreshButton(self):
self.tab1_refresh_btn.text = 'Last Refresh: {}'.format(time.ctime())
self.tab1_refresh_btn.enabled = True
def updateBar(self):
self.tab1_pgbar.value += 1
...
In your kv:
<Controller>:
...
TabbedPanelItem:
...
BoxLayout:
...
ScrollView:
size_hint: 1.0,0.7
GridLayout:
id: layout_content
height: root.h
size_hint_y: None
cols: 1
row_default_height: '100dp'
row_force_default: True
spacing: 0, 5
...
...
...
...

Categories