How can I access element of RecycleView Kivy? - python

I wonder to know how to access widgets in RecycleView. I constructed simple example:
main.py
from kivy.app import App
from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
class ShowBoxLayout(BoxLayout):
keys = ListProperty()
def __init__(self, **kwargs):
super(ShowBoxLayout, self).__init__(**kwargs)
self.keys = [x for x in range(5)]
def print_list(self):
#here I expect textinputs id but got empty dict
print(self.ids)
class TestApp(App):
def build(self):
bl = ShowBoxLayout()
return bl
app = TestApp()
app.run()
test.kv
<ShowBoxLayout>:
RecycleView:
viewclass: 'TextInput'
data: [{'id': str(x)} for x in range(10)]
RecycleGridLayout:
cols: 1
default_size: None, dp(26)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: False
touch_multiselect: False
Button:
text : 'Hello world'
on_press : root.print_list()
In that case I cannot access TextInput inside of it with ids nor with anything else. How should I access it in order to get text in them?
This is how screen looks like.
and this is what I get after button is pressed : {}.

The problem is that you cannot assign to the ids dictionary in python. That can only be done in kv. So another way to access the items is to assign an id to the RecycleGridLayout, then visit each of its children. You can also define a method of your viewclass to display the entered text:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
class ShowBoxLayout(BoxLayout):
keys = ListProperty()
def __init__(self, **kwargs):
super(ShowBoxLayout, self).__init__(**kwargs)
self.keys = [x for x in range(5)]
def print_list(self):
#here I expect textinputs id but got empty dict
for child in self.ids.grid.children:
print(child, child.text, child.id)
class MyTextInput(TextInput):
def __init__(self, **kwargs):
super(MyTextInput, self).__init__(**kwargs)
self.multiline = False
self.on_text_validate = self.get_text
def get_text(self):
print('get_text:', self.text)
Builder.load_string('''
<ShowBoxLayout>:
RecycleView:
viewclass: 'MyTextInput'
data: [{'id': str(x)} for x in range(10)]
RecycleGridLayout:
id: grid
cols: 1
default_size: None, dp(26)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: False
touch_multiselect: False
Button:
text : 'Hello world'
on_press : root.print_list()
''')
class TestApp(App):
def build(self):
bl = ShowBoxLayout()
return bl
app = TestApp()
app.run()
(I used Builder.load_string() as a convenience for myself)
Note that since this is a RecycleView, the viewclass items are recycled, so the print_list() method may not visit an item for every data element, but only the ones currently displayed.

Related

Spinner at bottom of recyleview in kivy and kivymd

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

How to replace deprecated ListView in kivy?

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

Editable spinner

