Kivy: Updating Label in several widgets and screens - python

I'm trying to update the value of a label in 2 widgets and a screen. This should be pretty straightforward but I haven't been able to make it work!
I suspect that whatever I'm doing is not being picked up by the GUI for some reason. I say this because when "printing values" to debug the value does update as expected.
Finally, I've looked at several solutions but neither seem to work: Kivy Label Text does not update, Kivy: Label text does not update during for-loop, Kivy Label.text Property doesn't update on the UI
The screen I'm trying to update:
Screens/recipe_screen.py
from kivymd.uix.screen import MDScreen
class RecipeScreen(MDScreen):
pass
Screens/recipe_screen.kv
#:import CounterWidget Components.counter_widget.CounterWidget
#:import IngredientsLabel Components.ingredients_label.IngredientsLabel
<RecipeScreen>:
name: 'recipe_screen'
id: recipe_screen
MDBoxLayout:
adaptive_height: True
orientation: 'horizontal'
padding: 30, 20
spacing: 0
IngredientsLabel:
id: il
label_text: 'text I'm trying to update'
CounterWidget:
Widget 1: this is what I'm calling from the GUI
components/counter_widget.py
from kivymd.uix.card import MDCard
from Components.ingredients_label import IngredientsLabel
class CounterWidget(MDCard):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.counter = 4
def increase_widget(self):
self.counter += 1
self.ids['counter_text'].text = str(self.counter) <--- THIS WORKS FINE
ingredient_label_ref = IngredientsLabel
ingredient_label_ref().change_label(self.counter) <--- THIS DOES NOT WORK
components/counter_widget.kv
#:import RecipeScreen Screens.recipe_screen.RecipeScreen
<CounterWidget>:
elevation: 5
border_radius: 15
radius: [15]
size_hint: None, None
size: 250, 90
MDGridLayout:
MDLabel:
id: counter_text
text: '4'
Widget 2: this is the widget I'm trying to update based on Widget 1
components/ingredients_label.py
from kivymd.uix.boxlayout import MDBoxLayout
class IngredientsLabel(MDBoxLayout):
def change_label(self, counter):
self.ids['servings_counter'].text = str(counter) + ' servings' <--- DOES NOT UPDATE GUI
print('IngredientsLabel: ' + str(counter) + ' servings') <--- PRINTS CORRECTLY IN TERMINAL
components/ingredients_label.kv
#:import RecipeScreen Screens.recipe_screen.RecipeScreen
<IngredientsLabel>:
adaptive_height: True
orientation: 'vertical'
MDLabel:
id: servings_counter
markup: True
text: '4 servings'
font_style: 'Subtitle2'
main.py
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.factory import Factory
from kivy.uix.screenmanager import ScreenManager
from Screens.recipe_screen import RecipeScreen
from Screens.carousel_screen import CarouselScreen
class MainApp(App, MDApp):
def build_app(self):
Window.size = [350, 560]
sm = ScreenManager()
sm.add_widget(RecipeScreen(name='recipe_screen'))
sm.add_widget(CarouselScreen(name='carousel_screen'))
return sm
if __name__ == '__main__':
MainApp().run()
Update post #John Anderson suggestion to:
def increase_widget(self):
self.counter += 1
self.ids['counter_text'].text = str(self.counter)
ingredient_label_ref = MDApp.get_running_app().root.get_screen('recipe_screen').ids.il # get reference to IngredientsLabel
ingredient_label_ref.change_label(self.counter)
Unfortunately, this didn't work either.
I also tried referencing in the .kv file via:
Changing the components/ingredients_label.kv to
#:import RecipeScreen Screens.recipe_screen.RecipeScreen
<IngredientsLabel>:
adaptive_height: True
orientation: 'vertical'
label_text: 'None'
MDLabel:
markup: True
text:'Ingredients for'
font_style: 'H6'
MDLabel:
markup: True
text: root.label_text
font_style: 'Subtitle2'
and components/counter_widget.py to (while keeping the 'il' id reference for the IngredientsLabel widget on the RecipeScreen)
from kivymd.uix.card import MDCard
from Components.ingredients_label import IngredientsLabel
class CounterWidget(MDCard):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.counter = 4
def increase_widget(self):
self.counter += 1
self.ids['il'].label_text = str(self.counter)
but this didn't work either (no error, but no update to the UI either...).
I included the App class above as well.

