Kivy - Editable RecycleView - python

I'm trying to create an editable table with the RecycleView widget using a TextInput widget as the individual element in Kivy. By looking at the examples and some posts on the web, I was able to write a table that takes input from the user.
Now I'm trying to get which row did the user edit. I could find events for on_text of the text input but that doesn't give information about the row number. Also I tried looking at the events available for the RecycleView but couldn't get much help from it.
Could anyone of you guide me in the right path. I'm fairly new to kivy. I have attached herewith, a simple example that I'm working on.
from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.lang import Builder
Builder.load_string('''
<Row#BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
itemText: ''
TextInput:
id:CellText
text:root.itemText
<RV>:
id: rv
viewclass: 'Row'
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
RecycleGridLayout:
cols:3
default_size: None, dp(30)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(1)
''')
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'itemText':'1'}, {'itemText':'John'}, {'itemText':'K'}, {'itemText':'2'}, {'itemText':'David'}, {'itemText':'P'}]
class rvTestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
rvTestApp().run()

I don't know if the following is recommandable but, to do that when I add a box in the recycle view data i pass it the recycle view instance and when the box is created I add it to a list of the recycle view so you can control the rv from each box and you can control each box from the rv:
from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import ObjectProperty, ListProperty
from kivy.clock import Clock
Builder.load_string('''
<Row>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
itemText: ''
TextInput:
id:CellText
text:root.itemText
<RV>:
id: rv
viewclass: 'Row'
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
RecycleGridLayout:
cols:3
default_size: None, dp(30)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(1)
''')
class RV(RecycleView):
list_items = ListProperty([])
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'itemText': '1', 'paren': self, 'index':0}, {'itemText': 'John', 'paren': self, 'index':1},
{'itemText': 'K', 'paren': self, 'index':2}, {'itemText': '2', 'paren': self, 'index':3},
{'itemText': 'David', 'paren': self, 'index':4}, {'itemText': 'P', 'paren': self, 'index':5}]
def which_edit(self, *args):
'''This will print the index of the box which is currently edited'''
print args[0].parent.index
class Row(BoxLayout):
paren = ObjectProperty() #the instance of the rv
def __init__(self, **kwargs):
super(Row, self).__init__(**kwargs)
Clock.schedule_once(self.update)
def update(self, *args):
self.paren.list_items.append(self)
self.ids.CellText.bind(text=self.paren.which_edit)
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()

SP SP's answer gets a way to work around this problem but I found another way (hoping this to be a better one) to get the row index.
Adding the below over-riding functions into the row class helped me get the row index when ever the user clicks on the textinput.
class Row(BoxLayout, RecycleDataViewBehavior):
index = None
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(Row, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
if super(Row, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos):
global rowIndex
rowIndex = self.index
Thanks again for helping me with your suggestions. Posting my solution in case if any one else is facing the same problem.

Related

Spinner at bottom of recyleview in kivy and kivymd

I want to user to see the spinner when they reach the bottom of the Recycleview..
In this bellow code the spinner is visible in the viewport this is the issue so there is some code below..
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.scrollview import ScrollView
from kivy.uix.screenmanager import Screen
Builder.load_string('''
<MyScreen>:
ScrollView:
BoxLayout:
orientation:"vertical"
size_hint_y:None
height:root.height
RV:
size_hint_y:None
height:root.height
viewclass: 'Button'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
size_hint_y:None
height:self.minimum_height
MDSpinner:
size_hint:None,None
width:dp(40)
height:dp(20)
pos_hint:{"center_x":.5}
''')
class MyScreen(Screen):
pass
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(25)]
class TestApp(MDApp):
def build(self):
return MyScreen()
if __name__ == '__main__':
TestApp().run()
Thanks in advance..
If you want the last item in the RecycleView to be a spinner, you cannot do that with a viewclass of Button (unless you can make a Button look like a Spinner). However, you can design your own viewclass that can look like either a Button or a Spinner. So defining a custom viewclass like this:
class MyViewClass(RelativeLayout):
text = StringProperty('') # text for the Button
spinner = BooleanProperty(False) # if True, show a Spinner instead
Then add a rule for it in your kv:
<MyViewClass>:
Button:
text: root.text
opacity: 0 if root.spinner else 1
MDSpinner:
pos_hint:{"center_x":.5}
opacity: 1 if root.spinner else 0
active: True if root.spinner else False
size_hint: None, None
height: root.height
width: root.height
The above rule uses opacity to determine whether the Button or the Spinner is visible. And the active attribute of the MDSpinner is used to avoid having a spinning MDSpinner running in the items where it is not visible.
Then just specify MyViewClass as the viewclass:
viewclass: 'MyViewClass'
And remove the old MDSpinner from your kv.
The last thing to do is to adjust your data:
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x), 'spinner': False} for x in range(25)]
self.data.append({'text': '', 'spinner': True})
The data now sets spinner to False for all the entries, and adds an extra entry with spinner set to True.

