How can i access the clicked button of a recycle view? - python

beginner here.
Im not completely sure if everything is correct in the code bellow, but it seems to work for making the size of the buttons created by recycleview, to adapt to each button's text.
Question:
How can i access (print for now) the clicked button's id in the on_release_m method?
Thanks for your time!
from kivy.config import Config
Config.set('graphics', 'width', '270')
Config.set('graphics', 'height', '550')
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.lang import Builder
# I included this in case you have to use it for the answer instead of the python code:
Builder.load_string("""
<MyRecycleBoxLayout>:
# orientation: 'vertical'
# default_size: None, None
# default_size_hint: 1, None
# size_hint_y: None
# height: self.minimum_height
<MyButton>:
# font_size: 30
# text_size: self.width, None
# size_hint_y: None
# height: self.texture_size[1]
""")
class MyRecycleBoxLayout(RecycleBoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.orientation= 'vertical'
self.default_size= (None, None)
self.default_size_hint=( 1, None)
self.size_hint_y= None
self.bind(minimum_height=self.setter("height"))
class MyButton(Button):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.font_size=30
self.bind(width=lambda s, w: s.setter("text_size")(s, (w, None)))
self.size_hint_y = None
self.bind(texture_size=self.setter("size"))
class RV(RecycleView):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.myRecycleBoxLayout=MyRecycleBoxLayout()
self.data.clear()
for i in range(150):
self.data.append({f"text":f"{i} "*50,"id": f"btn_{i}","on_release":self.on_release_m})
self.add_widget(self.myRecycleBoxLayout)
def on_release_m (self,*args):
#Here print the clicked button id and not the first's:
print(self.data[0]["id"])
pass
class RecApp(App):
def build(self):
self.rv=RV()
self.rv.viewclass=MyButton
return self.rv
RecApp().run()

You can pass additional information to your on_release_m() method by using partial:
for i in range(150):
self.data.append({f"text": f"{i} "*50, "id": f"btn_{i}", "on_release": partial(self.on_release_m, i)})
Then, in the on_release_m() method:
def on_release_m(self, i):
#Here print the clicked button id and not the first's:
print(self.data[i]["id"])

Related

Kivy TabbedPanel. How to call switch_to from another class?

In below code I need the button on the edit_button_tab to switch to edit_input_tab. I really need to switch it that way as I need to switch between predefined classes EditButton and EditInput. This is a part of a bigger program with few Buttons in different location of a layout and I cant define them within <MainTabbedPanel> class. I've tried many ways to call switch_to (example in the quotes) but they didn't work.
CODE
from kivy.animation import Animation
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelStrip
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
from kivy.factory import Factory
theRoot = """
#:import Factory kivy.factory.Factory
<EditButton>
orientation: 'vertical'
Button:
text: 'Switch to Edit Screen'
on_press: root.change_tab('edit_screen')
<EditInput>
orientation: 'vertical'
TextInput:
<UnCloseableHeader>
color: 0,0,0,1
disabled_color: self.color
# variable tab_width
text: 'tabx'
size_hint_x: None
width: self.texture_size[0] + 40
BoxLayout:
pos: root.pos
size_hint: None, None
size_y: 20
padding: 3
Label:
id: lbl
text: root.text
<MainTabbedPanel#BoxLayout>
size_hint: (1, 1)
default_tab: edit_button_tab
tab_width: 130
FloatLayout:
EditButton:
id: edit_button
EditInput:
id: edit_input
UnCloseableHeader:
id: edit_button_tab
text: 'Edit'
content: edit_button.__self__
UnCloseableHeader:
id: edit_input_tab
text: 'Edit Tab'
content: edit_input.__self__
MainTabbedPanel:
"""
class EditInput(BoxLayout):
def __init__(self, **kwargs):
super(EditInput, self).__init__(**kwargs)
class EditButton(BoxLayout):
def __init__(self, **kwargs):
super(EditButton, self).__init__(**kwargs)
def change_tab(self, tab):
print('TAB', tab)
#call switch method from MainTabbedPanel
'''the way I've tried
mtp = MainTabbedPanel
mtp.switch_to('edit_input_tab')'''
class MainTabbedPanel(TabbedPanel):
def __init__(self, **kwargs):
super(MainTabbedPanel, self).__init__(**kwargs)
def switch(self, tab):
print("SWITCH TO", tab, self.ids.keys())
self.switch_to(self.ids[tab])
class UnCloseableHeader(TabbedPanelHeader):
pass
Factory.register('UnCloseableHeader', cls=UnCloseableHeader)
sm = Builder.load_string(theRoot)
class TabbedPanelApp(App):
def build(self):
return sm
if __name__ == '__main__':
TabbedPanelApp().run()
EDIT
I've tried with below snippet. It prints IDS of MainTabbedPanel but does not change the tabs.
class EditButton(BoxLayout):
def __init__(self, **kwargs):
super(EditButton, self).__init__(**kwargs)
def change_tab(self, tab):
print('TAB', tab)
MainTabbedPanel.tab = tab
MainTabbedPanel()
#call switch method from MainTabbedPanel
'''the way I've tried
mtp = MainTabbedPanel
mtp.switch_to('edit_input_tab')'''
class MainTabbedPanel(TabbedPanel):
tab = ''
def __init__(self, **kwargs):
super(MainTabbedPanel, self).__init__(**kwargs)
self.tabs_showing = True
if self.tab != '':
Clock.schedule_once(self.switch)
def switch(self, dt):
print("SWITCH TO", self.tab, self.ids.keys())
self.switch_to(self.ids[self.tab])
Use App.get_running_app() to get an instance of your app
Use root to get an instance of your root
Snippets
def change_tab(self, tab):
print('TAB', tab)
mtp = App.get_running_app().root
mtp.switch_to(mtp.ids.edit_input_tab)
Notes
In your kv, you defined a Dynamic class,
<MainTabbedPanel#BoxLayout>. It should be a class rule,
<MainTabbedPanel> because in your Python code, you have defined
class MainTabbedPanel(TabbedPanel): i.e. inheritance mismatch and
class type mismatch.

Trying to add two data sources/types to a SelectableButton in RecycleView

Sorry if this is a ridiculous question. I'm new to programming and so far I've been able to lurk my way through problems that have already been answered, but I can't find anything that answers this particular issue, so here I am with my first ever question to the Stack community. Yay!
I'm writing a fresh produce stock management app in Python/Kivy, using an SQLite database. I've got a RecycleView list with SelectableButton viewclass, and have it contained within a GridLayout.
I have a class method (getItems) within ItemList that extracts the item(string) and quantity(real) from the database, and adds it to a ListProperty variable (data_all), which is where the SelectableButton should in turn get its labels from.
In my example, I've commented out this method, and populated the list in the format that the getItems should do. Actually, as I write this, I wonder if there is a critical difference between the way SQLite stores REAL values, and how python stores float values.
Anyway, ultimately, I'm having trouble displaying the item next to it's quantity in the Recycle view list.
The closest I've come to having it work is to make a 2-column GridLayout, with the item name on the left as a Button, and the quantity on the right as a Label. However, when the list exceeds the bottom of the screen, they scroll independently of one another, as per example 1 below. This is not what I want as the items and quanitities should be next to one another.
Python:
import kivy
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.button import Button
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.properties import ObjectProperty, BooleanProperty, ListProperty, StringProperty, ObjectProperty, NumericProperty, DictProperty
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
import string
#import sqlite3
class EditItemPopup(Popup):
obj = ObjectProperty(None)
obj_text = StringProperty("")
#The full code also includes quantity data to be displayed here, but that's irrelevant to the question
def __init__(self, obj, **kwargs):
super(EditItemPopup, self).__init__(**kwargs)
self.obj = obj
self.obj_text = self.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 = EditItemPopup(self)
popup.open()
def update_changes(self, txt):
self.text = txt
class ItemList(Widget):
'''User selects, adds, edits, and deletes items and quanitities'''
#this is how the data is extracted from the SQLite db
data_all = ListProperty(['Apples', 3.0, 'Bananas', 12.0, 'Lettuce', 16.5, 'Asparagus', 3.0])
def __init__(self, **kwargs):
super(ItemList, self).__init__(**kwargs)
# self.getItems()
return
#This method, and the invocation for it in the __init__ methd is how I would typically pull data from the SQLite db. I've left it commented here and open to constructive criticism
# def getItems(self):
# c.execute("SELECT item, quantity FROM itemTable ORDER BY item ASC")
# rows = c.fetchall()
# for row in rows:
# for col in row:
# self.data_all.append(col)
class ColStock(App):
def build(self):
return ItemList()
if __name__ == "__main__":
ColStock().run()
Kivy:
<EditItemPopup>:
title: "Edit Item"
size_hint: None, None
size: 400, 500
auto_dismiss: False
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint_y: None
height: 50
cols: 3
Label:
text: root.obj_text
Button:
size_hint_y: None
height: 50
text: "OK, thanks"
on_press: root.dismiss()
<SelectableButton>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected==True else (0, 0, 0, 0)
Rectangle:
pos: self.pos
size: self.size
<ItemList>:
GridLayout:
size: root.width,root.height
cols: 2
RecycleView:
viewclass: 'SelectableButton'
data: [{'text': str(x)} for x in root.data_all[0::2]]
SelectableRecycleGridLayout:
cols: 1
default_size: None, dp(30)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
RecycleView:
viewclass: 'Label'
data: [{'text': str(x)} for x in root.data_all[1::2]]
RecycleGridLayout:
cols: 1
default_size: None, dp(30)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
I tried working around this by instead creating one GridLayout column, with the item name, a string seperater, and quantity all on the same button, and an rpartition("......qty: ') in the EditItemPopup init method to extract only the item name.
However, everything I've tried returns an error, usually a TypeError.
Python:
import kivy
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.button import Button
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.properties import ObjectProperty, BooleanProperty, ListProperty, StringProperty, ObjectProperty, NumericProperty, DictProperty
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
import string
#import sqlite3
class EditItemPopup(Popup):
obj = ObjectProperty(None)
obj_text = StringProperty("")
item_name = StringProperty("")
item_qty = StringProperty("")
#The full code also includes quantity data to be displayed here, but that's irrelevant to the question
def __init__(self, obj, **kwargs):
super(EditItemPopup, self).__init__(**kwargs)
self.obj = obj
self.obj_text = self.obj.text
self.item_tuple = self.obj_text.rpartition("......qty: ")
self.item_name = self.item_tuple[0]
self.item_qty = self.item_tuple[2]
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 = EditItemPopup(self)
popup.open()
def update_changes(self, txt):
self.text = txt
class ItemList(Widget):
'''User selects, adds, edits, and deletes items and quanitities'''
#this is how the data is extracted from the SQLite db
data_all = ListProperty(['Apples', 3.0, 'Bananas', 12.0, 'Lettuce', 16.5, 'Asparagus', 3.0])
def __init__(self, **kwargs):
super(ItemList, self).__init__(**kwargs)
# self.getItems()
return
#This method, and the invocation for it in the __init__ methd is how I would typically pull data from the SQLite db. I've left it commented here and open to constructive criticism
# def getItems(self):
# c.execute("SELECT item, quantity FROM itemTable ORDER BY item ASC")
# rows = c.fetchall()
# for row in rows:
# for col in row:
# self.data_all.append(col)
class ColStock(App):
def build(self):
return ItemList()
if __name__ == "__main__":
ColStock().run()
Kivy:
<EditItemPopup>:
title: "Edit Item"
size_hint: None, None
size: 400, 500
auto_dismiss: False
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint_y: None
height: 50
cols: 3
Label:
text: root.item_name
Label:
text: root.item_qty
Button:
size_hint_y: None
height: 50
text: "OK, thanks"
on_press: root.dismiss()
<SelectableButton>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected==True else (0, 0, 0, 0)
Rectangle:
pos: self.pos
size: self.size
<ItemList>:
GridLayout:
size: root.width,root.height
cols: 1
RecycleView:
viewclass: 'SelectableButton'
#added self.obj_text.rpartition("......qty: ") for the popup window to extract item name as string
data: [{'text': str(x) + "......qty: " + "Quantity goes here"} for x in root.data_all[0::2]]
SelectableRecycleGridLayout:
cols: 1
default_size: None, dp(30)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
I've probably violated every convention in programming, and I know it's a hack, but I'd be happy with anything that works. Thank you in advance to anyone who can shed some light on this. :)
So, I've been stuck on this for about 2 or 3 weeks, and the day I ask for help on S.E is the day I discover a solution through trial and error that works for my needs.
I can use the following syntax in the kivy file which adds both item and quantity to each SelectableButton by iterating each pair in the data_all list:
data: [{'text': str(x) + "......qty: " + str(y)} for x,y in zip(root.data_all[0::2],root.data_all[1::2])]
I then add the following to the Popup's init method (python file) to extract only the item name for the label:
self.obj_tuple = self.obj_text.rpartition("......qty: ")
self.item_name = self.obj_tuple[0]
The self.item_name can then also be used to reference the correct row in the SQLite db.
Other suggestions are welcome of course, but I hope this helps someone who is facing similar issues.

Unknown class error even if class is defined Kivy/recycle view

I have this snippet from my design.kv file:
<Track>:
on_release:
root.print_data(self.text)
RecycleView:
viewclass: 'Track'
RecycleGridLayout:
cols: 1
default_size_hint: 1, None
orientation: 'vertical'
However it returns an error:
The class 'Track was defined as seen in the snippet above as well as in my python code.
I tried setting the viewclass to 'Button' and it worked but it just returned a button which is not the intended behavior.
What am I getting wrong here?
Thanks :)
The whole code of my python and kivy files are right here: https://github.com/Jezrianne/ANTS
Just in case the error does not originate from the snippet above :)
Root Widget - Screen
The following example illustrates using Screen widget as the root widget and combined with RecycleView widget.
main.py
from kivy.app import App
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
Builder.load_string('''
#:kivy 1.11.0
<Track>:
on_release:
root.print_data(self.text)
<RootWidget>:
RecycleView:
id: rv
viewclass: 'Track'
SelectableRecycleGridLayout:
cols: 1
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
''')
class Track(RecycleDataViewBehavior, Button):
def print_data(self, text):
print("\nprint_data: text=", text)
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class RootWidget(Screen):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.ids.rv.data = [{'text': str(x)} for x in range(100)]
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
Root Widget - RecycleView
There is no attribute, orientation: for GridLayout. Please remove it from your kv file.
You need to implement the following:
Snippet
class Track(RecycleDataViewBehavior, Button):
def print_data(self, text):
print("\nprint_data: text=", text)
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
Example
main.py
from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.button import Button
class Track(RecycleDataViewBehavior, Button):
def print_data(self, text):
print("\nprint_data: text=", text)
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
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()
test.kv
#:kivy 1.11.0
<Track>:
on_release:
root.print_data(self.text)
<RV>:
viewclass: 'Track'
SelectableRecycleGridLayout:
cols: 1
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
Output