Without a complete runnable code (you have not provided an App class), it is difficult to provide a definitive answer. However, assuming that your App creates a ScreenManager with RecipeScreen as one of its Screens, you can modify your increase_widget() method:
def increase_widget(self):
self.counter += 1
self.ids['counter_text'].text = str(self.counter)
ingredient_label_ref = MDApp.get_running_app().root.get_screen('recipe_screen').ids.il # get reference to IngredientsLabel
ingredient_label_ref.change_label(self.counter)
In order for this to work, you must add some ids to your kv:
<RecipeScreen>:
name: 'recipe_screen'
id: recipe_screen
MDBoxLayout:
adaptive_height: True
orientation: 'horizontal'
padding: 30, 20
spacing: 0
IngredientsLabel:
id: il # added id
CounterWidget:
id: cw # added, but not required

Related

How to refresh GridLayout in Kivy with kv file

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)

Kivy updating label when using function via button fails

I'm trying to build an App thats uses certain input Parameters and when hitting a submit button it uses a logic to generate output paramters. I managed to build the app and the input and the triggering via a submit button. Now i want to generate the output, for beginning with an easy logic. I looked up several similar solutions, but somehow they don't work for me.
For some reason my .kv file doen't get the updated value for the label text with the error: "ValueError: Label.text accept only str" Eventough everything is declared as a string in the .py. If i change it in the kv to str("...") I get some code line which i guess is the intern id of the attribute but not the assigned value i want to get.
I hope you can help. Pls don't be too harsh, I#M new to python and kivy...
main .py, shuldn't be part of the problem
Peenomat.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.anchorlayout import AnchorLayout
from kivy.core.text import LabelBase
Builder.load_file('Statusbar.kv')
Builder.load_file('Inputparameters.kv')
Builder.load_file('Outputparameters.kv')
#Layout
class Peenomat(AnchorLayout):
pass
class PeenomatApp(App):
def build(self):
return Peenomat()
if __name__=="__main__":
PeenomatApp().run()
.py with the classes and methods for the logic
StatusBar.py
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
from kivy.properties import StringProperty
from kivy.app import App
class InputParameters(GridLayout):
verfahren = ObjectProperty(None)
def on_state(self, togglebutton):
tb = togglebutton
if tb.state == 'down':
self.verfahren = tb.text
self.verfahren = tb.text
print(self.verfahren)
return self.verfahren
class StatusBar(BoxLayout):
#InputGrößen
group_mode = False
prozess = ObjectProperty(None)
vorbehandlung = ObjectProperty(None)
material = ObjectProperty(None)
haerte = ObjectProperty(None)
rauheit = ObjectProperty(None)
#OutputGrößen
frequenz = StringProperty(None)
def btn_submit(self):
ip = App.get_running_app().root.ids._input_parameters
print("Haerte:", ip.haerte.value, "Rauheit:", ip.rauheit.value, "Material:", ip.material.text, "Vorbehandlung:", ip.vorbehandlung.text)
if ip.haerte.value < 50:
self.frequency = str(180)
elif ip.haerte.value < 60:
self.frequency = str(200)
else:
self.frequency = str(220)
#control to see if right value is taken
print(self.frequency, "Hz")
def btn_clear(self):
np = App.get_running_app().root.ids._input_parameters
np.pro1.state = "normal"
np.pro2.state = "normal"
np.pro3.state = "normal"
np.material.text = "Auswahl treffen"
np.haerte.value = 55
np.rauheit.value = 5.5
the .kv file that can't get the label text:
outputparameters.kv
#: import statusbar StatusBar
<OutputParameters#GridLayout>
#Initialisierung .py zu .kv
frequenz: _frequenz
Label:
text:'Frequenz:'
font_size:
Label:
id: _frequenz
text: root.frequenz
font_size: 20
the .kv file with the submit button, shouldn't be part of the problem either, worked perfectly fine before implementing the part ehre i try to update the text
statusbar.kv
#: import statusbar StatusBar
<StatusBar#BoxLayout>
orientation:'horizontal'
Button:
text: 'Clear'
on_press: root.btn_clear()
Button:
text: 'Submit'
on_press: root.btn_submit()
the file where i put in all the inputparameters, rather important:
Inputparameters.kv
#: import statusbar StatusBar
<InputParameters#GridLayout>
#Initialisierung .py zu .kv Ids
prozess: _prozess
pro1: _prozess1
pro2: _prozess2
pro3: _prozess3
vorbehandlung: _vorbehandlung
material: _material
haerte: _haerte
rauheit: _rauheit
#Prozess
Label:
text:'Prozess:
BoxLayout:
orientation: 'horizontal'
id: _prozess
ToggleButton:
id:_prozess1
text:'P-MOH'
group: "proc_group"
on_state: root.on_state(self)
ToggleButton:
id:_prozess2
text:'E-MOH'
group: "proc_group"
on_state: root.on_state(self)
ToggleButton:
id:_prozess3
text:'PE-MOH'
group: "proc_group"
on_state: root.on_state(self)
#Material
Label:
text: 'Material:'
Spinner:
id: _material
text: ""
values:
# Herstellschritte
Label:
text:'Fertigungsschritte:'
Spinner:
id: _vorbehandlung
text:
values:
# Haerte
Label:
text:'Haerte:'
BoxLayout:
orientation: 'vertical'
Label:
text: str(_haerte.value)
Slider:
id: _haerte
# Rauheit
Label:
text:'Rauheit:
BoxLayout:
orientation: 'vertical'
Label:
text:
Slider:
id: _rauheit
and the file where my layout is embedded (also rather necessary)
peenomat.kv
<Peenomat>
AnchorLayout:
anchor_x: 'left'
anchor_y: 'bottom'
GridLayout:
cols: 1
canvas.before:
Color:
Rectangle:
pos: self.pos
size: self.size
InputParameters:
id:_input_parameters
StatusBar:
id:_status_bar
OutputParameters:
id:_output_parameters
I really hope you can help, have been struggeling with this for a while and it should rather be easy...thanks in advance!
In your kv rule for <OutputParameters#GridLayout> you have a line:
frequenz: _frequenz
which sets frequenz to be a reference to the Label with the id of _frequenz. Then in that Label you are setting text using:
text: root.frequenz
So, you are trying to set the text of the Label to a reference to that Label
I suggest trying something like this:
<OutputParameters#GridLayout>
#Initialisierung .py zu .kv
frequenz: _frequenz
frequency: ''
And change the Label to:
Label:
id: _frequenz
text: root.frequency
font_size: 20
But to actually change the value shown in the label, you will need a reference to the instance of OutputParameters, using something like:
App.get_running_app().root.ids._output_parameters.frequency = str(500)

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.

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
...
...
...
...

