Get selected node from Kivy Recycleview - python

I've got some programs written in previous versions of Kivy that use ListViews. Using ListViews, it was quite easy to get the selected node via the adapter. However, it is much less clear how to do this with a RecycleView. Now, it is the case that rv.layout_manager.selected_nodes can be used to get the selected value, but there are also times where I'm interested in the actual node. Also, the following snippet can be used to generate the nodes, but apparently, they aren't the actual nodes in the RecycleView.
opts = rv.layout_manager.view_opts
for i in range(len(rv.data)):
s = rv.view_adapter.get_view(i, rv.data[i], opts[i]['viewclass'])
print s.text, s.selected
I'd be interested in finding a way of getting the selected node from the RecycleView.
Full code:
import random
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
#Aside: the code in the string would need to be indented back one tab, but it's like this for SO formatting
Builder.load_string('''
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
key_selection: "True"
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: False
touch_multiselect: True
touch_deselect_last: True
''')
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. '''
self.selected = is_selected
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(random.random())} for x in range(20)]
class TestApp(App):
def build(self):
self.rv = RV()
self.rv.layout_manager.bind(selected_nodes=self.selectionChange)
return self.rv
def selectionChange(self, inst, val):
print inst, val
if __name__ == '__main__':
b = TestApp()
b.run()

please show us your code. in the rv.data you can add a parameter 'selected' = 0 when the node is not selected and 1 when it is this is an example:
class TestRecycleView(RecycleView):
def __init__(self, **kwargs):
super(TestRecycleView, self).__init__(**kwargs)
self.data = [{'name': 'test', 'ind':0,'selected': 0}]
class TestBox(RecycleDataBehaviour, BoxLayout):
def action(self, boss):
if boss.data[self.ind]['selected'] == 0:
boss.data[self.ind]['selected'] = 1
else:
boss.data[self.ind]['selected'] = 0
the kv:
<TestRecycleView>:
viewclass: 'TestBox'
RecycleBoxLayout:
default_size: None, 440
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<TestBox>:
Button:
on_press: root.action(root.parent.parent)
if i understand well you want to print the selectedLabel value:
class TestApp(App):
def build(self):
self.rv = RV()
self.rv.layout_manager.bind(selected_nodes=self.selectionChange)
return self.rv
def selectionChange(self, inst, val):
print self.rv.data[val[0]]['text']
proof(image link)

Related

Kivy get all selected items in RecycleView

Problem
I've been messing around with Kivy's RecycleViews in hopes of creating a list builder for one of my projects. I am working out of the second example on Kivy docs' RecycleView page, as it is already almost what I am trying to create. For reference, the example contains a list where multiple items can be selected or unselected.
My main issue is that I have been unable to find any way to get any sort of list containing all of the selected items in the RecycleView. I thought at the very least, I could have a separate list in the RecycleView containing all of the selected items using the apply_selection() method in the SelectableLabel class, however I am unable to distinguish unselecting a SelectableLabel from moving the label outside of the RecycleView's view.
Code
listBuilder.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.datamodel import RecycleDataModel
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
class TestScreen(Screen):
''' Screen for testing stuff '''
def pressed(self):
print(f'Selected: {self.ids.rv.data}')
self.ids.rv.data.append({'text': '200'})
class RV(RecycleView):
''' Recycle View '''
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(100)]
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout, RecycleDataModel):
''' Adds selection and focus behaviour to the view. '''
def on_data_changed(self, **kwargs):
print('Data changed: ', kwargs)
super(SelectableRecycleBoxLayout, self).on_data_changed(**kwargs)
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. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
Builder.load_file('listBuilder.kv')
class MainApp(App):
def build(self):
return TestScreen()
if __name__ == '__main__':
MainApp().run()
listBuilder.kv
#:kivy 1.11
#:import ScrollEffect kivy.effects.scroll.ScrollEffect
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (0.2, 0.2, 0.2, 1) if self.selected else (0.5, 0.5, 0.5, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
viewclass: 'SelectableLabel'
# Scroll effects
effect_cls: ScrollEffect # Disable overscroll
scroll_type: ['bars', 'content']
bar_width: dp(15)
scroll_wheel_distance: dp(90)
# Content of recycle view
SelectableRecycleBoxLayout:
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
<TestScreen>:
name: 'test'
FloatLayout:
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
pos: 0,0
size: self.width, self.height
# Container for recycle view
RV:
id: rv
size_hint: 0.3, 0.5
pos_hint: {'center_x': 0.3, 'center_y': 0.5}
Button:
text: 'Print selected'
size_hint: 0.2, 0.05
pos_hint: {'center_x': 0.8, 'center_y': 0.1}
on_release:
root.pressed()
For anyone wondering why I am using screens in this example--it's because this is test code for a larger program that uses screens.
I'm using Kivy 1.11.1 and Python 3.7.8
Any help is appreciated as I do not really understand yet fully grasp the RecycleView data models.
Thanks!
I found another solution as I started to delve deeper into my project and thought I'd share it here.
There is a built-in way to get the selected nodes; it's accessed through RecycleView.layout_manger.selected_nodes. It returns a list of the indices selected nodes, though it should be noted that they are not in numerical order but in the order that the nodes were selected.
Here are the changes I made to the original code using the new method:
RV class:
class RV(RecycleView):
''' Recycle View '''
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(100)]
def get_selected(self):
''' Returns list of selected nodes dicts '''
return [self.data[idx] for idx in self.layout_manager.selected_nodes]
If you just care about the indices, you don't necessarily need a method, but I figured it'd be nice to get the actual dicts.
The pressed method then looks like:
def pressed(self):
print('Selected:')
for d in self.ids.rv.get_selected():
('\t', d)
The main reason I opted to switch to this method is that the selected dictionary key does not correspond to the selected state of the node. In the program, I had to remove certain items from the list and the new items at the old indices become selected. It's a bit odd, but it makes more sense when thinking about the selection as a list of indices rather than the individual items being selected or not.
For anyone who is having trouble with other list items becoming selected after the original ones were removed, I found this helpful: https://www.reddit.com/r/kivy/comments/6b0pfp/dhjh7q4
If you add selected as a key in your RecycleView data, then you can get what you want like this:
class RV(RecycleView):
''' Recycle View '''
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x), 'selected': False} for x in range(100)]
Then, in the SelectableLabel class:
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
# change selected in data
rv.data[index]['selected'] = self.selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
And, finally, you can assemble the list in the pressed() method:
def pressed(self):
print('Selected:')
rv = self.ids.rv
for d in rv.data:
if d['selected']:
print('\t', d)

Replacing kivy Listview with RecycleView

I am currently trying to replace some features in an app using the ListView with the RecycleView.
From the documentation I can't figure out how to that though.
The current code looks similar to this:
ListView:
id: x
adapter:
sla.SimpleListAdapter(data=[], cls=label.Label)
x.adapter.data.append(‘frank’)
Are there any resources or tips on how to achieve this?
I am trying to use the recycleview because the ListView seems to be deprecated now.
ListView is removed in Kivy version 1.11.0. Below snippets show the equivalent in RecycleView. There are two examples in Kivy documentation for RecycleView.
The snippets below show the equivalent using RecycleView.
Snippets: kv file
RecycleView:
id: x
viewclass: 'Label'
RecycleBoxLayout:
default_size: None, dp(26)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
Snippets: py file
x.data.append({'text‘: 'frank’)
Examples
The following three examples illustrate Kivy RecycleView.
How to fetch data from database and show in table
How to add vertical scroll in RecycleView
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
Builder.load_string('''
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
id: rv
# Optional: ScrollView attributes
bar_width: 5
bar_color: 1, 0, 0, 1 # red
bar_inactive_color: 0, 0, 1, 1 # blue
effect_cls: "ScrollEffect"
scroll_type: ['bars', 'content']
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
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
''')
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. '''
self.selected = is_selected
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(100)]
self.data.insert(0, {'text': 'frank'})
self.data.append({'text': 'liam'})
class TestApp(App):
title = 'Kivy RecycleView Demo'
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
Output