Kivy get name/text from clicked ListItemButton

i have a question about dynamic listview in python with kivy. So the listview is working, but know i want to get the text from the clicked listview item, to push them to the python file. But i can't finde any solution that works for me. I think one way to do this is with "on_selection_change" but i'm not able to get this run.
So here is my code
BoxLayout:
pos: 130,10
size: 500,400
ListView:
adapter:
ListAdapter(data=root.my_data,
args_converter=lambda row_index, an_obj: {'text': an_obj,'size_hint_y': 28,'height': 40, 'font_size': 20},
selection_mode='single',
allow_empty_selection=True,
cls=ListItemButton)
<ListItemButton>:
#on_selection_change=self.on_selection_change
on_release: app.getname()
and my python file
class PCScreen(GridLayout,Screen):
filename = open("/home/pi/Documents/ppcont/config/name.txt")
my_data = ListProperty(filename)
def getname(self):
pass
As for ListView, use ModalView to control selection and invoke getname() method. Please refer to the two examples (ListView and RecycleView) for details.
Note
ListView has been deprecated since version 1.10.0, use RecycleView instead.
Example 1 - ListView
mainlistview.py
from kivy.app import App
from kivy.uix.modalview import ModalView
from kivy.properties import ObjectProperty
class MyListView(ModalView):
list_view = ObjectProperty(None)
def __init__(self, **kwargs):
super(MyListView, self).__init__(**kwargs)
self.list_view.adapter.bind(on_selection_change=self.callback_function)
def callback_function(self, instance):
print(instance)
App.get_running_app().getname()
class MainListViewApp(App):
title = "ListView ListItemButton Demo"
def build(self):
return MyListView()
def getname(self):
print("App.getname() - Called")
if __name__ == '__main__':
MainListViewApp().run()
mainlistview.kv
#:kivy 1.10.0
#:import lv kivy.uix.listview
#:import la kivy.adapters.listadapter
<MyListView>:
list_view: list_view
ListView:
id: list_view
adapter:
la.ListAdapter(
data=["Item #{0}".format(i) for i in range(100)],
selection_mode='single',
allow_empty_selection=True,
cls=lv.ListItemButton)
Output - ListView
Example 2 - RecycleView
mainrecycleview.py
from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.properties import ObjectProperty
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout):
""" Adds selection and focus behaviour to the view. """
pass
class SelectableButton(RecycleDataViewBehavior, Button):
""" Add selection support to the Label """
index = None
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_release(self):
print("Pressed & released on", self.text)
App.get_running_app().getname()
class RV(RecycleView):
rv_layout = ObjectProperty(None)
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': "Button " + str(x), 'id': str(x)} for x in range(100)]
class MainRecycleViewApp(App):
title = "RecycleView Button Demo"
def build(self):
return RV()
def getname(self):
print("RecycleView: App.getname() - Called")
if __name__ == "__main__":
MainRecycleViewApp().run()
mainrecycleview.kv
#:kivy 1.10.0
<SelectableButton>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (0.0, 0.9, 0.1, 0.3)
Rectangle:
pos: self.pos
size: self.size
<RV>:
rv_layout: layout
viewclass: 'SelectableButton'
SelectableRecycleBoxLayout:
id: layout
default_size: None, dp(26)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: "vertical"
Output - RecycleView
I really think you should dump ListView, its deprecated (from the docs):
ListAdapter:
Module: kivy.adapters.listadapter Added in 1.5
Deprecated since version 1.10.0: The feature has been deprecated.
you should really use https://kivy.org/docs/api-kivy.uix.recycleview.html

