from kivy.uix.modalview import ModalView
from kivy.uix.screenmanager import Screen
from kivymd.app import MDApp
from kivy.metrics import dp
from kivy.lang.builder import Builder
KV = """
<ImgCard#ButtonBehavior+BoxLayout>
path: ""
orientation: "vertical"
size_hint_y: None
Image:
source: root.path
size_hint_y: .9
MDCheckbox:
<Gallery>
orientation: 'vertical'
ScreenManager:
Screen:
BoxLayout:
orientation: 'vertical'
RecycleView:
id: img_base
viewclass: "ImgCard"
canvas.before:
#$#Color:
#$rgba: (.4, .4, .4, .7)
Rectangle:
size: self.size
pos: self.pos
RecycleGridLayout:
spacing: 10
cols: 3
default_size: None, dp(48)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
multiselect: True
touch_multiselect: True """
class ImageManager(ModalView):
pass
class Gallery(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.manager_list = []
self.dir = os.getcwd()
self.available_image_format = ['.png', '.jpg', '.jpeg', '.bmp'] # etc
def load_images(self):
if not self.manager_list:
for image in os.listdir(self.dir):
target_filename, target_file_extension = os.path.splitext(image)
if target_file_extension in self.available_image_format:
path_to_image = os.path.join(self.dir, image)
self.manager_list.append(
{
"ImgCard": "ImageManager",
"path": path_to_image,
"height": dp(200),
}
)
self.ids.img_base.data = self.manager_list
self.images=[self.dir]
class GalleryApp(MDApp):
def build(self):
Builder.load_string(KV)
return Gallery()
def on_start(self):
self.root.load_images()
if __name__=='__main__':
GalleryApp().run()
How to link checkbox in gallery for also multiple select in kivy. Can anyone help this?
If you are using MDCheckbox in a RecycleView, then you should include the state of that MDCheckbox in the data. If you don't do that, then the RecyclView doesn't know about the state of the MDCheckbox and when an MDCheckbox gets recycled, its state will be whatever it was last time it was used.
To accomplish that, first modify your kv rule for ImgCard:
<ImgCard#BoxLayout>
path: ""
selected: False # added to indicate selection
index: -1 # added to enable accessing this item in the data
orientation: "vertical"
size_hint_y: None
Image:
source: root.path
size_hint_y: .9
MDCheckbox:
id: cb
state: 'down' if root.selected else 'normal'
on_release: app.root.adjust_data(root)
Note that the ButtonBehavior is not necessary.
Then, modify the initial data to initialize the new properties:
def load_images(self):
count = 0
if not self.manager_list:
for image in os.listdir(self.dir):
target_filename, target_file_extension = os.path.splitext(image)
if target_file_extension in self.available_image_format:
path_to_image = os.path.join(self.dir, image)
self.manager_list.append(
{
"ImgCard": "ImageManager",
"path": path_to_image,
"height": dp(200),
"selected": False,
"index": count
}
)
count += 1
self.ids.img_base.data = self.manager_list
self.images = [self.dir]
And add a method in the Gallery class to update the data when a MDCheckbox is changed:
def adjust_data(self, imgcard):
rv = self.ids.img_base
rv.data[imgcard.index]['selected'] = imgcard.ids.cb.state == 'down'
imgcard.ids.cb.state = 'normal'
rv.refresh_from_data()
To remove the selected items, you can add a Button in your <Gallery> rule that calls:
def delete(self, instance):
rv = self.ids.img_base
deleted = False
for i in range(len(rv.data) - 1, -1, -1):
item = rv.data[i]
if item['selected']:
del rv.data[i]
deleted = True
if deleted:
self.adjust_indices()
Since deleting any items will upset the index property of items in the data, another method is required to adjust the index properties:
def adjust_indices(self):
# adjust index values to account for removd items
rv = self.ids.img_base
index = 0
for item in rv.data:
item['index'] = index
index += 1
Related
I'm learning kivy by cobbling together a small application to understand the behavior of different widgets.
What works:
The app accepts text and images as input & stores to the database, stored data is correctly displayed on buttons using RecycleView.
Problem:
On pressing the buttons on the RecycleView the app crashes with the error: AttributeError: 'super' object has no attribute 'getattr'
What I've tried:
I understand from this post, that initialization may not be complete and tried scheduling with kivy clock but this throws a new error AttributeError:'float' object has no attribute 'index'.
Expected behavior:
On button click set selected button data (text & image values) in their respective widgets. I have not been able to understand why this is not working in a multiscreen environment.
The full code is as follows.
main.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.screenmanager import ScreenManager, Screen
from kivy.uix.accordion import Accordion
from kivy.clock import Clock
from tkinter.filedialog import askopenfilename
from tkinter import Tk
class Manager(ScreenManager):
screen_one = ObjectProperty(None)
screen_two = ObjectProperty(None)
class ScreenTwo(BoxLayout, Screen, Accordion):
data_items = ListProperty([])
def __init__(self, **kwargs):
super(ScreenTwo, self).__init__(**kwargs)
# Clock.schedule_once(self.populate_fields)
self.create_table()
self.get_table_column_headings()
self.get_users()
def populate_fields(self, instance): # NEW
columns = self.data_items[instance.index]['range']
self.ids.no.text = self.data_items[columns[0]]['text']
self.user_name_text_input.text = self.data_items[columns[1]]['text']
def get_table_column_headings(self):
connection = sqlite3.connect("demo.db")
with connection:
cursor = connection.cursor()
cursor.execute("PRAGMA table_info(Users)")
col_headings = cursor.fetchall()
self.total_col_headings = len(col_headings)
def filechooser(self):
Tk().withdraw()
self.image_path = askopenfilename(initialdir = "/",title = "Select file",filetypes = (("jpeg files","*.jpg"),("all files","*.*")))
self.image.source = self.image_path
image_path = self.image_path
return image_path
def create_table(self):
connection = sqlite3.connect("demo.db")
cursor = connection.cursor()
sql = """CREATE TABLE IF NOT EXISTS Employees(
EmpID integer PRIMARY KEY,
EmpName text NOT NULL,
EmpPhoto blob NOT NULL)"""
cursor.execute(sql)
connection.close()
def get_users(self):
connection = sqlite3.connect("demo.db")
cursor = connection.cursor()
cursor.execute("SELECT * FROM Employees ORDER BY EmpID ASC")
rows = cursor.fetchall()
# create list with db column, db primary key, and db column range
data = []
low = 0
high = self.total_col_headings - 1
# Using database column range for populating the TextInput widgets with values from the row clicked/pressed.
self.data_items = []
for row in rows:
for col in row:
data.append([col, row[0], [low, high]])
low += self.total_col_headings
high += self.total_col_headings
# create data_items
self.data_items = [{'text': str(x[0]), 'Index': str(x[1]), 'range': x[2]} for x in data]
def save(self):
connection = sqlite3.connect("demo.db")
cursor = connection.cursor()
EmpID = self.ids.no.text
EmpName = self.ids.name.text
image_path = self.image_path # -- > return value from fielchooser
EmpPhoto = open(image_path, "rb").read()
try:
save_sql="INSERT INTO Employees (EmpID, EmpName, EmpPhoto) VALUES (?,?,?)"
connection.execute(save_sql,(EmpID, EmpName, EmpPhoto))
connection.commit()
connection.close()
except sqlite3.IntegrityError as e:
print("Error: ",e)
self.get_users() #NEW
class ScreenOne(Screen):
var = ScreenTwo()
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleGridLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableButton(RecycleDataViewBehavior, Button):
''' Add selection support to the Button '''
var = ScreenTwo()
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
class OneApp(App):
def build(self):
return Manager()
if __name__ =="__main__":
OneApp().run()
one.kv
#:kivy 1.10.0
#:include two.kv
<Manager>:
id: screen_manager
screen_one: screen_one_id # original name: our set name
screen_two: screen_two_id
ScreenOne:
id: screen_one_id # our set name
name: 'screen1'
manager: screen_manager # telling each screen who its manager is.
ScreenTwo:
id: screen_two_id # our set name
name: 'screen2'
manager: screen_manager
<ScreenOne>:
Button:
text: "On Screen 1 >> Go to Screen 2"
on_press: root.manager.current = 'screen2'
two.kv
#:kivy 1.10.0
<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
on_press:
root.var.populate_fields(self)
<ScreenTwo>:
user_no_text_input: no
user_name_text_input: name
image: image
AccordionItem:
title: "INPUT FIELDS"
GridLayout:
rows:3
BoxLayout:
size_hint: .5, None
height: 600
pos_hint: {'center_x': 1}
padding: 10
spacing: 3
orientation: "vertical"
Label:
text: "Employee ID"
size_hint: (.5, None)
height: 30
TextInput:
id: no
size_hint: (.5, None)
height: 30
multiline: False
Label:
text: "Employee NAME"
size_hint: (.5, None)
height: 30
TextInput:
id: name
size_hint: (.5, None)
height: 30
multiline: False
Label:
text: "Employee PHOTO"
size_hint: (.5, None)
height: 30
Image:
id: image
allow_stretch: True
keep_ratio: True
Button:
text: "SELECT IMAGE"
size_hint_y: None
height: self.parent.height * 0.2
on_release: root.filechooser()
Button:
id: save_btn
text: "SAVE BUTTON"
height: 50
on_press: root.save()
AccordionItem:
title: "RECYCLE VIEW"
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 2
Label:
text: "Employee ID"
Label:
text: "Employee Name"
# Display only the first two columns Employee ID and Employee Name NOT EmployeePhoto on the RecycleView
BoxLayout:
RecycleView:
viewclass: 'SelectableButton'
data: root.data_items
SelectableRecycleGridLayout:
cols: 2
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
Button:
text: "On Screen 2 >> Go to Screen 1"
on_press: root.manager.current = 'screen1'
Apologies for the really long post, I thank you for your time and attention.
Problem - AttributeError
self.ids.no.text = self.data_items[columns[0]]['text']
File "kivy/properties.pyx", line 841, in kivy.properties.ObservableDict.__getattr__
AttributeError: 'super' object has no attribute '__getattr__'
self.ids - Empty
The problem was self.ids was empty.
Root Cause
There were three instances of class ScreenTwo(). If you apply id() function, it will show three different memory addresses/locations. self.ids is only available when the kv file is parsed. Therefore, self.ids is only available in the instance instantiated in one.kv file.
At class ScreenOne(Screen):, var = ScreenTwo()
At class SelectableButton(RecycleDataViewBehavior, Button):, var = ScreenTwo()
In one.kv file, ScreenTwo:
Kv language » self.ids
When your kv file is parsed, kivy collects all the widgets tagged with
id’s and places them in this self.ids dictionary type property.
Solution
In the example provided, I am using a SQLite3 database containing table, Users with columns, UserID and UserName. Please refer to the example for details.
Python Code
Remove var = ScreenTwo() from class ScreenOne(Screen): and class SelectableButton(RecycleDataViewBehavior, Button): because you don't need to instantiate another objects of ScreenTwo() which are different from the one instantiated in kv file, one.kv.
In populate_fields() method, replace self.ids.no.text with self.user_no_text_input.text because in kv file (two.kv), there is already an ObjectProperty, user_no_text_input defined and hooked to TextInput's id, no i.e. user_no_text_input: no.
In filechoser() method, remove image_path = self.image_path and return image_path because self.image_path is a class attributes of class ScreenTwo().
In save() method, replace self.ids.no.text and self.ids.name.text with self.user_no_text_input.text and self.user_name_text_input.text respectively because they are defined and hooked-up to the TextInputs in kv file, two.kv *plus it is generally regarded as ‘best practice’ to use the ObjectProperty. This creates a direct reference, provides faster access and is more explicit.*
kv file - one.kv
Remove all references of id: screen_manager and manager: screen_manager because each screen has by default a property manager that gives you the instance of the ScreenManager used.
kv file - two.kv
In class rule, <SelectableButton>: replace root.var.populate_fields(self) with app.root.screen_two.populate_fields(self)
Screen default property manager
Each screen has by default a property manager that gives you the
instance of the ScreenManager used.
Accessing Widgets defined inside Kv lang in your python code
Although the self.ids method is very concise, it is generally
regarded as ‘best practice’ to use the ObjectProperty. This
creates a direct reference, provides faster access and is more
explicit.
Example
main.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, ObjectProperty
from kivy.uix.recyclegridlayout import RecycleGridLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.accordion import Accordion
from tkinter.filedialog import askopenfilename
from tkinter import Tk
class Manager(ScreenManager):
screen_one = ObjectProperty(None)
screen_two = ObjectProperty(None)
class ScreenTwo(BoxLayout, Screen, Accordion):
data_items = ListProperty([])
def __init__(self, **kwargs):
super(ScreenTwo, self).__init__(**kwargs)
self.create_table()
self.get_table_column_headings()
self.get_users()
def populate_fields(self, instance): # NEW
columns = self.data_items[instance.index]['range']
self.user_no_text_input.text = self.data_items[columns[0]]['text']
self.user_name_text_input.text = self.data_items[columns[1]]['text']
def get_table_column_headings(self):
connection = sqlite3.connect("demo.db")
with connection:
cursor = connection.cursor()
cursor.execute("PRAGMA table_info(Users)")
col_headings = cursor.fetchall()
self.total_col_headings = len(col_headings)
def filechooser(self):
Tk().withdraw()
self.image_path = askopenfilename(initialdir = "/",title = "Select file",filetypes = (("jpeg files","*.jpg"),("all files","*.*")))
self.image.source = self.image_path
def create_table(self):
connection = sqlite3.connect("demo.db")
cursor = connection.cursor()
sql = """CREATE TABLE IF NOT EXISTS Users(
UserID integer PRIMARY KEY,
UserName text NOT NULL)"""
cursor.execute(sql)
connection.close()
def get_users(self):
connection = sqlite3.connect("demo.db")
cursor = connection.cursor()
cursor.execute("SELECT * FROM Users ORDER BY UserID ASC")
rows = cursor.fetchall()
# create list with db column, db primary key, and db column range
data = []
low = 0
high = self.total_col_headings - 1
# Using database column range for populating the TextInput widgets with values from the row clicked/pressed.
self.data_items = []
for row in rows:
for col in row:
data.append([col, row[0], [low, high]])
low += self.total_col_headings
high += self.total_col_headings
# create data_items
self.data_items = [{'text': str(x[0]), 'Index': str(x[1]), 'range': x[2]} for x in data]
def save(self):
connection = sqlite3.connect("demo.db")
cursor = connection.cursor()
UserID = self.user_no_text_input.text
UserName = self.user_name_text_input.text
EmpPhoto = open(self.image_path, "rb").read()
try:
save_sql = "INSERT INTO Users (UserID, UserName) VALUES (?,?)"
connection.execute(save_sql, (UserID, UserName))
connection.commit()
connection.close()
except sqlite3.IntegrityError as e:
print("Error: ", e)
self.get_users() #NEW
class ScreenOne(Screen):
pass
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
class OneApp(App):
def build(self):
return Manager()
if __name__ == "__main__":
OneApp().run()
one.kv
#:kivy 1.11.0
#:include two.kv
<Manager>:
screen_one: screen_one_id
screen_two: screen_two_id
ScreenOne:
id: screen_one_id
name: 'screen1'
ScreenTwo:
id: screen_two_id
name: 'screen2'
<ScreenOne>:
Button:
text: "On Screen 1 >> Go to Screen 2"
on_press: root.manager.current = 'screen2'
two.kv
#:kivy 1.11.0
<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
on_press:
app.root.screen_two.populate_fields(self)
<ScreenTwo>:
user_no_text_input: no
user_name_text_input: name
image: image
AccordionItem:
title: "INPUT FIELDS"
GridLayout:
rows:3
BoxLayout:
size_hint: .5, None
height: 600
pos_hint: {'center_x': 1}
padding: 10
spacing: 3
orientation: "vertical"
Label:
text: "Employee ID"
size_hint: (.5, None)
height: 30
TextInput:
id: no
size_hint: (.5, None)
height: 30
multiline: False
Label:
text: "Employee NAME"
size_hint: (.5, None)
height: 30
TextInput:
id: name
size_hint: (.5, None)
height: 30
multiline: False
Label:
text: "Employee PHOTO"
size_hint: (.5, None)
height: 30
Image:
id: image
allow_stretch: True
keep_ratio: True
Button:
text: "SELECT IMAGE"
size_hint_y: None
height: self.parent.height * 0.2
on_release: root.filechooser()
Button:
id: save_btn
text: "SAVE BUTTON"
height: 50
on_press: root.save()
AccordionItem:
title: "RECYCLE VIEW"
BoxLayout:
orientation: "vertical"
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 2
Label:
text: "Employee ID"
Label:
text: "Employee Name"
# Display only the first two columns Employee ID and Employee Name NOT EmployeePhoto on the RecycleView
BoxLayout:
RecycleView:
viewclass: 'SelectableButton'
data: root.data_items
SelectableRecycleGridLayout:
cols: 2
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
Button:
text: "On Screen 2 >> Go to Screen 1"
on_press: root.manager.current = 'screen1'
Output
I am using kivy and python to build an application.
I am trying to build an application in which I can select several images, add them to an array, and then pass this array of images through another method which stitches the images (using the stitcher class). The output image will display on one of the three screens (also I want to remove the middle screen).
So essentially what I would like help with is how to be able to select multiple files with filechooser in kivy and then add these files to array that I can later pass through a different method.
With the help of #ikolim in this post, I have been able to create the application.
main.py
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from PIL import Image
class RootWidget(TabbedPanel):
manager = ObjectProperty(None)
img = ObjectProperty(None)
img3 = ObjectProperty(None)
img4 = ObjectProperty(None)
lab = ObjectProperty(None)
def on_touch_up(self, touch):
if not self.img3.collide_point(*touch.pos):
return True
else:
self.lab.text = 'Pos: (%d,%d)' % (touch.x, touch.y)
return True
def switch_to(self, header):
# set the Screen manager to load the appropriate screen
# linked to the tab head instead of loading content
self.manager.current = header.screen
# we have to replace the functionality of the original switch_to
self.current_tab.state = "normal"
header.state = 'down'
self._current_tab = header
def select_to(self, *args):
try:
print(args[1][0])
iw = Image.open(args[1][0])
iw.save('./phase.jpg')
gray = iw.convert('1')
gray.save('./gray_im.jpg')
self.img3.source = './gray_im.jpg'
self.img4.source = './gray_im.jpg'
self.img.source = './phase.jpg'
self.img.reload()
self.img3.reload()
self.img4.reload()
except:
pass
def update_touch_label(self, label, touch):
label.text = 'Pos:(%d, %d)' % (touch.x, touch.y)
label.texture_update()
label.pos = touch.pos
label.size = label.texture_size[0] + 20, label.texture_size[1] + 20
class TestApp(App):
title = 'Screen Widget'
def build(self):
return RootWidget()
def on_pause(self):
return True
if __name__ == '__main__':
TestApp().run()
Test.kv
#:kivy 1.10.1
<RootWidget>:
manager: manager
img: img
img3: img3
img4: img4
lab: lab
do_default_tab: False
ScreenManager:
id: manager
Screen:
id: sc1
name:'Load img'
FileChooserIconView:
canvas.before:
Color:
rgb: 0.5, 0.4, 0.5
Rectangle:
pos: self.pos
size: self.size
on_selection:
root.select_to(*args)
Screen:
id: sc2
name: 'Image'
FloatLayout:
Button:
id: lab
pos_hint: {"right": 0.55, 'top': 1}
size_hint: .15,0.1
RelativeLayout:
Image:
id: img
on_touch_down:
str('Relative:{}'.format(args[1].pos))
pos_hint: {"left": 1, 'bottom': 1}
size_hint: 0.5, 1
allow_stretch: True
RelativeLayout:
Image:
id: img3
pos_hint: {"right": 1, 'bottom': 1}
size_hint: 0.5, 1
allow_stretch: True
Screen:
id: sc3
name: 'Image_'
FloatLayout:
Image:
id: img4
keep_data: True
post: self.pos
size: self.size
TabbedPanelHeader:
text: sc1.name
background_color: 1, 0, 0, 1
screen: sc1.name
TabbedPanelHeader:
text: sc2.name
background_color: 1, 1, 0, 1
screen: sc2.name
TabbedPanelHeader:
text: sc3.name
background_color: 1, 0, 1, 1
screen: sc3.name
In your Test.kv file, after FileChooserIconView: add multiselect: True
FileChooserIconView:
multiselect: True
This will allow the selection of multiple files.
If you use FileChooserListView instead of FileChooserIconView, the file chooser window will not discriminate selected/not selected files visually. You can still select multiple files, but you need to remember the selected files. The first click selects and the second click deselects. I hope the developers of this widget will correct this issue soon.
Here is an example that does what I think you want:
import os
import kivy
from kivy import platform
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
import kivy.garden.filebrowser
class FileBrowserApp(App):
def build(self):
self.root = FloatLayout()
button = Button(text='Select Files', pos_hint={'x':0, 'y': 0}, size_hint=(0.2, 0.1))
button.bind(on_press=self.do_select)
self.root.add_widget(button)
return self.root
def do_select(self, *args):
homeDir = None
if platform == 'win':
homeDir = os.environ["HOMEPATH"]
elif platform == 'android':
homeDir = os.path.dirname(os.path.abspath(__file__))
elif platform == 'linux':
homeDir = os.environ["HOME"]
self.fbrowser = kivy.garden.filebrowser.FileBrowser(select_string='Select',
multiselect=True, filters=['*.png'], path=homeDir)
self.root.add_widget(self.fbrowser)
self.fbrowser.bind(
on_success=self._fbrowser_success,
on_canceled=self._fbrowser_canceled,
on_submit=self._fbrowser_success)
def _fbrowser_success(self, fbInstance):
if len(fbInstance.selection) == 0:
return
selected = []
for file in fbInstance.selection:
selected.append(os.path.join(fbInstance.path, file))
print('selected: ' + str(selected))
self.root.remove_widget(self.fbrowser)
self.fbrowser = None
def _fbrowser_canceled(self, instance):
self.root.remove_widget(self.fbrowser)
self.fbrowser = None
if __name__=="__main__":
app = FileBrowserApp()
app.run()
test.kv
:kivy 1.10.0
<CRUD>:
title: self.mode + " State"
size_hint: None, None
size: 350, 350
auto_dismiss: False
BoxLayout:
orientation: "vertical"
GridLayout:
cols: 2
Label:
text: root.label_rec_id
Label:
id: userid
text: root.col_data[0] # root.userid
Label:
text: "First Name"
TextInput:
id: fname
text: root.col_data[1] # root.fname
Label:
text: "Last Name"
TextInput:
id: lname
text: root.col_data[2] # root.lname
Button:
size_hint: 1, 0.4
text: "Save Changes"
on_release:
root.package_changes(fname.text, lname.text)
app.root.update_changes(root)
root.dismiss()
Button:
size_hint: 1, 0.4
text: "Cancel Changes"
on_release: root.dismiss()
when click on any row then edit form open.Can i use same form for add user.
At this time i click on add user then user.py file run and open a new form.how to use same form for both add update.
Yes, you can use the same form to add city. In the example, I added a button, a method add_record, some variable e.g. mode of type StringProperty, and INSERT SQL command. Please refer to the example for details.
Example
main.py
import kivy
kivy.require('1.10.0') # replace with your current kivy version !
import sqlite3 as lite
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import BooleanProperty, ListProperty, StringProperty, ObjectProperty, NumericProperty
from kivy.lang import Builder
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.button import Button
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 kivy.core.window import Window
Window.size = (500, 500)
MAX_TABLE_COLS = 3
con = lite.connect('company.db')
# con.text_factory = str
cur = con.cursor()
class CRUD(Popup):
"""CRUD - Create, Read, Update, Delete"""
label_id_text = ObjectProperty(None)
label_id_data = ObjectProperty(None)
mode = StringProperty("")
label_rec_id = StringProperty("UserID")
start_point = NumericProperty(0)
col_data = ListProperty(["", "", ""])
def __init__(self, obj, **kwargs):
super(CRUD, self).__init__(**kwargs)
self.mode = obj.mode
if obj.mode == "Add":
self.label_id_text.opacity = 0 # invisible
self.label_id_data.opacity = 0 # invisible
else:
self.label_id_text.opacity = 1 # visible
self.label_id_data.opacity = 1 # visible
self.start_point = obj.start_point
self.col_data[0] = obj.rv_data[obj.start_point]["text"]
self.col_data[1] = obj.rv_data[obj.start_point + 1]["text"]
self.col_data[2] = obj.rv_data[obj.start_point + 2]["text"]
def package_changes(self, fname, lname):
self.col_data[1] = fname
self.col_data[2] = lname
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)
rv_data = ObjectProperty(None)
start_point = NumericProperty(0)
mode = StringProperty("")
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
self.rv_data = rv.data
def on_press(self):
self.mode = "Update"
self.start_point = 0
end_point = MAX_TABLE_COLS
rows = len(self.rv_data) // MAX_TABLE_COLS
for row in range(rows):
if self.index in list(range(end_point)):
break
self.start_point += MAX_TABLE_COLS
end_point += MAX_TABLE_COLS
popup = CRUD(self)
popup.open()
class RV(BoxLayout):
rv_data = ListProperty([])
start_point = NumericProperty(0)
mode = StringProperty("")
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.get_users()
def get_users(self):
'''This result retrieve from database'''
self.rv_data = []
cur.execute("SELECT * FROM Users ORDER BY UserID ASC")
rows = cur.fetchall()
# create data_items
for row in rows:
for col in row:
self.rv_data.append(col)
def add_record(self):
self.mode = "Add"
popup = CRUD(self)
popup.open()
def update_changes(self, obj):
if obj.mode == "Add":
# insert record into Database Table
cur.execute("INSERT INTO Users VALUES(NULL, ?, ?)",
(obj.col_data[1], obj.col_data[2]))
else:
# update Database Table
cur.execute("UPDATE Users SET FirstName=?, LastName=? WHERE UserID=?",
(obj.col_data[1], obj.col_data[2], obj.col_data[0]))
con.commit()
self.get_users()
class ListUser(App):
title = "Users"
def build(self):
self.root = Builder.load_file('main.kv')
return RV()
if __name__ == '__main__':
ListUser().run()
main.kv
#:kivy 1.10.0
<CRUD>:
label_id_text: label_id_text
label_id_data: label_id_data
title: self.mode + " State"
size_hint: None, None
size: 350, 350
auto_dismiss: False
BoxLayout:
orientation: "vertical"
GridLayout:
cols: 2
Label:
id: label_id_text
text: "ID"
Label:
id: label_id_data
text: root.col_data[0] # root.userid
Label:
text: "First Name"
TextInput:
id: fname
text: root.col_data[1] # root.fname
Label:
text: "Last Name"
TextInput:
id: lname
text: root.col_data[2] # root.lname
Button:
size_hint: 1, 0.4
text: "Confirm " + root.mode
on_release:
root.package_changes(fname.text, lname.text)
app.root.update_changes(root)
root.dismiss()
Button:
size_hint: 1, 0.4
text: "Cancel " + root.mode
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"
Button:
size_hint: 1, 0.1
text: "Add Record"
on_press: root.add_record()
GridLayout:
size_hint: 1, None
size_hint_y: None
height: 25
cols: 3
Label:
text: "ID"
Label:
text: "First Name"
Label:
text: "Last Name"
BoxLayout:
RecycleView:
viewclass: 'SelectableButton'
data: [{'text': str(x)} for x in root.rv_data]
SelectableRecycleGridLayout:
cols: 3
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
Output
I am working on an application where I want to add n image frames in a vertical BoxLayout. The number of n frames depend on the desktop screen size. Next to this the frames will be filled dynamically with images. I was able to develop this in a static manner with a fixed number of frames.
Now I am trying to convert this to a dynamic solution where n frame widgets will be created depending on the height of the screen (in the example below 7 widgets). And I am lost..... :(
The frames should 'sit' between a top bar and a bottom bar. In the .kv file this is indicated by the # Ilist: line.
I have the following questions:
1) How could I add these widgets dynamically using a .kv file?
2) How could I reference to these individual frame widgets for assigning images dynamically? For example: frame[idx] = swid?
Thanks very much for your time and shared knowledge in advance.
The Python file pplay.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.properties import StringProperty
class IListItem(Widget):
sid = StringProperty('')
image = StringProperty('')
label = StringProperty('')
pass
class IList(Widget):
pass
class pplayHome(BoxLayout):
def init_player(self):
global swid
self.Ilayout = IList()
for idx in range (1, 7):
swid = IListItem(
sid = "s" + str(idx),
image = 'empty_image.png',
label = 'Image' + str(idx)
)
self.Ilayout.add_widget(swid)
class pplayApp(App):
def build(self):
Window.clearcolor = (1,1,1,1)
Window.borderless = True
Window.size = 275, 1080
homeWin = pplayHome()
homeWin.init_player()
return homeWin
if __name__ == "__main__":
pplayApp().run()
and the Kivy file pplay.kv
# File: pplay.kv
<IList#BoxLayout>
pid: self.pid
source: self.source
label: self.label
size_hint_y: None
height: "120dp"
BoxLayout:
orientation: "vertical"
Label:
size_hint: None, 1
text: root.label
Image:
size_hint: None, 1
source: root.source
<pplayHome>:
orientation: "vertical"
ActionBar:
font_size: 8
size: (275,25)
# background color in Kivy acts as a tint and not just a solid color.
# set a pure white background image first.
background_image: 'white-bg.png'
background_color: 0,.19,.34,1
ActionView:
ActionPrevious:
with_previous: False
app_icon: 'trButton.png'
ActionOverflow:
ActionButton:
icon: 'butt_exit.png'
on_press: root.exit_player()
# Ilist:
BoxLayout:
height: "10dp"
size_hint_y: None
pos_x: 0
canvas.before:
Color:
rgb: 0.55,0.77,0.25 # groen
Rectangle:
pos: self.pos
size: self.size
You were actually pretty close.
Here is the slightly changed kv file, with added colored rectangles for illustration of sizes:
# File: pplay.kv
<IListItem#Widget>
source: ''
label: ''
size_hint_y: None
height: "120dp"
canvas.before:
Color:
rgb: 0.55,0.77*self.idx/7,0.25 # groen
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: "vertical"
size: root.size
pos: root.pos
Label:
size_hint: None, 1
text: root.label
canvas.before:
Color:
rgb: 0.77*root.idx/7,0.55,0.25 # groen
Rectangle:
pos: self.pos
size: self.size
Image:
size_hint: None, 1
source: root.source
<pplayHome>:
orientation: "vertical"
ActionBar:
font_size: 8
size: (275,25)
# background color in Kivy acts as a tint and not just a solid color.
# set a pure white background image first.
background_image: 'white-bg.png'
background_color: 0,.19,.34,1
ActionView:
ActionPrevious:
with_previous: False
app_icon: 'trButton.png'
ActionOverflow:
ActionButton:
icon: 'butt_exit.png'
on_press: root.exit_player()
IList:
id: ilist
orientation: 'vertical'
BoxLayout:
height: "10dp"
size_hint_y: None
pos_x: 0
canvas.before:
Color:
rgb: 0.55,0.77,0.25 # groen
Rectangle:
pos: self.pos
size: self.size
In pplay.py, the essential part is to retrieve the IList widget using self.ids['ilist']. It also shows how its children (the IListItems) can be retrieved via a differentiating property.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.properties import StringProperty, NumericProperty
class IListItem(Widget):
idx = NumericProperty(1)
sid = StringProperty('')
image = StringProperty('')
label = StringProperty('')
pass
class IList(BoxLayout):
pass
class pplayHome(BoxLayout):
def init_player(self):
#global swid
print self.ids
ilayout = self.ids['ilist']
for idx in range (0, 7):
swid = IListItem(
sid = "s" + str(idx),
image = 'empty_image.png',
label = 'Image' + str(idx),
idx=idx
)
ilayout.add_widget(swid)
children_ids = [il.sid for il in ilayout.children]
print children_ids
print ilayout.children[children_ids.index('s3')]
class pplayApp(App):
def build(self):
# Window.clearcolor = (1,1,1,1)
Window.borderless = True
Window.size = 275, 1080
homeWin = pplayHome()
homeWin.init_player()
return homeWin
if __name__ == "__main__":
pplayApp().run()
I am new to kivy wanted to know how we can bind textinput box to suggestion so that user and touch and select the suggestions.
I have long list of buttons out of which i want to select on the basis of name.
i am using kivymd i dont real know how to do it in kivy but here are the codes for kivymd
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen
from kivymd.icon_definitions import md_icons
from kivymd.app import MDApp
from kivymd.uix.list import OneLineIconListItem
Builder.load_string(
'''
#:import images_path kivymd.images_path
<CustomOneLineIconListItem>:
IconLeftWidget:
icon: root.icon
<PreviousMDIcons>:
BoxLayout:
orientation: 'vertical'
spacing: dp(10)
padding: dp(20)
BoxLayout:
size_hint_y: None
height: self.minimum_height
MDIconButton:
icon: 'magnify'
MDTextField:
id: search_field
hint_text: 'Search icon'
on_text: root.set_list_md_icons(self.text, True)
RecycleView:
id: rv
key_viewclass: 'viewclass'
key_size: 'height'
RecycleBoxLayout:
padding: dp(10)
default_size: None, dp(48)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
'''
)
class CustomOneLineIconListItem(OneLineIconListItem):
icon = StringProperty()
class PreviousMDIcons(Screen):
def set_list_md_icons(self, text="", search=False):
'''Builds a list of icons for the screen MDIcons.'''
def add_icon_item(name_icon):
self.ids.rv.data.append(
{
"viewclass": "CustomOneLineIconListItem",
"icon": name_icon,
"text": name_icon,
"callback": lambda x: x,
}
)
self.ids.rv.data = []
for name_icon in md_icons.keys():
if search:
if text in name_icon:
add_icon_item(name_icon)
else:
add_icon_item(name_icon)
class MainApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = PreviousMDIcons()
def build(self):
return self.screen
def on_start(self):
self.screen.set_list_md_icons()
MainApp().run()
some results
here is the screen shot result when your not searching for anything
here is a screen shot when i am trying to search
second
here is a result when the word am searching does not exist
Here is a simple example that will use the use text input to search in the option_list and will display the suggestion under the text input widget.
I used the kivymd widget to get a nice look design you replaced with the normal kivy widgets if you want
from kivy.properties import ListProperty
from kivymd.app import MDApp
from kivymd.uix.list import OneLineAvatarIconListItem
from kivymd.uix.textfield import MDTextField
kv = """
Screen:
BoxLayout:
orientation: 'vertical'
spacing: 1
BoxLayout:
size_hint_y: 1/5
canvas.before:
Color:
rgba: 0, 0, 0, 1
Rectangle:
pos: self.pos
size: self.size[0], 2
MDIconButton:
icon: 'magnify'
size_hint_y: 1
SearchTextInput:
id: Search_TextInput_id
size_hint_y: .97
pos_hint:{ 'left':0 , 'top': 1}
hint_text: 'search'
hint_text_color: 1,1,1,1
icon_left: 'magnify'
mode: "fill"
helper_text_mode: "persistent"
helper_text: "Search"
line_color: [1,1,1,1]
color_normal: [1,1,1,1]
font_size: .35 * self.height
active_line: False
multiline: False
MDIconButton:
icon: 'close'
size_hint_y:1
text_color: 0,0,0,1
BoxLayout:
orientation: 'vertical'
padding: 4
RecycleView:
viewclass: 'Search_Select_Option'
data:app.rv_data
RecycleBoxLayout:
spacing: 15
padding : 10
default_size: None, None
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<Search_Select_Option>:
on_release: print(self.text)
IconRightWidget:
icon: "arrow-top-left"
"""
class Search_Select_Option(OneLineAvatarIconListItem):
pass
class SearchTextInput(MDTextField):
option_list = 'one1,two1,two2,three1,three2,three3,four1,four2,four3,four4,five1,five2,five3,five4,five5'.split(',')
def on_text(self, instance, value):
app = MDApp.get_running_app()
option_list = list(set(self.option_list + value[:value.rfind(' ')].split(' ')))
val = value[value.rfind(' ') + 1:]
if not val:
return
try:
app.option_data = []
for i in range(len(option_list)):
word = [word for word in option_list if word.startswith(val)][0][len(val):]
if not word:
return
if self.text + word in option_list:
if self.text + word not in app.option_data:
popped_suggest = option_list.pop(option_list.index(str(self.text + word)))
app.option_data.append(popped_suggest)
app.update_data(app.option_data)
except IndexError:
pass
class RVTestApp(MDApp):
rv_data = ListProperty()
def update_data(self, rv_data_list):
self.rv_data = [{'text': item} for item in rv_data_list]
print(self.rv_data, 'update')
def build(self):
return Builder.load_string(kv)
RVTestApp().run()
Incase anyone is wondering how can get the value of the selected item from the list, I have a little updated version here:
from kivymd.icon_definitions import md_icons
from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.list import OneLineListItem
from kivy.uix.screenmanager import Screen
KV = '''
<ListSelect>
BoxLayout:
orientation: 'vertical'
spacing: dp(10)
padding: dp(20)
pos_hint:{'center_x': 0.5, 'y': 0.85}
BoxLayout:
size_hint_y: None
height: self.minimum_height
MDIconButton:
icon: 'magnify'
MDTextField:
id: search_field
hint_text: 'Search icon'
on_text:
root.set_list(self.text)
RecycleView:
pos_hint:{'center_x': 0.5, 'center_y': 0.4}
MDList:
id: container
'''
class ListSelect(Screen):
def pressed(self, value):
# value here is the OneLineListItem
self.ids.container.clear_widgets()
# set TextField text to selected list item
self.ids.search_field.text = value.text
print(value.text)
def set_list(self, text=" "):
# text defaults to blank space to not show any icons initally
# each OneLineListItem takes the pressed func on press
self.ids.container.clear_widgets() # refresh list
for icon in md_icons.keys():
# using casefold() to make the input case insensitve
if icon.startswith(text.casefold()):
self.ids.container.add_widget(
OneLineListItem(text=icon, on_press=self.pressed)
)
class Test(MDApp):
def build(self):
Builder.load_string(KV)
self.screen = ListSelect()
return self.screen
def on_start(self):
self.screen.set_list()
Test().run()
One catch: the search needs quite a long time to finish. If anyone has any hints on how to speed it up, you are very welcome!