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.
Related
I want to display a table in one of the screen and the displayed data in the table should come from the loan database, loan table.
I have created the database and also there is no error which is being shown. The table is created but there is no data displayed on it.Its empty table.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
import json
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.popup import Popup
from loandatabase import Database
database = Database()
Builder.load_file('design.kv')
class MainScreen(Screen):
def goto_admin_page(self):
self.manager.current = "adminfirst_screen"
def goto_agent_page(self):
pass
def goto_customer_page(self):
pass
class AdminScreenFirst(Screen):
def go_to_adminsecond(self,uname,pword):
with open("users.json") as file:
users =json.load(file)
if uname in users and users[uname]['password'] == pword:
self.manager.current = "adminsecond_screen"
else:
self.ids.login_wrong.text = "Invalid Credentials.Please Contact the administrator"
class AdminScreenSecond(Screen):
def go_to_adminpending(self):
self.manager.current = "adminPending_screen"
def go_to_adminapproved(self):
self.manager.current = "adminsecond_screen"
def go_to_adminrejected(self):
self.manager.current = "adminsecond_screen"
class AdminPendingScreen(Screen):
def display(self):
self.manager.current = "rv_screen"
class RV(Screen):
data_items = ListProperty([])
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
for row in database.view():
for col in row:
self.data_items.append(col)
class TextInputPopup(Popup):
obj = ObjectProperty(None)
obj_text = StringProperty("")
def __init__(self, obj, **kwargs):
super(TextInputPopup, self).__init__(**kwargs)
self.obj = obj
self.obj_text = obj.text
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableButton(RecycleDataViewBehavior, Button):
''' 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(SelectableButton, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableButton, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
def on_press(self):
popup = TextInputPopup(self)
popup.open()
def update_changes(self, txt):
self.text = txt
class RootWidget(ScreenManager):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == "__main__":
MainApp().run()
my kivy code :
<MainScreen>:
GridLayout:
cols:1
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Login"
font_size:"20sp"
Button:
text:"Admin"
on_press : root.goto_admin_page()
Button:
text:"Agent"
on_press : root.goto_agent_page()
Button:
text: "Customer"
on_press:root.goto_customer_page()
<AdminScreenFirst>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Admin Login"
font_size:"20sp"
TextInput:
id:username
hint_text:"Username"
TextInput:
id:password
password:True
hint_text:"Password"
RelativeLayout:
Button:
text:"Login"
on_press : root.go_to_adminsecond(root.ids.username.text,root.ids.password.text)
size_hint :0.3,0.5
pos_hint: {'center_x' : 0.5 , 'center_y' : 0.6}
Label:
id:login_wrong
text:""
<AdminScreenSecond>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Loan"
font_size:"20sp"
Button:
text:"Pending Request"
on_press:root.go_to_adminpending()
Button:
text:"Approved"
on_press: root.go_to_adminapproved()
Button:
text:"Rejected"
on_press: root.go_to_adminrejected()
<AdminPendingScreen>:
GridLayout:
cols:1
padding:15,15
spacing:20,20
Label:
text:"Loans"
Button:
text:"Display"
on_press: root.display()
Label:
id:display
text:""
<TextInputPopup>:
title: "Popup"
size_hint: None, None
size: 400, 400
auto_dismiss: False
BoxLayout:
orientation: "vertical"
TextInput:
id: txtinput
text: root.obj_text
Button:
size_hint: 1, 0.2
text: "Save Changes"
on_release:
root.obj.update_changes(txtinput.text)
root.dismiss()
Button:
size_hint: 1, 0.2
text: "Cancel Changes"
on_release: root.dismiss()
<RV>:
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 11
Label:
text: "LoanID"
Label:
text: "Name"
Label:
text:"Tenure"
Label:
text:"Balance"
Label:
text:"LoanType"
Label:
text:"InterestType"
Label:
text:"Interest % p.a."
Label:
text:"Security"
Label:
text:"Total"
Label:
text:"EMI"
Label:
text:"Request"
BoxLayout:
RecycleView:
viewclass: 'SelectableButton'
data: [{'text': str(x)} for x in root.data_items]
SelectableRecycleGridLayout:
cols: 11
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
<RootWidget>:
MainScreen:
name:"mainfirst_screen"
AdminScreenFirst:
name:"adminfirst_screen"
AdminScreenSecond:
name:"adminsecond_screen"
AdminPendingScreen:
name:"adminPending_screen"
RV:
name:"rv_screen"
my database code:
import sqlite3
class Database:
def __init__(self):
self.conn = sqlite3.connect("loandatabase.db")
self.cur = self.conn.cursor()
self.cur.execute("CREATE TABLE IF NOT EXISTS loan (id INTEGER PRIMARY KEY, customer_name TEXT, tenure integer, balance integer, loantype TEXT, interesttype TEXT,interest integer,security TEXT,totalpayment integer,emi integer,instruction TEXT)")
self.conn.commit()
def insert(self,cname,tenure,blance,ltype,itype,interest,security,tpay,emi,instr):
self.cur.execute("INSERT INTO loan VALUES (NULL,?,?,?,?,?,?,?,?,?,?)",(cname,tenure,blance,ltype,itype,interest,security,tpay,emi,instr))
self.conn.commit()
def view(self):
self.cur.execute("SELECT * FROM loan")
rows = self.cur.fetchall()
return rows
def __del__(self):
self.conn.close()
[![enter image description here][1]][1]
I would request you to help me with it. Thank You in Advance
I am using Kivy,Python,sqlite3
This is displayed:
[1]: https://i.stack.imgur.com/fkUvS.png
Try this:
class MainApp(App):
title = 'Title of the app'
def build(self):
sm = ScreenManager('''You can add transition here''')
sm.add_widget(MainScreen(name='main'))
sm.add_widget(AdminScreenFirst(name='admin1'))
sm.add_widget(AdminScreenSecond(name='admin2'))
sm.add_widget(AdminPendingScreen(name='pending'))
sm.add_widget(RV(name='rv'))
return sm
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)
In a previous question, I asked how to have a row of text inputs which is dinamically added on press of a button, all from a py script.
I am now attempting at moving all my layout code to a kv file. While this was pretty straightforward for the widgets which appear on screen by default, I am not really sure of how to define the dinamically added text inputs from the kv file.
My solution, at present, is to create the 'default' widgets in the kv file, and to add the other ones from the py script through the addIngredient method. Below a minimal working version.
The kv file:
WMan:
AddWindow:
<AddWindow>:
name: 'add'
ingsGrid: ingsGrid
ing1: ing1
quant1: quant1
addIng: addIng
saveButton: saveButton
StackLayout:
id: ingsGrid
size_hint: .9, None
height: self.minimum_height
orientation: 'lr-tb'
spacing: '5sp', '5sp'
TextInput:
id: ing1
multiline: False
size_hint: .65, None
height: self.minimum_height
TextInput:
id: quant1
multiline: False
size_hint: .25, None
height: self.minimum_height
Button:
id: addIng
text: "+"
size_hint: .1, None
height: ing1.height
on_release: root.addIngredient(self)
Button:
id: saveButton
text: "Save"
size_hint: .3, None
on_release:
root.saveRec(self)
The py script reads:
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
recipeName = ObjectProperty(None)
ingsGrid = ObjectProperty(None)
ing1 = ObjectProperty(None)
quant1 = ObjectProperty(None)
self.i = 1
self.ingsList = {}
self.ingsList[0] = ing1
self.quants = {}
self.quants[0] = quant1
def addIngredient(self, instance):
tmp_children_list = self.ingsGrid.children[:]
self.ingsGrid.clear_widgets()
# range(start,stop[, step])
for index in range(len(tmp_children_list)-1, -1, -1):
# the last item, then last-1, etc
child = tmp_children_list[index]
# add the last first (new ones will be added on top)
self.ingsGrid.add_widget(child)
# if child is the pressed button
if child == instance:
self.ing = TextInput(
size_hint=(.65, None),
height='30sp')
self.ingsGrid.add_widget(self.ing)
self.ingsList[self.i] = self.ing
self.quant = TextInput(
size_hint=(0.25, None),
height='30sp')
self.ingsGrid.add_widget(self.quant)
self.quants[self.i] = self.quant
self.i += 1
self.addNext = Button(
text="+",
size_hint=(0.1, None),
height='30sp')
self.addNext.bind(on_press=self.addIngredient)
self.ingsGrid.add_widget(self.addNext)
def saveRec(self, instance): # grab all inputs and send to SQLite db
print(self.ingsList)
print(self.ingsList[0].text)
print(self.ingsList[1].text)
class WMan(ScreenManager):
pass
kv = Builder.load_file("test.kv")
class TestApp(App):
def build(self):
return kv
if __name__ == "__main__":
TestApp().run()
My problem here is twofold: first, while this way of dinamically adding rows works as it should, it is to me a bit messy to have half of the layout defined on the kv file, and the other half defined in the py script. So my first question is:
1. Is there a way to move the entire layout to the kv file?
Second questions is:
2. How do I access the content of textInput 'ing1' (the one created in the kv file)?
when I run print(self.ingsList), I get:
{0: <ObjectProperty name=>, 1: <kivy.uix.textinput.TextInput object at 0x000002077FB89C88>}
So while I can easily do print(self.ingsList[1].text), running print(self.ingsList[0].text) will give error:
AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
Here is a modified version of your code that does what I think you want:
from kivy.app import App
from kivy.properties import ObjectProperty, NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class AddWindow(Screen):
def __init__(self, **kwargs):
super(AddWindow, self).__init__(**kwargs)
recipeName = ObjectProperty(None)
ingsGrid = ObjectProperty(None)
ing1 = ObjectProperty(None)
quant1 = ObjectProperty(None)
self.i = 1
self.ingsList = {}
self.ingsList[0] = ing1
self.quants = {}
self.quants[0] = quant1
def saveRec(self, instance): # grab all inputs and send to SQLite db
for child in self.ids.ingsGrid.children:
if isinstance(child, OneRow):
print('ingedient name:', child.ids.ing1.text)
print('quantity:', child.ids.quant1.text)
print('')
class WMan(ScreenManager):
pass
class OneRow(BoxLayout):
inst_count = NumericProperty(0)
count = 0
def __init__(self, **kwargs):
OneRow.count += 1
self.inst_count = OneRow.count
super(OneRow, self).__init__(**kwargs)
def get_index(self):
par = self.parent
if par is None:
return None
index = 0
for index in range(len(par.children) - 1, -1, -1):
child = par.children[index]
if child == self:
return index
kv_str = '''
#:import Factory kivy.factory.Factory
WMan:
AddWindow:
id: add
#:set row_height 30
<OneRow>:
orientation: 'horizontal'
size_hint_y: None
height: row_height
TextInput:
id: ing1
multiline: False
size_hint: .65, None
height: row_height
TextInput:
id: quant1
multiline: False
text: str(root.inst_count)
size_hint: .25, None
height: row_height
Button:
id: addIng
text: "+"
size_hint: .1, None
height: row_height
on_release: app.root.ids.add.ids.ingsGrid.add_widget(Factory.OneRow(), index=root.get_index())
<AddWindow>:
name: 'add'
ingsGrid: ingsGrid
saveButton: saveButton
StackLayout:
id: ingsGrid
size_hint: .9, None
height: self.minimum_height
orientation: 'lr-tb'
spacing: '5sp', '5sp'
OneRow:
Button:
id: saveButton
text: "Save"
size_hint: .3, None
on_release:
root.saveRec(self)
'''
# kv = Builder.load_file("test.kv")
kv = Builder.load_string(kv_str)
class TestApp(App):
def build(self):
return kv
if __name__ == "__main__":
TestApp().run()
I used Builder.load_string() instead of load_file() just for my own convenience.
I have created a class named OneRow that holds a single row from the ingredients list, and the + Button now just adds an instance of that class. The get_index() method of that class is only used to position new ingredients below the row with the Button that was pressed. And the other code in that class is just to add some identifying info. If those things are not important to you, you can eliminate the OneRow class definition from the python, and just replace <OneRow>: in the kv with <OneRow#BoxLayout>: and where the OneRow is added you can just set index=1.
I'm trying to display an sqlite3 database in python using kivy. Tested out ikolim's solution here and it works as intended in displaying the database contents into the RecycleView's button labels:
And when a button is pressed, a popup and edit function appears as intended:
And it does edit the selected button's text as shown in the next screenshot.. :
..but not the sqlite database values. Would it be possible to reflect the changes the user makes inside the Kivy app to the values inside the sqlite database?
Here are the codes:
test.py
import sqlite3
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.popup import Popup
connection = sqlite3.connect("demo.db", isolation_level=None)
cursor = connection.cursor()
class TextInputPopup(Popup):
obj = ObjectProperty(None)
obj_text = StringProperty("")
def __init__(self, obj, **kwargs):
super(TextInputPopup, self).__init__(**kwargs)
self.obj = obj
self.obj_text = obj.text
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableButton(RecycleDataViewBehavior, Button):
''' Add selection support to the Button '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableButton, self).refresh_view_attrs(rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableButton, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected
def on_press(self):
popup = TextInputPopup(self)
popup.open()
def update_changes(self, txt):
self.text = txt
class RV(BoxLayout):
data_items = ListProperty([])
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.get_users()
def get_users(self):
cursor.execute("CREATE TABLE IF NOT EXISTS Callbacks(cName TEXT, cID INT, cbTime INT, cbRems TEXT)")
cursor.execute("INSERT INTO Callbacks VALUES ('Client1','1','1500','Test1')")
cursor.execute("INSERT INTO Callbacks VALUES ('Client2','2','1600','Test2')")
cursor.execute("INSERT INTO Callbacks VALUES ('Client3','3','1700','Test3')")
connection.commit()
cursor.execute("SELECT * FROM Callbacks ORDER BY ROWID DESC")
rows = cursor.fetchall()
# create data_items
for row in rows:
for col in row:
self.data_items.append(col)
for row in rows:
print(self.data_items)
class TestApp(App):
title = "Kivy RecycleView & SQLite3 Demo"
def build(self):
return RV()
if __name__ == "__main__":
TestApp().run()
test.kv
#:kivy 1.10.0
<TextInputPopup>:
title: "Popup"
size_hint: None, None
size: 400, 400
auto_dismiss: False
BoxLayout:
orientation: "vertical"
TextInput:
id: txtinput
text: root.obj_text
Button:
size_hint: 1, 0.2
text: "Save Changes"
on_release:
root.obj.update_changes(txtinput.text)
root.dismiss()
Button:
size_hint: 1, 0.2
text: "Cancel Changes"
on_release: root.dismiss()
<SelectableButton>:
# 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>:
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 4
Label:
text: "Name"
Label:
text: "ID"
Label:
text: "CB Time"
Label:
text: "Remarks"
BoxLayout:
RecycleView:
viewclass: 'SelectableButton'
data: [{'text': str(x)} for x in root.data_items]
SelectableRecycleGridLayout:
cols: 4
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
In order to reflect the changes to the sql, you'll need to use the update command to update the sql table.
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)