When I click a button different button flashes in kivy recycleview

So, I know there have been couple of similar questions but I haven't found a solution on any of those questions.
When I click one button in my RecycleView screen in my kivy app different button flashes. I haven't changed anything about buttons so I don't see any mistake in my code but there might be something that I have not seen.
Here is the code:
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.uix.recycleview import RecycleView
from kivy.app import App
class Kupci_sve(RecycleView, Screen):
def __init__(self, **kwargs):
super(Kupci_sve, self).__init__(**kwargs)
self.data = [{"text": str(i)} for i in range(20)]
self.bar_width = 8
self.scroll_type = ['bars']
kv = Builder.load_string('''
<Kupci_sve>:
name: 'kupci_sve'
viewclass: 'Button'
RecycleBoxLayout:
default_size: (None, 100)
default_size_hint: (1, None)
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
''')
class app(App):
def build(self):
return Kupci_sve()
if __name__ == '__main__':
app().run()
I import this screen to my main.py file and run it from there but I didn't paste that code because I think it has nothing to do with this problem. If you need any other info, just tell me. Thank you.
Without a minimal, complete, reproducible example, I suspect your problem may be in making your Kupci_sve class extend both Screen and RecycleView. A better approach would be to just extend Screen and simply include a RecycleView in the kv rule for <Kupci_sve>.
Here is what I mean:
<Kupci_sve>:
name: 'kupci_sve'
RecycleView:
id: rv # added id
bar_width: 8
scroll_type: ['bars']
viewclass: 'Button'
RecycleBoxLayout:
default_size: (None, 100)
default_size_hint: (1, None)
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
And the Kupci_sve class becomes:
class Kupci_sve(Screen):
def __init__(self, **kwargs):
super(Kupci_sve, self).__init__(**kwargs)
self.ids.rv.data = [{"text": str(i), "on_release":partial(self.butt_release, i)} for i in range(20)]
def butt_release(self, index):
print('butt_release:', index)

kivy: how can i have a recycleview display data unique to that instance