How to refresh kivy RecycleView every time data changes?

I am trying to create a simple attendance app.
When program is launched all labels are in deselected list
Expected behavior: when any label is selected data moves to the selected list and now selected labels are in the end of the joined list. Then RecycleView refreshes to display this change.
So I managed to make the data to move from one list to another, but I can't make RecycleView to refresh
I tried using ids but failed
I hope someone can help me. I think this is a routine problem for people who are knowledgeable, but for noobs like me it is not.
I am asking question on this site for the first time so sorry in advance
here is the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.properties import ListProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from datetime import datetime
import kivy
from kivy.config import Config
Config.set('graphics', 'width', '300')
Config.set('graphics', 'height', '500')
importedlist = ['Novella Varela', 'Caroll Faircloth', 'Douglas Schissler',
'Rolande Hassell', 'Hayley Rivero', 'Niesha Dungy', 'Winfred Dejonge', 'Venetta Milum']
deselected_list = importedlist[:]
selected_list = []
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, Label):
''' 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(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.
and add/remove items from lists
'''
self.selected = is_selected
if self.selected and self.text in deselected_list:
selected_list.append(self.text)
deselected_list.remove(self.text)
print(selected_list)
elif not self.selected and self.text in selected_list:
deselected_list.append(self.text)
selected_list.remove(self.text)
print(deselected_list)
class RV(RecycleView):
# this needs to be updated every time any label is selected or deselected
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = ([{'text': str(row)} for row in sorted(deselected_list)]
+ [{'text': str(row)} for row in sorted(selected_list)])
class Screen(BoxLayout):
now = datetime.now()
def nowdate(self):
return self.now.strftime('%d')
def nowmonth(self):
return self.now.strftime('%m')
def nowyear(self):
return self.now.strftime('%y')
def nowhour(self):
return self.now.strftime('%H')
def nowminute(self):
return self.now.strftime('%M')
Builder.load_string('''
#:import datetime datetime
<Screen>:
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height: 30
TextInput:
id: date
text: root.nowdate()
TextInput:
id: date
text: root.nowmonth()
TextInput:
id: date
text: root.nowyear()
TextInput:
id: date
text: root.nowhour()
TextInput:
id: date
text: root.nowminute()
Button:
RV:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(45)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
Button:
size_hint_y: None
height: 30
<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
''')
class TestApp(App):
def build(self):
return Screen()
if __name__ == '__main__':
TestApp().run()
Alright, so I did actually stumble a few times trying to sort this out but I think I've got it. What I did was create two recycle views and a CustomLabel that has access to ButtonBehaviors so you can use 'on_press' instead of 'on_touch_down' (which propagates through the entire tree rather than the interacted with element).
Example Video
The py file:
from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.floatlayout import FloatLayout
# Create a Custom ButtonLabel that can use on_press
class ButtonLabel(ButtonBehavior, Label):
# App.get_running_app() lets us traverse all the way through our app from
# the very top, which allows us access to any id. In this case we are accessing
# the content of our selected_list_view of our app
#property
def selected_list_content(self):
return App.get_running_app().root.ids.selected_list.ids.content
# And in this case, we're accessing the content of our deselected_list_view
#property
def deselected_list_content(self):
return App.get_running_app().root.ids.deselected_list.ids.content
# This is our callback that our Label's will call when pressed
def change_location(self):
# If the label's parent is equal* the selected list, we remove the label from its
# parent, and then we add it to the other list
if self.parent == self.selected_list_content:
self.parent.remove_widget(self)
self.deselected_list_content.add_widget(self)
# If the label's parent is not the selected list, then it is the deselected list
# so we remove it from its parent and add it to the selected list
else:
self.parent.remove_widget(self)
self.selected_list_content.add_widget(self)
#* Note: Kivy uses weak references. This is why we use ==, and not 'is'
# We create a CustomRecycleView that we will define in our kv file
class CustomRecycleView(RecycleView):
pass
class MainWindow(FloatLayout):
pass
class ExampleApp(App):
def build(self):
# We create an instance of the MainWindow class, so we can access its id
# to import our list. Otherwise we would have nothing to add the list too
main_window = MainWindow()
importedlist = ['Novella Varela', 'Caroll Faircloth', 'Douglas Schissler',
'Rolande Hassell', 'Hayley Rivero', 'Niesha Dungy', 'Winfred Dejonge', 'Venetta Milum']
# We create a Label for each Name in our imported list, and then add it
# to the content of selected list as a default
# I'm sure you'll be importing our own lists in a different manner
# This is just for the example
for name in importedlist:
NameLabel = ButtonLabel(text=(name))
main_window.ids.selected_list.ids.content.add_widget(NameLabel)
return main_window
if __name__ == '__main__':
ExampleApp().run()
The kv file:
#:kivy 1.10.0
# We create a reference to the ButtonLabel class in our py file
<ButtonLabel>:
# We add our callback to our ButtonLabels on press event, on_press
on_press: root.change_location()
# We create a reference to our CustomRecycleView class in our py file
<CustomRecycleView>:
# We create a GridLayout to store all of the content in our RecycleView
GridLayout:
# We give it the id content so we can define the two property values in
# ButtonLabel class in the py file
id: content
size_hint_y: None
# One column because we want it to be vertical list list
cols: 1
# This set up, as well as size_hint_y set to None
# is so we can scroll vertically without issue
row_default_height: 60
height: self.minimum_height
<MainWindow>:
# We then create two instances of our CustomRecycleView, give them the ids
# referenced by the ButtonLabel methods as well as give them equal share of the
# screen space so they do not step on each others toes
# The canvas here is just for prototyping purposes to make sure they are the
# properly defined sizes. You can do whatever with them you would like tbh.
CustomRecycleView:
id: selected_list
size_hint: 1, .5
pos_hint: {'x': 0, 'y': .5}
canvas:
Color:
rgba: 100, 0, 0, .2
Rectangle:
size: self.size
pos: self.pos
CustomRecycleView:
id: deselected_list
size_hint: 1, .45
canvas:
Color:
rgba: 0, 0, 100, .2
Rectangle:
size: self.size
pos: self.pos

Categories