How to use RecycleView in kivy?

Based on the code that is posted on kivy docs about Recycle View, how do I change the data? how to change the size of the selectable labels? and especially if I want to have more widget on the screen, how do set the position of the list to be in the bottom side of the screen?
I've tried to change the position with GridLayout, BoxLayout and nothing happens.
'''
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
Builder.load_string('''
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
''')
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. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(100)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
'''
Question 1
How do I change the data?
Answer
Change the data by updating self.data
RecycleView » data
The RecycleView is generatad by processing the data (i.e.
self.data), essentially a list of dicts, and uses these dicts to
generate instances of the viewclass as required.
Question 2
How to change the size of the selectable labels?
Answer
The size especially the height of each selectable widgets can be change in either Python script or kv file.
With reference to this example, the height can be changed by setting default_size: None, dp(30) at SelectableRecycleBoxLayout. As for the width of each selectable widgets, it will change according to the number columns in a row of data, self.data
With a SelectableRecycleGridLayout, one can specify the minimum width for each column by using cols_minimum
Question 3
If I want to have more widget on the screen, how to set the position
of the list to be in the bottom side of the screen?
Answer
Declare a root widget with inheritance of BoxLayout
Snippets
<RootWidget>:
orientation: 'vertical'
BoxLayout:
size_hint: 1, 0.8
BoxLayout:
size_hint: 1, 0.2
RV:
Output
Example
How to add vertical scroll in RecycleView

