A sample Kivy app populates Recycleview upon a button click.
However, when I copy the code from the class functions into the App.build to populate the Recycleview upon app loading, it doesn't do it. Is it not referencing the Recycleview correctly? Why is this not working?
I saw some answers here that used "self.ids." to find the Recycleview data, and also "App.get_running_app()." but it didn't work.
(Note: requires Kivy 2.0.0rc4 to compile)
from random import sample, randint
from string import ascii_lowercase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
kv = """
<Row#RecycleKVIDsDataViewBehavior+BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
Label:
id: name
Label:
text: root.value
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
rv: rv
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Populate list'
on_press: root.populate()
Button:
text: 'Sort list'
on_press: root.sort()
Button:
text: 'Clear list'
on_press: root.clear()
BoxLayout:
spacing: dp(8)
Button:
text: 'Insert new item'
on_press: root.insert(new_item_input.text)
TextInput:
id: new_item_input
size_hint_x: 0.6
hint_text: 'value'
padding: dp(10), dp(10), 0, 0
BoxLayout:
spacing: dp(8)
Button:
text: 'Update first item'
on_press: root.update(update_item_input.text)
TextInput:
id: update_item_input
size_hint_x: 0.6
hint_text: 'new value'
padding: dp(10), dp(10), 0, 0
Button:
text: 'Remove first item'
on_press: root.remove()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Test(BoxLayout):
def populate(self):
self.rv.data = [
{'name.text': ''.join(sample(ascii_lowercase, 6)),
'value': str(randint(0, 2000))}
for x in range(50)]
def sort(self):
self.rv.data = sorted(self.rv.data, key=lambda x: x['name.text'])
def clear(self):
self.rv.data = []
def insert(self, value):
self.rv.data.insert(0, {
'name.text': value or 'default value', 'value': 'unknown'})
def update(self, value):
if self.rv.data:
self.rv.data[0]['name.text'] = value or 'default new value'
self.rv.refresh_from_data()
def remove(self):
if self.rv.data:
self.rv.data.pop(0)
class TestApp(App):
def build(self):
t = Test()
t.rv.data = [
{'name.text': ''.join(sample(ascii_lowercase, 6)),
'value': str(randint(0, 2000))}
for x in range(50)]
t.rv.refresh_from_data()
return Test()
if __name__ == '__main__':
TestApp().run()
One solution is to perform the Recycleview update from the Test class.
from random import sample, randint
from string import ascii_lowercase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
kv = """
<Row#RecycleKVIDsDataViewBehavior+BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
Label:
id: name
Label:
text: root.value
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
rv: rv
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Populate list'
on_press: root.populate()
Button:
text: 'Sort list'
on_press: root.sort()
Button:
text: 'Clear list'
on_press: root.clear()
BoxLayout:
spacing: dp(8)
Button:
text: 'Insert new item'
on_press: root.insert(new_item_input.text)
TextInput:
id: new_item_input
size_hint_x: 0.6
hint_text: 'value'
padding: dp(10), dp(10), 0, 0
BoxLayout:
spacing: dp(8)
Button:
text: 'Update first item'
on_press: root.update(update_item_input.text)
TextInput:
id: update_item_input
size_hint_x: 0.6
hint_text: 'new value'
padding: dp(10), dp(10), 0, 0
Button:
text: 'Remove first item'
on_press: root.remove()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Test(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.populate()
def populate(self):
self.rv.data = [
{'name.text': ''.join(sample(ascii_lowercase, 6)),
'value': str(randint(0, 2000))}
for x in range(50)]
def sort(self):
self.rv.data = sorted(self.rv.data, key=lambda x: x['name.text'])
def clear(self):
self.rv.data = []
def insert(self, value):
self.rv.data.insert(0, {
'name.text': value or 'default value', 'value': 'unknown'})
def update(self, value):
if self.rv.data:
self.rv.data[0]['name.text'] = value or 'default new value'
self.rv.refresh_from_data()
def remove(self):
if self.rv.data:
self.rv.data.pop(0)
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
It seems to be a question of the hierarchy and Kivy design. There's no instance of the RecycleView getting passed to the top object App.
Related
good day
what im trying to accomplish is to input text on one screen then have it create a button on another screen within a recycleview where if I keep adding buttons the recycleview keeps getting populated. I would assume that the button1 function would update the rvs.rv.data and that the recycleview would use the it update itself. could you point me in the right direction please?
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen, ScreenManager
kv = """
<custombutton#BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
Button:
text: root.value
<Root>:
RVScreen:
name: 'Rv'
InputScreen:
name: 'input_screen'
<InputScreen>:
name: "input_screen"
orientation: "vertical"
BoxLayout:
orientation: "vertical"
TextInput:
id: textinput
Button:
text: "hi"
on_press: print(app.rvs.rv.data)
on_press: app.rvs.button1('some_value')
Button:
text: 'rvscreen'
on_press: root.manager.current = 'Rv'
<RVScreen>:
name: 'Rv'
rv: rv
orientation: "vertical"
BoxLayout:
orientation: "vertical"
Button:
text: 'refresh'
on_press: root.rv.refresh_from_data()
on_press: print(app.rvs.rv.data)
Button:
text: "input page"
on_press: root.manager.current = 'input_screen'
RecycleView:
id: rv
viewclass: 'custombutton'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
"""
Builder.load_string(kv)
class Root(ScreenManager):
pass
class RVScreen(Screen):
def __init__(self, **kwargs):
super(RVScreen, self).__init__(**kwargs)
def button1(self, value):
self.rv.data.insert(0, {'value': value or 'default value'})
class InputScreen(Screen):
pass
class TestApp(App):
rvs = RVScreen()
def build(self):
return Root()
if __name__ == '__main__':
TestApp().run()
The problem is that you are populating the data of the app.rvs, but that is created by the line:
rvs = RVScreen()
which creates a new RVScreen instance that is entirely unrelated to the RVScreen that is displayed in your GUI. In order to update the RVScreen in your GUI, you need to get a reference to that RVScreen.
I like to that that through a method in the App, just because it makes the code simpler. So here is a modified version of your code that does it my way:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
kv = """
<custombutton#BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
Button:
text: root.value
<Root>:
RVScreen:
name: 'Rv'
InputScreen:
name: 'input_screen'
<InputScreen>:
name: "input_screen"
orientation: "vertical"
BoxLayout:
orientation: "vertical"
TextInput:
id: textinput
Button:
text: "hi"
on_press: app.add_data(textinput.text)
Button:
text: 'rvscreen'
on_press: root.manager.current = 'Rv'
<RVScreen>:
name: 'Rv'
rv: rv
orientation: "vertical"
BoxLayout:
orientation: "vertical"
Button:
text: 'refresh'
on_press: rv.refresh_from_data()
Button:
text: "input page"
on_press: root.manager.current = 'input_screen'
RecycleView:
id: rv
viewclass: 'custombutton'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
"""
Builder.load_string(kv)
class Root(ScreenManager):
pass
class RVScreen(Screen):
pass
class InputScreen(Screen):
pass
class TestApp(App):
# rvs = RVScreen() # this code does nothing useful
def build(self):
return Root()
def add_data(self, value):
# get the `Screen` instance
rvs = self.root.get_screen('Rv')
# insert the new value into the data
rvs.ids.rv.data.insert(0, {'value': value or 'default value'})
if __name__ == '__main__':
TestApp().run()
I also made a small modification, so that the new value comes from the TextInput.
i was just playing with Python and kivy , I've loaded my String data into a RecyclerView as per the kivy official documentation. but I've faced trouble on loading an object to multiple columns inside the list like a form data. for example i wanted to have name,family name and age to three columns with title headers row by row , I've also tried RecyclerGridLayout with 3 columns , but it can load just name into grids regardless of row by row requirement
<RV>:
viewclass: 'Label'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
Will appreciate any hint or sample code to learn how RecyclerView works on kivy
I was also looking for this and I could not find a specific example, so I have provided my solution. As el3ien has said, you will need to create a custom class which will represent each row of your selectable label.
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
label1_text: 'label 1 text' # I have included two methods of accessing the labels
label2_text: 'label 2 text' # This is method 1
label3_text: 'label 3 text'
pos: self.pos
size: self.size
Label:
id: id_label1 # method 2 uses the label id
text: root.label1_text
Label:
id: id_label2
text: root.label2_text
Label:
id: id_label3
text: root.label3_text
In applying your data into the RV, you will need to restructure the dictionary to reflect the label layout
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
paired_iter = zip(items_1, items_2) # items_1 and items_2 are defined elsewhere
self.data = []
for i1, i2 in paired_iter:
d = {'label2': {'text': i1}, 'label3': {'text': i2}}
self.data.append(d)
Finally in the refresh_view_attrs, you will specify .label_text which is bound to each label, or you can use label id's.
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.label1_text = str(index)
self.label2_text = data['label2']['text']
self.ids['id_label3'].text = data['label3']['text'] # As an alternate method of assignment
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
The entire code is below:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
Builder.load_string('''
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
label1_text: 'label 1 text'
label2_text: 'label 2 text'
label3_text: 'label 3 text'
pos: self.pos
size: self.size
Label:
id: id_label1
text: root.label1_text
Label:
id: id_label2
text: root.label2_text
Label:
id: id_label3
text: root.label3_text
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
''')
items_1 = {'apple', 'banana', 'pear', 'pineapple'}
items_2 = {'dog', 'cat', 'rat', 'bat'}
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, GridLayout):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
cols = 3
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.label1_text = str(index)
self.label2_text = data['label2']['text']
self.ids['id_label3'].text = data['label3']['text'] # As an alternate method of assignment
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, 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
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
paired_iter = zip(items_1, items_2)
self.data = []
for i1, i2 in paired_iter:
d = {'label2': {'text': i1}, 'label3': {'text': i2}}
self.data.append(d)
# can also be performed in a complicated one liner for those who like it tricky
# self.data = [{'label2': {'text': i1}, 'label3': {'text': i2}} for i1, i2 in zip(items_1, items_2)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
Instead of using Label as viewclass, create a custom class. That could be a horizontal box layout with two boxes.
<CustomClass#BoxLayout>:
orientation: "horizontal"
Label:
Label:
I used the above idea from #el3ien. My code is below.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
Builder.load_string('''
<RV>:
viewclass: 'myView'
RecycleBoxLayout:
default_size: None, dp(200)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<myView#BoxLayout>:
BoxLayout:
orientation: 'horizontal'
BoxLayout:
orientation: 'vertical'
on_release:
Button:
size_hint: (1,1)
background_normal: 'C:/Users/Arsalan/Desktop/dummyImage2.jpg'
background_down: 'C:/Users/Arsalan/Desktop/dummyImage1.png'
text:
text_size: self.size
halign:
valign: 'middle'
Label:
size_hint: (1,0.3)
text: 'Product summary'
text_size: self.size
halign:
valign: 'middle'
canvas.before:
Color:
rgba: (0.6, 0.7, 0.4, 1)
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
size_hint :(1,0.01)
Label:
size_hint: (1,0.3)
text: 'Rs 600'
text_size: self.size
halign:
valign: 'middle'
BoxLayout:
orientation: 'vertical'
size_hint: (0.001,1)
BoxLayout:
orientation: 'vertical'
on_release:
Button:
size_hint: (1,1)
background_normal: 'C:/Users/Arsalan/Desktop/dummyImage2.jpg'
background_down: 'C:/Users/Arsalan/Desktop/dummyImage1.png'
text:
text_size: self.size
halign:
valign: 'middle'
Label:
size_hint: (1,0.3)
text: 'Product summary'
text_size: self.size
halign:
valign: 'middle'
canvas.before:
Color:
rgba: (0.6, 0.7, 0.4, 1)
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
size_hint :(1,0.01)
Label:
size_hint: (1,0.3)
text: 'Rs 600'
text_size: self.size
halign:
valign: 'middle'
''')
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(100)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
Try it out and let me know if you still have any question.
I would like to add a headers row to the table; and, I am trying the following:
<RV>:
BoxLayout:
orientation: "vertical"
BoxLayout:
orientation: 'horizontal'
size_hint: 1, None
size_hint_y: None
height: 25
Label:
text: "Item"
Label:
text: "User ID"
Label:
text: "User Name"
BoxLayout:
RecycleView:
id: review
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
But then the :SelectableRecycleBoxLayout moves deeper into the hierarchy and I no longer get the table, only the headers...how to fix this? I still don't quite get how to get to kv stuff from the python. :-(
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'
I am new to python/Kivy.
I have two files test.py and test.ky.
Now I am using two static row with serial number 1 and 2.
Can anyone tell me?
How to add row dynamic when click on '+add more' button.Now 2 row shows which are static row with serial number increment. I want add row dynamic 1 to 10 with serial number increment.
test.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
Window.size = (450, 525)
class display(Screen):
def add_more(self):
print('test')
class test(App):
def build(self):
self.root = Builder.load_file('test.kv')
return self.root
if __name__ == '__main__':
test().run()
test.kv
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'
BoxLayout:
orientation: "horizontal"
spacing: 0, 5
Button:
text: '1'
size_hint_x: .2
TextInput:
size_hint_x: .8
BoxLayout:
orientation: "horizontal"
spacing: 0, 5
Button:
text: '2'
size_hint_x: .2
TextInput:
size_hint_x: .8
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.dismiss()
Button:
text: 'Cancel'
on_release: root.dismiss()
Can someone help me?
You can make a custom class for Row and Rows, then have a method adding rows.
I modified your example a bit. Try this:
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.size = (450, 525)
class display(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_string(KV)
return self.root
KV = """
<Row>:
orientation: "horizontal"
spacing: 0, 5
Button:
text: root.button_text
size_hint_x: .2
TextInput:
size_hint_x: .8
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.dismiss()
Button:
text: 'Cancel'
on_release: root.dismiss()
"""
test().run()