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.
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 have a music app written with kivymd that scans your default download directory and extracts information about your audio files. (Basically retrieving the IDV3 tags and other things)
I only have 20 audio files at max on my desktop which shouldn't be a problem but I decided to test my program with many audio (like 120) files and the results were horrible.
The interface was so slow that it barely worked. How am I supposed to display large set of widgets in kivy without causing such catastrophic performance degradation?
Also, how can I make the loading of the widgets asynchronous so that they don't take up a lot of time on startup as well?
A minimal reproducible example of my code:
import os
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
class Song:
"""Class for extracting information about an audio file."""
def __init__(self, path: str):
self._path = path
self._title = "My Song"
self._artist = "Unknown artist"
# the album cover is going to a texture retrieved from the file itself
# but in this case, I will refer to a file.
self._album_cover = "Default-Album-Cover.jpg"
#property
def path(self):
return self._path
#property
def title(self):
return self._title
#property
def artist(self):
return self._artist
#property
def album_cover(self):
return self._album_cover
class SongCard(ButtonBehavior, RectangularRippleBehavior, MDBoxLayout):
"""Custom widget for creating cards."""
song_obj = ObjectProperty(Song("dummy song"), rebind=True)
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.kv = Builder.load_string('''
#:kivy 2.0.0
<SongCard>:
orientation: "vertical"
size_hint_y: None
height: "300dp"
radius: ("10dp",)
canvas.before:
Color:
rgba: app.theme_cls.bg_dark
RoundedRectangle:
pos: self.pos
size: self.size
radius: root.radius
FitImage:
source: root.song_obj.album_cover
MDLabel:
text: root.song_obj.title
adaptive_height: True
MDLabel:
text: root.song_obj.artist
adaptive_height: True
ScrollView:
do_scroll_x: False
MDGridLayout:
id: song_grid
cols: 2
adaptive_height: True
spacing: "10dp"
padding: "10dp"
''')
def build(self):
return self.kv
def on_start(self):
for _ in range(25):
for audio_file in os.listdir(os.path.join(os.path.expanduser('~'), "Downloads")):
if audio_file.endswith(".mp3"):
song_obj = Song(audio_file)
self.kv.ids.song_grid.add_widget(
SongCard(
song_obj=song_obj
)
)
if __name__ == '__main__':
MainApp().run()
You can use the RecycleView class to manage the amount of widgets and help your program to be able to accept a big amount of widgets, the ScrollView is great but recycleview manages data as dictionary and then you can use two BoxLayout class under a BoxLayout widget to have the GridLayout class functionality, I tested this with 213 elements and it works with a lot of widgets and good performance:
import os
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty, StringProperty
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
from kivy.uix.recyclegridlayout import RecycleGridLayout
class Song:
"""Class for extracting information about an audio file."""
def __init__(self, path: str):
self._path = path
self._title = "My Song"
self._artist = "Unknown artist"
# the album cover is going to a texture retrieved from the file itself
# but in this case, I will refer to a file.
self._album_cover = "Default-Album-Cover.jpg"
## #property
## def path(self):
## return self._path
##
## #property
## def title(self):
## return self._title
##
## #property
## def artist(self):
## return self._artist
##
## #property
## def album_cover(self):
## return self._album_cover
class SongCard(MDBoxLayout):
"""Custom widget for creating cards."""
#song_obj = ObjectProperty(Song("dummy song"), rebind=True)
song_obj = StringProperty()
class B2(ButtonBehavior, RectangularRippleBehavior, MDBoxLayout):
"""Custom widget for creating cards."""
pass
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.kv = Builder.load_string('''
#:kivy 2.0.0
<SongCard>:
orientation: "vertical"
size_hint_y: None
height: "300dp"
#radius: ("10dp",)
BoxLayout:
padding: dp(10)
spacing: dp(5)
B2:
padding: dp(10)
canvas.before:
Color:
rgba: [0,1,0,.2]
RoundedRectangle:
pos: self.pos
size: self.size
radius: [30,]
orientation: "vertical"
spacing: dp(5)
FitImage:
size_hint: 1,1
source: "Help5/cc.png" #root.song_obj.album_cover
MDLabel:
text: root.song_obj[:40] #.title
adaptive_height: True
MDLabel:
text: " - jbsidis" #.artist
adaptive_height: True
B2:
padding: dp(10)
canvas.before:
Color:
rgba: [1,0,0,.2]
RoundedRectangle:
pos: self.pos
size: self.size
radius: [30,]
orientation: "vertical"
spacing: dp(5)
FitImage:
source: "Help5/cc.png" #root.song_obj.album_cover
MDLabel:
text: root.song_obj[:40] #.title
adaptive_height: True
MDLabel:
text: " - jbsidis" #.artist
adaptive_height: True
Screen:
BoxLayout:
orientation: "vertical"
#do_scroll_x: False
RecycleSupportGridLayoutjbsidis:
id: song_grid
key_viewclass: 'viewclass'
key_size: 'height'
RecycleBoxLayout:
padding: dp(10)
default_size: None, dp(248)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<RecycleSupportGridLayoutjbsidis#RecycleView>:
''')
def build(self):
return self.kv
def on_start(self):
#for _ in range(25):
for audio_file in os.listdir("/media/jbsidis/Android/Q/D/Anthony Robbins/"): #os.path.join(os.path.expanduser('~'), "Downloads")
#if audio_file.endswith(".mp3"):
#song_obj = Song(audio_file)
self.kv.ids.song_grid.data.append(
{
"viewclass": "SongCard",
"song_obj": audio_file
}
)
## {
## "viewclass": "CustomOneLineIconListItem",
## "icon": name_icon,
## "text": name_icon,
## "on_release": lambda:Clipboard.copy(name_icon),
## }
## self.kv.ids.song_grid.add_widget(
## SongCard(
## song_obj=song_obj
## )
## )
if __name__ == '__main__':
MainApp().run()
Pictures:
What i'm trying to do is find a way to refresh a table made by using recycleview,
If you press one of the label created, my code delete data from table, but not refresh the table on a graphic level
Obviously if you press the same label 2 times, python gives ValueError...
Here's my code:
temp.py
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.properties import BooleanProperty
from kivy.properties import ListProperty
class Sfondo_tabella(RecycleDataViewBehavior, Label):
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos) and self.selectable:
x = Screen_1()
x.remove(self.text)
class Screen_1(Screen):
data_items = ListProperty([])
data_items = ['1', '2', '3']
def remove(self, data):
self.data_items.remove(data)
class TempApp(App):
pass
if __name__ == '__main__':
TempApp().run()
temp.kv
ScreenManager:
id: screen_manager
name: "screen_manager"
Screen_1:
id: screen_1
name: "Screen_1"
manager: screen_manager
<Sfondo_tabella>:
# Draw a background to indicate selection
color: 0,0,0,1
font_size: self.height * 0.5
text_size: self.width, None
valign: 'top'
halign: 'center'
canvas.before:
Color:
rgba: (1, 1, 1, 1)
Rectangle:
pos: self.pos
size: self.size
canvas:
Color:
rgba:0,0,0,1
Line:
width:0.5
rectangle:(self.x,self.y,self.width,self.height)
<Screen_1>:
BoxLayout:
RecycleView:
viewclass: 'Sfondo_tabella'
data: [{'text': str(x)} for x in root.data_items]
RecycleGridLayout:
cols: 10
size_hint: 1, None
default_size: None, dp(20)
default_size_hint: 1, None
height: self.minimum_height
width: self.minimum_width
In your code you are not updating the RecycleView data. The refresh_view_attrs function should be overloaded to get the index of the label clicked/touched upon and then the data needs to be updated accordingly. Following is the updated code for temp.py:
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.properties import BooleanProperty
from kivy.properties import ListProperty
class Sfondo_tabella(RecycleDataViewBehavior, Label):
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
self.index = index
return super(Sfondo_tabella, self).refresh_view_attrs(rv, index, data)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos) and self.selectable:
x = Screen_1()
x.remove(self.text)
self.parent.parent.data.pop(self.index)
class Screen_1(Screen):
data_items = ListProperty([])
data_items = ['1', '2', '3']
def remove(self, data):
self.data_items.remove(data)
class TempApp(App):
pass
if __name__ == '__main__':
TempApp().run()
This will update properly the data of RecycleView and the GUI accordingly.
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()
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