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
Related
For some reason MDExpansionPanel doesn't behave as it should in a RecycleView, each time you expand the list, the more it deviates from the pattern. I don't know if there is something wrong with my code or the MDExpansionPanel was not made for a RecycleView
main.py
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.expansionpanel import (MDExpansionPanel,
MDExpansionPanelOneLine)
Builder.load_string("""
<MyRecycleView>:
viewclass: "MyExpansionPanel"
effect_cls: "ScrollEffect"
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(10)
<Content>:
adaptive_height: True
IconLeftWidget:
icon: "account"
""")
class MyRecycleView(RecycleView):
pass
class Content(MDBoxLayout):
pass
class MyExpansionPanel(MDExpansionPanel, RecycleDataViewBehavior):
text = StringProperty()
def __init__(self, **kwargs):
self.panel_cls = MDExpansionPanelOneLine()
super().__init__(**kwargs)
def refresh_view_attrs(self, rv, index, data):
self.panel_cls.text = data["text"]
return super().refresh_view_attrs(rv, index, data)
class MyApp(MDApp):
def build(self):
recycle = MyRecycleView()
recycle.data = [
{
"icon":"arrow-right",
"text":"Test",
"content":Content()
}for _ in range(5)
]
return recycle
MyApp().run()
Result:
Any idea how to resolve this?
I'm trying to better understand how the RecycleView functions. Seems like only practical examples will teach me. Docs aren't helping. Kindly have a look at my current screen in the pic below.
Now following are what I'm trying to achieve.
Add/Remove new rows to/from the list.
The first column sl/no should maintain the order even after deleting from in between.
How to achieve 'sort' function? Is it by taking the data into python and do the sort and then update that to the RV again ?
Please find the both .py and .kv codes below.
rv_main.py
import os
os.environ['KIVY_GL_BACKEND'] = 'gl'
import kivy
kivy.require('1.11.0')
from kivy.uix.boxlayout import BoxLayout
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.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.properties import ObjectProperty
from kivy.properties import ListProperty, BooleanProperty
from kivy.properties import NumericProperty
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
#-----------------------------------------------------------------------
class RecycleViewRow(RecycleDataViewBehavior, BoxLayout):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
slno = StringProperty('')
typ = StringProperty('')
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(RecycleViewRow, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(RecycleViewRow, 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:
pass
else:
pass
def delete_row(self,rv, index, is_selected):
if is_selected:
rv.data.pop(index)
rv.layout_manager.clear_selection()
#-----------------------------------------------------------------------
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
#fetch data from the database
self.data = [{'slno': str(x+1),'typ': 'default'} for x in range(4)]
#-----------------------------------------------------------------------
class DataTable(BoxLayout):
def addRow(self):
pass
def removeRow(self):
pass
#-----------------------------------------------------------------------
class RvMainApp(App):
def build(self):
return DataTable()
if __name__ == '__main__':
RvMainApp().run()
rvmain.kv
#: kivy 1.11.0
<RecycleViewRow>:
id: rv
slno: ""
typ: ""
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0.4, 0.4, 0.4, 1)
Rectangle:
pos: self.pos
size: self.size
orientation: 'horizontal'
size_hint: 1.0, 1.0
Label:
text: root.slno
size_hint_x : 1.0
Label:
text: root.typ
size_hint_x : 1.0
#----------------------------------------------------------------
<RV>:
id : rv
viewclass: 'RecycleViewRow'
SelectableRecycleBoxLayout:
default_size: None, dp(40)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
#----------------------------------------------------------------
<DataTable>:
orientation : 'vertical'
Button:
BoxLayout:
Button:
RV:
Button:
BoxLayout:
Button:
text: "Add"
on_release: rv.data.append({"slno": "?", "typ": 'default'})
Button:
text: "Remove"
on_release:
You edit the data list of the recycleview. The data will be sorted as it is sorted in that list.
So here is an example of the add remove feature:
from kivy.app import App
from kivy.lang import Builder
KV = '''
<Row#BoxLayout>:
ind: 1
Button:
text: str(root.ind)
Button:
text: "default"
BoxLayout:
ind: 1
orientation: "vertical"
Button:
BoxLayout:
Button:
RecycleView:
id: rv
data: [{"text":"first","ind":1}]
viewclass: 'Row'
RecycleBoxLayout:
default_size_hint: 1, None
default_size: None, dp(56)
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
Button
BoxLayout:
Button:
text: "Add"
on_release:
root.ind += 1
rv.data.append({"ind": root.ind})
Button:
text: "Remove"
on_release:
root.ind = root.ind - 1 if root.ind > 0 else 0
if len(rv.data): rv.data.pop(-1)
'''
class Test(App):
def build(self):
self.root = Builder.load_string(KV)
return self.root
Test().run()
And here is an example on how to sort the data list by some key. In this case ind.
from kivy.app import App
from kivy.lang import Builder
KV = '''
RecycleView:
viewclass: 'Label'
data: sorted([{"text":"!", "ind":3},{"text":"world", "ind":2},{"text":"hello", "ind":1}], key=lambda k: k["ind"])
RecycleBoxLayout:
id: layout
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
'''
class TestApp(App):
def build(self):
return Builder.load_string(KV)
if __name__ == '__main__':
TestApp().run()
How can I pass data from RequestRecycleView to refresh_view_data? I tried with global variables and instantiating data in RequestRecycleView but still can't trigger refresh_view_data by appending Observable data. It is working when I return RequestRecycleView as root but I want ScreenManager to be my root.
from kivy.config import Config
Config.set('graphics', 'multisamples', '0')
from random import sample
from string import ascii_lowercase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import BooleanProperty
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
kv = """
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManager:
transition: FadeTransition()
RequestsScreen:
<RequestRow#BoxLayout>
size_hint_y: None
orientation: 'vertical'
row_index: id_row_index.text
row_index:''
pos: self.pos
size: self.size
Label:
id: id_row_index
text: root.row_index
<RequestRecycleView#RecycleView>:
#id: rv
viewclass: 'RequestRow'
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
<RequestsScreen>
name: 'RequestsScreen'
BoxLayout:
orientation: 'vertical'
Label:
text: 'recycle'
RequestRecycleView:
"""
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class RequestRow(RecycleDataViewBehavior):
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
self.row_index = str(index)
self.row_content = data['text']
return super(RequestRow, self).refresh_view_attrs(
rv, index, data)
class ScreenManagement(ScreenManager):
pass
class RequestRecycleView(RecycleView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.data = []
for r in range(30):
row = {'text': ''.join(sample(ascii_lowercase, 6))}
self.data.append(row)
class RequestsScreen(Screen):
pass
Builder.load_string(kv)
sm = ScreenManagement()
sm.add_widget(RequestsScreen(name = 'requests'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
By modifying
<RequestRow#BoxLayout>
size_hint_y: None
orientation: 'vertical'
row_index: id_row_index.text
row_index:''
pos: self.pos
size: self.size
Label:
id: id_row_index
text: root.row_index
to
<RequestRow#BoxLayout>
text: 'abba'
size_hint_y: None
orientation: 'vertical'
row_index: id_row_index.text
row_index:''
pos: self.pos
size: self.size
Label:
id: id_row_index
text: self.parent.text
results in working code. Note that the dictionary keys in the data are expected to be attributes of the viewclass. The added text attribute in RequestRow, provides that attribute, and the text: self.parent.text in the Label places that text (from the viewclass) into the Label. Also, you can replace the lines:
Builder.load_string(kv)
sm = ScreenManagement()
sm.add_widget(RequestsScreen(name = 'requests'))
with just:
sm = Builder.load_string(kv)
since your kv file specifies the ScreenManager as the root object.
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
I am trying to implement a custom closable tab header in kivy.
What I did was combine a class:TabbedPanelHeader object with a custom class:CloseButton object. Both of these widgets are inside a class:BoxLayout, side-by-side.
However, once I add this into a class:TabbedPanel object, nothing shows up..
I am not sure how to move forward and would greatly appreciate all the help!
Below is the relevant part of the code.
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.graphics import *
from kivy.uix.tabbedpanel import TabbedPanelHeader
class CloseButton(ButtonBehavior, Image):
def __init__(self, **kwargs):
super(CloseButton, self).__init__(**kwargs)
self.source = 'atlas://data/images/defaulttheme/close'
self.size_hint_x = .2
def on_press(self):
self.source = 'atlas://data/images/defaulttheme/checkbox_radio_off'
def on_release(self):
self.source = 'atlas://data/images/defaulttheme/checkbox_radio_off'
## do the actual closing of the tab
class ClosableTabHeader(BoxLayout):
def __init__(self, **kwargs):
super(ClosableTabHeader, self).__init__(**kwargs)
self.size = (100, 30)
self.size_hint = (None, None)
self.canvas.before.add(Color(.25, .25, .25))
self.canvas.before.add(Rectangle(size=(105, 30)))
self.add_widget(TabbedPanelHeader(background_color=(.65, .65, .65, 0), text='testing'))
self.add_widget(CloseButton())
if __name__ == '__main__':
from kivy.app import App
class TestApp(App):
def build(self):
return ClosableTabHeader()
TestApp().run()
Here is some code which comes close to achieve what you are trying to achieve
from kivy.app import App
from kivy.animation import Animation
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
from kivy.factory import Factory
from kivy.lang import Builder
class CloseableHeader(TabbedPanelHeader):
pass
class TestTabApp(App):
def build(self):
return Builder.load_string('''
TabbedPanel:
do_default_tab: False
FloatLayout:
BoxLayout:
id: tab_1_content
Label:
text: 'Palim 1'
BoxLayout:
id: tab_2_content
Label:
text: 'Palim 2'
BoxLayout:
id: tab_3_content
Label:
text: 'Palim 3'
CloseableHeader:
text: 'tab1'
panel: root
content: tab_1_content.__self__
CloseableHeader:
text: 'tab2'
panel: root
content: tab_2_content.__self__
CloseableHeader:
text: 'tab3'
panel: root
content: tab_3_content.__self__
<CloseableHeader>
color: 0,0,0,0
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: root.size
padding: 3
Label:
id: lbl
text: root.text
BoxLayout:
size_hint: None, 1
orientation: 'vertical'
width: 22
Image:
source: 'tools/theming/defaulttheme/close.png'
on_touch_down:
if self.collide_point(*args[1].pos) :\
root.panel.remove_widget(root); \
''')
if __name__ == '__main__':
TestTabApp().run()
It is based on https://github.com/kivy/kivy/blob/master/examples/widgets/tabbed_panel_showcase.py