Using the ScreenManager with RecycleView in Kivy

I have a RecycleView widget that responds to touch input. I need each row on the RecycleView to take the user to a specific screen. For now I only have two screens setup.
Here is the Python code:
class Navigator(NavigationDrawer):
image_source = StringProperty('images/1canaa.jpg')
title = StringProperty('Navigation')
# This is the screen that is initiated when the app runs
class MainScreen(Screen):
pass
# This is the screen that is suppose to initiate when the first row is
# touched
class MapScreen(Screen):
pass
class Manager(ScreenManager):
main_screen_obj = ObjectProperty(None)
map_screen_obj = ObjectProperty(None)
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. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
if rv.data[index] == {'text': 'FIRST ROW'}:
Manager.current = 'mapScreen'
print('The evaluation was executed')
else:
print("selection removed for {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
Unidades = ['FIRST ROW', 'SECOND ROW', 'THIRD ROW', 'FORTH ROW']
self.data = [{'text': x} for x in Unidades]
class Main(App):
theme_cls = ThemeManager()
nav_drawer = ObjectProperty()
def build(self):
self.nav_drawer = Navigator()
return Manager()
if __name__ == '__main__':
Main().run()
If you look at the apply_selection method, which is in the SelectableLabel class, you will see that I have tried to sort this out by checking:
if the rv.data[index] == {'text': 'FIRST ROW'}:
Manager.current = 'mapScreen'
print('The evaluation was executed')
This did not work. Notice that I printed a message to check if the evaluation had happened, and it did. When I run the app I get the message: 'The evaluation was executed'. The user was not taken to the MapScreen though.
And here is the kv code:
#:import MapSource mapview.MapSource
#:import Toolbar kivymd.toolbar.Toolbar
#:import hex kivy.utils.get_color_from_hex
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: hex('#867979') if self.selected else hex('#808080')
Rectangle:
pos: self.pos
size: self.size
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: False
touch_multiselect: True
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Label:
size_hint: None, None
height: 45
width: 100
pos_hint: {'center_y': 0.5, 'center_x': 0.5}
text: '[size=40]Unidades de Assistência[/size]'
color: hex('#676767')
markup: True
font_name: 'alex-brush.regular.ttf'
RV:
<MapScreen>:
BoxLayout:
Label:
text: 'MapScreen'
<Manager>:
id: screen_manager
main_screen_obj: main_screen
map_screen_obj: map_screen
MainScreen:
id: main_screen
name: 'mainScreen'
manager: screen_manager
MapScreen:
id: map_screen
name: 'mapScreen'
manager: screen_manager
So, what I am trying to accomplish here is very simple, or at least it should be. It all boils down to this: If the first row is touched, take the user to MapScreen... If the second row is touched, take the user to some other screen ...so on.
I hope this is not very confusing. Thanks for any help.
There are two ways to do this, off the top of my head.
One is to use the router module in the kivy garden:
https://github.com/kivy-garden/garden.router
The other one would be by passing a reference of the screenmanager to the widgets in the recycle view so that you can just say self.manager.current = self.text, but this means that you would have had to add all screens previously to the manager.
I would use the factory for this:
from kivy.factory import Factory
class MyScreenManager(ScreenManager):
def create_and_switch_screen(self, name):
# This creates an instance of the class name you passed
new_screen = getattr(Factory, name)()
self.switch_to(my_screen)
This way you can programatically create each screen and define them as separate classes.

Kivy - python - multiple widgets in recycleview row

I would like to make a recycleview that has multiple labels in each recycleview row. In my specfic example I would like to have 3 labels in each row: 1 label containing the item index, one label containing an item from one dataset, and another label from another dataset
In this example (taken from the kivy examples) we have a recycleview where each row in the recycleview contains a single label:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
Builder.load_string('''
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
''')
items_1= {'apple', 'banana', 'pear', 'pineapple'}
items_2= {'dog', 'cat', 'rat', 'bat'}
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, 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. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in items_1]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
I would like each recycleview row to have 3 labels: first label is the index, second label is items_1 and third label is items_2. Like this:
0 apple dog
1 banana cat
2 pear rat
3 pineapple bat
Thank you!
I was looking for a similar solution and could not find it. I do not think PalimPalim has really answered the question since I think the Ben t was looking for multiple label widgets treated as a single line.
In this case you use a custom widget for GridLayout and specify it's structure.
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
label1_text: 'label 1 text'
label2_text: 'label 2 text'
label3_text: 'label 3 text'
pos: self.pos
size: self.size
Label:
id: id_label1
text: root.label1_text
Label:
id: id_label2
text: root.label2_text
Label:
id: id_label3
text: root.label3_text
In applying your data into the RV, you will need to restructure the dictionary to reflect the label layout
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
paired_iter = zip(items_1, items_2)
self.data = []
for i1, i2 in paired_iter:
d = {'label2': {'text': i1}, 'label3': {'text': i2}}
self.data.append(d)
Finally in the refresh_view_attrs, you will specify .label_text which is bound to each label, or you can use label id's.
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.label1_text = str(index)
self.label2_text = data['label2']['text']
self.ids['id_label3'].text = data['label3']['text'] # As an alternate method of assignment
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
The entire code is below:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
Builder.load_string('''
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
label1_text: 'label 1 text'
label2_text: 'label 2 text'
label3_text: 'label 3 text'
pos: self.pos
size: self.size
Label:
id: id_label1
text: root.label1_text
Label:
id: id_label2
text: root.label2_text
Label:
id: id_label3
text: root.label3_text
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
''')
items_1 = {'apple', 'banana', 'pear', 'pineapple'}
items_2 = {'dog', 'cat', 'rat', 'bat'}
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, GridLayout):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
cols = 3
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.label1_text = str(index)
self.label2_text = data['label2']['text']
self.ids['id_label3'].text = data['label3']['text'] # As an alternate method of assignment
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
paired_iter = zip(items_1, items_2)
self.data = []
for i1, i2 in paired_iter:
d = {'label2': {'text': i1}, 'label3': {'text': i2}}
self.data.append(d)
# can also be performed in a complicated one liner for those who like it tricky
# self.data = [{'label2': {'text': i1}, 'label3': {'text': i2}} for i1, i2 in zip(items_1, items_2)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
the easiest way is to change from a RecycleBoxLayout to RecycleGridLayout with
3 columns and using the following list items = [0, "apple", "dog", 1, "banana", "cat", 2, "pear", "rat", 3, "pineapple", "bat"] Obviously, you could stick to you original list data structure and merge them together to form the list above, but I will leave that for you ;).
Another option which should be possible is to add to the RecycleBoxLayout a RecycleBoxLayout with an horizontal orientation per row.
This is all the python code.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
Builder.load_string('''
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleGridLayout:
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
cols: 3
''')
items = [0, "apple", "dog", 1, "banana", "cat", 2, "pear", "rat", 3, "pineapple", "bat"]
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' 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. '''
self.selected = is_selected
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in items]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()

Categories