Kivy Python: Accordion inside an accordion, using a variable

I am trying to create an accordion menu (no.1), in which there is another accordion menu (no.2).
The size of accordion no.2 will be defined by the user (an example of the outcome is shown in this image).
The issue is that though I have managed to create a class that creates accordion no.2 following users input - I can`t seem to find the way to display it on the screen.
This is my py code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.properties import NumericProperty
wide = 0
long = 0
class AccordionClass():
def calc(val):
number = val
root = Accordion(size= (200,700), pos = (50,80), orientation= 'vertical')
for x in range(number):
print ('x = ',x)
item = AccordionItem(title='Title %d' % x)
item.add_widget(Label(text='Very big content\n' * 10))
root.add_widget(item)
print ('END')
return root
class LoginScreen(GridLayout):
numOfStories = NumericProperty()
def printTxt(self, text, TextInputId):
pass
def addFloor(self,text):
self.numOfStories = int(text)
print ('self.numOfStories = ', self.numOfStories)
rootAc = AccordionClass.calc(self.numOfStories)
return rootAc
pass
class screen2(App):
def build(self):
self.root = GridLayout()
return LoginScreen()
if __name__ == "__main__":
screen2().run()
and my kv code:
<TextInput>:
multiline: False
size:150,23
font_size: 12
padding: [5, ( self.height - self.line_height ) / 2]
<Label>:
size:120,18
font_size: 12
padding: [5, ( self.height - self.line_height ) / 2]
<LoginScreen>:
canvas:
Color:
rgb: (0.93, 0.93, 0.93,1)
Rectangle:
pos: self.pos
size: self.size
GridLayout:
size:root.size
cols:2
Accordion:
size_hint: (1.0,0.2)
orientation: 'vertical'
AccordionItem:
title: 'Plot'
GridLayout:
AccordionItem:
title: 'Number'
GridLayout:
Label:
text: "Number"
color: [0, 0, 0, 1]
pos:root.x, root.top-self.height-100
TextInput:
pos:root.x+120, root.top-self.height-100
id: NumOfStories
on_text_validate: root.addFloor(NumOfStories.text)
AccordionItem:
title: 'Another number'
Button:
background_color: (5,5,5,1)
Any idea how to solve this issue?
Thanks
It isn't displaying because of you returning an instance of Accordion into nothing in addFloor/calc in on_text_validate. To create a widget you have to call <parent>.add_widget(<widget>), so let's do that:
on_text_validate: root.add_widget(root.addFloor(NumOfStories.text))
Then there's the thing your calc() is a class method for now and you either need to use self as an additional parameter (and have even more mess), or use the #staticmethod decorator, which makes the calc() free of class stuff and let you use it this way Class.method(...)
#staticmethod
def calc(val):
After that a new Accordion will show up, but the sizing and positioning is up to you. Also, by default there's probably no background for that widget, so you'll end up with putting it there via canvas instructions.

Categories