I need to make something like a spinner or dropdown list but with the ability to input the value manually when there is no right value on the list.
I'm trying to find some properties of spinner but no one seems to fit. Is there any posibility in kv language?
Here's a basic implementation using DropDown widget.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
kv = """
<CustomDropdown>:
size_hint_y: None
pos_hint: {"top": 1}
height: 56.0
orientation: "horizontal"
TextInput:
id: txt_input
size_hint_x: 0.5
Button:
id: show_drop
size_hint_x: 0.2
text: "drop"
on_release: root.show_dropdown()
Button:
id: submit_btn
size_hint_x: 0.3
text: "Submit"
on_press: root.validate_txt()
"""
class CustomDropdown(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.dropdown = DropDown()
self.fakeValues = ["First", "Second", "Third"]
for val in self.fakeValues:
btn = Button(text=val, size_hint=[0.5, None])
btn.height = 56.0
btn.bind(on_press=self.change_value)
self.dropdown.add_widget(btn)
def change_value(self, *args):
self.ids.txt_input.text = args[0].text
self.hide_dropdown()
def add_options(self, *args):
btn = Button(text=args[0], size_hint=[0.5, None])
btn.height = 56.0
btn.bind(on_press=self.change_value)
self.dropdown.add_widget(btn)
def validate_txt(self, *args):
curText = self.ids.txt_input.text
if ((curText in self.fakeValues) or (curText == None) or (curText == "")):
return
self.fakeValues.append(curText)
self.add_options(curText)
self.hide_dropdown()
def hide_dropdown(self):
self.dropdown.dismiss()
def show_dropdown(self, *args):
if self.dropdown.parent:
self.hide_dropdown()
else:
self.dropdown.open(self.ids.txt_input)
class TestApp(App):
def build(self):
Builder.load_string(kv)
return CustomDropdown()
TestApp().run()
Before doing anything:
Showing DropDown:
Typing some custom words and submitting it:
Showing DropDown again:

How can I use the RecycleView of Kivy on kv language with ScreenManager?

I have a database on Firebase of Google working well, I can save my data there easily. I would like to return this data for my app, but before I have problems with this, I can't list anything on Kivy.
I would want to use the ListView of Kivy, but in the documentation is recommended to use the RecycleView. But I can't understand the documentation. I have some doubts.
If you can read the docs of RecycleView, you'll see this as an example:
Builder.load_string('''
<RV>:
viewclass: 'Label'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
''')
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()
But I'm using the ScreenManager to control my screens, then, in the TestApp class I return 'sm', like this example of the documentation:
# Declare both screens
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
If you see the syntaxes are different, and it's here where I don't know how to code this. I would like to keep using the ScreenManager to control the screens and use a RecycleView to return my data in a list.
How can I use the RecycleView with my ScreenManager? This is my main.py, I configure the screen in another document, and I use the ki language too. So if you all can to do an example to me I will be grateful.
import kivy
from kivy.app import App, Builder
from kivy.config import Config
from kivy.uix.screenmanager import ScreenManager
from telas.telas import Acesso, Comprando, Vendendo, CadastrarEvento
kivy.require('1.10.1')
Builder.load_file('ing.kv')
Config.read('config.ini')
sm = ScreenManager()
sm.add_widget(Acesso(name='acesso'))
sm.add_widget(Comprando(name='comprando'))
sm.add_widget(Vendendo(name='vendendo'))
sm.add_widget(CadastrarEvento(name='cadastrarEvento'))
sm.add_widget(ListaEventos(name='listaEventos'))
class IngApp(App):
def build(self):
return sm
if __name__ == '__main__':
IngApp().run()
Here the kv that I tried the first time
<ListaEventos>:
canvas:
Rectangle:
source: 'design/fundo.png'
size: self.width, self.height
viewclass: 'Label'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
ListaEventos:
class ListaEvento(Screen, RecycleView):
def __init__(self, **kwargs):
super(ListaEvento, self).__init__(**kwargs)
self.data = [{'text': str(x)} for x in range(20)]
You should not inherit from 2 widgets but what widget is going to be painted? For example, if you want an image that behaves like a button, you must inherit from the Image widget and the ButtonBehavior class, that is, visually it is an image but added the button behavior.
So to solve your problem it is not correct to use the inheritance but the composition, that is, to add the RecyclerView as a son of the Screen.
*.py
import kivy
from kivy.app import App, Builder
from kivy.config import Config
from kivy.uix.screenmanager import ScreenManager, Screen
class ListaEventos(Screen):
def __init__(self, **kwargs):
super(ListaEventos, self).__init__(**kwargs)
# assigning data in RecyclerView
self.rv.data = [{'text': str(x)} for x in range(100)]
kivy.require('1.10.1')
Builder.load_file('ing.kv')
Config.read('config.ini')
sm = ScreenManager()
sm.add_widget(ListaEventos(name='listaEventos'))
class IngApp(App):
def build(self):
return sm
if __name__ == '__main__':
IngApp().run()
ing.kv
<ListaEventos>:
rv: rv # expose the widget
canvas:
Rectangle:
source: 'design/fundo.png'
size: self.width, self.height
RecycleView:
id: rv
viewclass: 'Label'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'

I try to put spinner object on tab panel in kivy but not working, help me

So this is the basic code of my tab item.
import kivy
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.gridlayout import GridLayout
Builder.load_string("""
<Test>:
size_hint: .8, .5
pos_hint: {'center_x': .4, 'center_y': .3}
do_default_tab: False
TabbedPanelItem:
text: 'Tab 1'
Label:
text: 'Syntax'
TabbedPanelItem:
text: 'Changeover'
TabbedPanelItem:
text: 'Map'
""")
class Test(TabbedPanel):
pass
class TabbedPanelApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TabbedPanelApp().run()
and i want to insert spinner on Tab 1, how to make it work? this is my spinner code.
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.properties import ListProperty, ObjectProperty
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
import kivy
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
class MultiSelectSpinner(Button):
dropdown = ObjectProperty(None)
values = ListProperty([])
selected_values = ListProperty([])
def __init__(self, **kwargs):
self.bind(dropdown=self.update_dropdown)
self.bind(values=self.update_dropdown)
super(MultiSelectSpinner, self).__init__(**kwargs)
self.bind(on_release=self.toggle_dropdown)
def toggle_dropdown(self, *args):
if self.dropdown.parent:
self.dropdown.dismiss()
else:
self.dropdown.open(self)
def update_dropdown(self, *args):
if not self.dropdown:
self.dropdown = DropDown()
values = self.values
if values:
if self.dropdown.children:
self.dropdown.clear_widgets()
for value in values:
b = Factory.MultiSelectOption(text=value)
b.bind(state=self.select_value)
self.dropdown.add_widget(b)
def select_value(self, instance, value):
if value == 'down':
if instance.text not in self.selected_values:
self.selected_values.append(instance.text)
else:
if instance.text in self.selected_values:
self.selected_values.remove(instance.text)
def on_selected_values(self, instance, value):
if value:
self.text = ', '.join(value)
else:
self.text = ''
kv = '''
BoxLayout:
orientation: 'vertical'
size_hint: .7, .6
pos_hint: {'center_x': .3, 'center_y': .7}
do_default_tab: False
BoxLayout:
Label:
text: 'Food Type'
MultiSelectSpinner:
values: 'Fried Chicken', 'Burger'
<MultiSelectOption#ToggleButton>:
size_hint: 8, None
height: '40dp'
'''
runTouchApp(Builder.load_string(kv))
i need abit pointer please
i try to merge the code but not working.
glad if someone can help
i think its best if you create a custom TabbledPanelItem. and then implement that spinner in it.forgive my indentation
class Mod(TabbedPanelItem):
one = ObjectProperty(None)
def __init__(self,**kwargs):
super(Mod,self).__init__(**kwargs)
def on_touch_down(self, touch):
if self.state == 'down':
# dispatch to children, not to self
for child in self.children:
child.dispatch('on_touch_down', touch)
return
else:
super(Mod, self).on_touch_down(touch)
def on_release(self, *largs):
if self.parent:
self.parent.tabbed_panel.switch_to(self)
print(self.parent.children)
lola = MultiSelectSpinner()
lola.toggle_dropdown()
else:
# tab removed before we could switch to it. Switch back to
# previous tab
self.panel.switch_to(self.panel.current_tab)

Categories

Resources