i have two instances of a recycleview managed buy a screen manager, but im struggling to have each instance display its unique data. they seem to share the data attribute. when i add a button to 'screen1' it shows up under 'screen2' instance as well. how do i direct the current instance to use the data unique to it? any help aprreciated thanks.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
kv = """
<ViewList>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
text: self.value
InputScreen:
name: 'input_screen'
id: inputscreen
MyReusableScreen:
name: 'screen1'
id: screen1
MyReusableScreen:
name: 'screen2'
id: screen2
<InputScreen>:
name: 'input_screen'
orientation: "vertical"
BoxLayout:
orientation: "vertical"
Button:
text: "first screen"
on_press: root.manager.current = 'screen1'
Button:
text: 'second screen'
on_press: root.manager.current = 'screen2'
<MyReusableScreen>:
name: 'mrs'
rv: rv
orientation: "vertical"
BoxLayout:
orientation: "vertical"
Label:
text: root.name
Button:
text: "add"
on_press: app.add_data('text')
Button:
text: "input screen"
on_press: root.manager.current = 'input_screen'
RV:
id: rv
viewclass: 'ViewList'
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 ViewList(RecycleDataViewBehavior, Button):
def refresh_view_attrs(self, rv, index, data):
self.index = index
return super(ViewList, self).refresh_view_attrs(
rv, index, data)
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
class Root(ScreenManager):
pass
class MyReusableScreen(Screen):
def __init__(self, ** kwargs):
super(MyReusableScreen, self).__init__(**kwargs)
class InputScreen(Screen):
pass
class TestApp(App):
def build(self):
return Root()
def add_data(self, value):
sc = self.root.get_screen('screen1')
sc.ids.rv.data.insert(0, {'value': value} or 'default 1')
sc2 = self.root.get_screen('screen2')
sc2.ids.rv.data.insert(0, {'value': value or 'default 2'})
if __name__ == '__main__':
TestApp().run()
I adjusted some things to make your example run. First of all I added some things in your build method to see your InputScreen when the programm starts. Then I removed the value part, because for demonstration purposes it is only neccessary to see the switching default number when you switch between your screens. It looked like this:
class TestApp(App):
def build(self):
root = Root()
root.add_widget(InputScreen())
screen1 = MyReusableScreen(name='screen1')
screen2 = MyReusableScreen(name='screen2')
root.add_widget(screen1)
root.add_widget(screen2)
root.current = "input_screen"
return root
def add_data(self, value):
sc = self.root.get_screen('screen1')
sc.ids.rv.data.insert(0, {'value': 'default 1'})
sc2 = self.root.get_screen('screen2')
sc2.ids.rv.data.insert(0, {'value': 'default 2'})
And now the important part. You have to set a property in your viewclass in order to use the data value. As it is a string a StringProperty is what we are looking for. It should look like this.
class ViewList(RecycleDataViewBehavior, Button):
value = StringProperty("")
Another way would be to use the refresh_view_attrs method and set the button text to your new data. But then you have to change your initial kv string as self.value does not exist then.
KV
<ViewList>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
text: ""
Python
class ViewList(RecycleDataViewBehavior, Button):
def refresh_view_attrs(self, rv, index, data):
self.index = index
self.text = data['value']
return super(ViewList, self).refresh_view_attrs(
rv, index, data)

How to replace deprecated ListView in kivy?

I am studying kivy for now and reading Creating Apps with kivy. Author use follwing code:
.kv
AddLocationForm:
<AddLocationForm#BoxLayout>:
orientation : 'vertical'
BoxLayout:
pos : 100, 100
height : '40dp'
size_hint_y : None
TextInput:
size_hint_x : 50
Button:
text : 'search'
size_hint_x : 25
Button:
text : 'current location'
size_hint_x : 25
ListView:
item_strings: ["Palo Alto, MX", "Palo Alto, US"]
and .py
from kivy.app import App
class FirstKivyApp(App):
pass
FApp = FirstKivyApp()
FApp.run()
But as much as I understand ListView is deprecated now. It is supposed to be changed on RecycleView now. I've checked for some solutions but they don't make sense for me because use things I've not accomplished yet. I tried to use
RecycleView:
data : ["Palo Alto, MX", "Palo Alto, US"]
instead of ListView but it isn't shown whereas I can access the data through id and ObjectProperty. Is there a way to display data in simplier way, than using ScreenManager, constructing classes and referring to build method? For example something like in author's or my example, but working. Adding RecycleBoxLayout didn't work too.
When calling your Recycleview in kivy, make sure it has an appropriate id, this can then be called in your python code with data applied with the following:
rows = ["Palo Alto, MX", "Palo Alto, US"] # declare list
self.ids.rv.data = [{'text':str(row)}for row in rows] # add list
The kivy docs site has a great example of how to implement RecycleView, found here: https://kivy.org/doc/stable/api-kivy.uix.recycleview.html
Try this for a basic example showing how to use ScreenManager with RecycleView:
import kivy
# import main libraries, import object types and layouts
from kivy.config import Config
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.properties import (StringProperty, ObjectProperty,
OptionProperty, NumericProperty, ListProperty, BooleanProperty)
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.behaviors import FocusBehavior
# import screen features
from kivy.uix.label import Label
from kivy.uix.button import Button
# load in kv file, deals with cosmetics of each screen
kv = """
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if root.selected else (0, 0, 0, .1)
Rectangle:
pos: self.pos
size: self.size
<SelectScreen>:
BoxLayout:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
orientation: 'vertical'
GridLayout:
cols: 2
rows: 1
size_hint_y: .25
height: dp(54)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Select all'
font_size: 24
on_release:
controller.select_all(len(rv.data))
Button:
text: 'Deselect all'
font_size: 24
on_release:
controller.clear_all()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
id: controller
key_selection: 'selectable'
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
spacing: dp(2)
"""
Builder.load_string(kv)
# Adds selectable labels to lists (recycleview)
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
def get_nodes(self):
nodes = self.get_selectable_nodes()
if self.nodes_order_reversed:
nodes = nodes[::-1]
if not nodes:
return None, None
selected = self.selected_nodes
if not selected: # nothing selected, select the first
self.select_node(nodes[0])
return None, None
if len(nodes) == 1: # the only selectable node is selected already
return None, None
last = nodes.index(selected[-1])
self.clear_selection()
return last, nodes
def select_all(self, num):
print(num)
#print(len(self.ids.rv.data))
last, nodes = self.get_nodes()
print(nodes)
for x in range(num):
print(x)
self.select_node(x)
def clear_all(self):
self.clear_selection()
# Create action on selectable list, for example apply selection remembers previous selection and saves to sqlite db
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
index = int
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
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 self.selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
# Screen for selecting from recycleview
class SelectScreen(Screen):
interfacename = StringProperty()
def __init__(self, **kwargs):
super(SelectScreen, self).__init__(**kwargs)
rows = ["Palo Alto, MX", "Palo Alto, US"] # declare list
self.ids.rv.data = [{'text':str(row)}for row in rows] # add list
def select(self): # selects participants from list new value to database
print (self.ids.rv.data[val[0]]['text'])
def selectall(self):
for num in range(len(self.ids.rv.data)):
SelectableLabel.apply_selection(self, self.ids.rv, num, True)
# screen manager
sm = ScreenManager()
sm.add_widget(SelectScreen(name='select')) # main starting menu
# Build the app return screenmanager
class RecycleviewApp(App):
def build(self):
return sm
if __name__ == '__main__':
RecycleviewApp().run()
Example image of application and terminal printing application and removal of RecycleView selection:
I founded out the issue.
Firstly, format of data must be suitable. As twicejiggled showed it must be list of dicts.
Secondly, as much as I understood, some layout is neede for showing. For example this code in .kv will work:
RecycleView:
data : [{'text':'text'} for x in range(50)]
viewclass: 'Label'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'

Listing Order Numbers, Items and Subitems from database

So, I'm trying to create a list that pulls from a mysql database that displays an order # as a button and a new window expands to display the order numbers items/subitems with checkboxes to have checked when an item is selected. I have the window to pop up; however, nothing populates and my query prints out information/list. After all boxes are checked and a Finish button is clicked on, it will remove the order from the list to show it has been worked on. I found this on the forums; however, it's not pulling anything for me:
How to fetch data from database and show in table in kivy+python
Thank you!
main.py
import pymysql
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
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
orderNum = "1"
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 Button '''
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 RV(BoxLayout):
data_items = ListProperty([])
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.get_order()
def get_order(self):
connection = pymysql.connect(host='localhost', user='root',passwd='password', db='Shop')
cursor = connection.cursor()
cursor.execute("SELECT itemName, ITEM FROM order_list WHERE orderID=%s", (orderNum,))
rows = cursor.fetchall()
# create data_items
for row in rows:
for col in row:
self.data_items.append(col)
class TestApp(App):
title = "Kivy RecycleView & pymysql Demo"
def build(self):
return RV()
if __name__ == "__main__":
TestApp().run()
kivy.kv
#:kivy 1.10.0
<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: "Finish"
on_release:
root.obj.update_changes(txtinput.text)
root.dismiss()
Button:
size_hint: 1, 0.2
text: "Cancel Changes"
on_release: root.dismiss()
<SelectableButton>:
# 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
<RV>:
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 2
Label:
text: "Order Number"
BoxLayout:
RecycleView:
viewclass: 'SelectableButton'
data: [{'text': str(x)} for x in root.data_items]
SelectableRecycleGridLayout:
cols: 2
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
Here's my list that pulls information and uses a print function:
('Sub', 'Italian')
('Sub', 'Meatball')
('Drink', 'Apple Juice')
('Side', 'Salad')
('Soup', 'Chicken and rice')
What I have and What I'm Trying to Achieve

Categories

Resources