How to set the text of optional labels in .kv from .py - python

I have problem with setting the labels texts.
I need to set different text on the different labels that just have an id in .kv file, so that means that I don't want to create a class for each of the labels.
But I want to have the access to changing each labels text.
Please, help me with that problem on Python 3.6 and Kivy 1.11.1
There is my main.py code:
from kivy.app import App
from kivymd.theming import ThemeManager
from kivymd.label import MDLabel
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.metrics import dp, sp, pt
def toast(text):
from kivymd.toast.kivytoast import toast
toast(text)
class MyScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.menu_items = [
{
"viewclass": "MDMenuItem",
"text": "text%d" % i,
"callback": self.callback,
}
for i in range(1, 3)
]
self.menu_button = None
def change_variable(self, value):
print("\nvalue=", value)
self.VARIABLE = value
print("\tself.VARIABLE=", self.VARIABLE)
def callback(self, *args):
toast(args[0])
class MainApp(App):
title = "KivyMD MDDropdownMenu Demo"
theme_cls = ThemeManager()
def build(self):
return MyScreen()
def results(self):
msc = MyScreen()
msc.ids.firstlabel.text = 'FIRST TEXT'
msc.ids.secondlabel.text = 'SECOND TEXT'
msc.ids.thirdlabel.text = 'THIRD TEXT'
if __name__ == "__main__":
MainApp().run()
There is my main.kv code:
#:import MDDropdownMenu kivymd.menus.MDDropdownMenu
#:import MDRaisedButton kivymd.button.MDRaisedButton
#:import MDLabel kivymd.label.MDLabel
<OptionalLabel#MDLabel>:
halign: 'center'
font_size: dp(12)
<MDRB#MDRaisedButton>:
size_hint: None, None
size: 3 * dp(48), dp(48)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
opposite_colors: True
<MDMenuItem>:
on_release:
app.root.change_variable(self.text)
app.root.menu_button.text = self.text
<MyScreen>:
name: 'myscrn'
AnchorLayout:
anchor_y: 'center'
BoxLayout:
orientation: 'vertical'
size_hint: 0.1, 0.5
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
spacing: dp(10)
MDRB:
id: mainbutton
text: 'MDButton1'
on_release:
root.menu_button = mainbutton
MDDropdownMenu(items=root.menu_items, width_mult=4).open(self)
MDRB:
id: secondbutton
text: 'MDButton2'
on_release:
root.menu_button = secondbutton
MDDropdownMenu(items=root.menu_items, width_mult=4).open(self)
MDRB:
text: 'set the labels'
on_release:
app.results()
OptionalLabel:
id: firstlabel
text: 'label1'
OptionalLabel:
id: secondlabel
text: 'label2'
OptionalLabel:
id: thirdlabel
text: 'label3'
Thanks for attention!

You are pretty close. Accessing a text by ids is the way you want to go, you just have another issue here.
In short, you are actually creating two different MyScreen objects, you have one in your build() (which is actually what shows on the window) and then you are creating another screen in local scope of the "results" function.
class MainApp(App):
title = "KivyMD MDDropdownMenu Demo"
theme_cls = ThemeManager()
def build(self):
return MyScreen() ///THIS IS A OBJECT IN MEMORY
def results(self):
msc = MyScreen() /// THIS IS A DIFFERENT OBJECT IN MEMORY, BUT DOESN'T REFERENCE THE SCREEN IN Build
msc.ids.firstlabel.text = 'FIRST TEXT'
msc.ids.secondlabel.text = 'SECOND TEXT'
msc.ids.thirdlabel.text = 'THIRD TEXT'
In this situation, I'd suggest taking your results function and bringing down into your MyScreen class like the below example. Key point here, you can bind to the on_release function from either KV lang or in Python Class. In the example below, I'm doing so in Python class.
from kivy.app import App
from kivy.lang import Builder
from kivymd.theming import ThemeManager
from kivymd.label import MDLabel
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.metrics import dp, sp, pt
Builder.load_string("""
#:import MDDropdownMenu kivymd.menus.MDDropdownMenu
#:import MDRaisedButton kivymd.button.MDRaisedButton
#:import MDLabel kivymd.label.MDLabel
<OptionalLabel#MDLabel>:
halign: 'center'
font_size: dp(12)
<MDRB#MDRaisedButton>:
size_hint: None, None
size: 3 * dp(48), dp(48)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
opposite_colors: True
<MDMenuItem>:
on_release:
app.root.change_variable(self.text)
app.root.menu_button.text = self.text
<MyScreen>:
name: 'myscrn'
AnchorLayout:
anchor_y: 'center'
BoxLayout:
orientation: 'vertical'
size_hint: 0.1, 0.5
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
spacing: dp(10)
MDRB:
id: mainbutton
text: 'MDButton1'
on_release:
root.menu_button = mainbutton
MDDropdownMenu(items=root.menu_items, width_mult=4).open(self)
MDRB:
id: secondbutton
text: 'MDButton2'
on_release:
root.menu_button = secondbutton
MDDropdownMenu(items=root.menu_items, width_mult=4).open(self)
MDRB:
id: changesresultsbutton
text: 'set the labels'
OptionalLabel:
id: firstlabel
text: 'label1'
OptionalLabel:
id: secondlabel
text: 'label2'
OptionalLabel:
id: thirdlabel
text: 'label3'
""")
def toast(text):
from kivymd.toast.kivytoast import toast
toast(text)
class MyScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.menu_items = [
{
"viewclass": "MDMenuItem",
"text": "text%d" % i,
"callback": self.callback,
}
for i in range(1, 3)
]
self.menu_button = None
self.ids.changesresultsbutton.bind(on_release = self.results)
def change_variable(self, value):
print("\nvalue=", value)
self.VARIABLE = value
print("\tself.VARIABLE=", self.VARIABLE)
def callback(self, *args):
toast(args[0])
def results(self, value):
self.ids.firstlabel.text = 'FIRST TEXT'
self.ids.secondlabel.text = 'SECOND TEXT'
self.ids.thirdlabel.text = 'THIRD TEXT'
class MainApp(App):
title = "KivyMD MDDropdownMenu Demo"
theme_cls = ThemeManager()
def build(self):
return MyScreen()
if __name__ == "__main__":
MainApp().run()

Related

Binding of on_release to OneLineIconListItem in KivyMD

I am trying to modify a KivyMD OneLineIconListItem example from https://kivymd.readthedocs.io/en/latest/themes/icon-definitions/ that does the autocomplete search that I've been trying to figure out for a current project.
TODO: Add on_release to item to print item and add to list
Bind on_release in Kivy List item to function is the closest that I've found to do what I want, but I'm having trouble adapting it.
Here is what I have so far:
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen
from kivymd.app import MDApp
from kivymd.icon_definitions import md_icons
from kivymd.uix.list import OneLineIconListItem
Builder.load_string(
'''
#:import images_path kivymd.images_path
<CustomOneLineIconListItem>
on_release: root.print_item
IconLeftWidget:
icon: root.icon
<PreviousMDIcons>
MDBoxLayout:
orientation: 'vertical'
spacing: dp(10)
padding: dp(20)
MDBoxLayout:
adaptive_height: True
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):
def print_item(self, instance):
print(instance, instance.text)
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()
All you need to do is just replacing
<CustomOneLineIconListItem>
on_release: root.print_item
with,
<CustomOneLineIconListItem>
on_release: root.print_item(self) # Call the method by passing this instance.

MDExpansion Panel - kivy.properties.DictProperty' object has no attribute 'panel_container'

I am currently building a mobile app with kivy. In one of my screens I am trying to include an MDExpansionPanel, although I have tried with a lot of different codes, and searched on the web for solutions, either I get an error, or simply the Expansion Panel is not rendered in my screen. I am using ScreenManager since I have 5 screens, with the possibility of increasing such number.
The relevant Python code is the following:
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
from kivymd.theming import ThemeManager
from kivymd.toast import toast
from kivymd.uix.picker import MDDatePicker, MDTimePicker
from kivymd.uix.expansionpanel import MDExpansionPanel
from kivymd.uix.boxlayout import MDBoxLayout
class LoginWindow(Screen):
pass
class CreateAccountWindow(Screen):
pass
class MainWindow(Screen):
pass
class TravelManagerWindow(Screen):
pass
class IngActivWindow(Screen):
panel_container = ObjectProperty(None)
def on_pre_enter(self, *args):
self.add_panels()
def show_timepicker(self):
picker = MDTimePicker()
picker.bind(time=self.got_time)
picker.open()
def got_time(self, picker_widget, time):
self.text = str(time.hour) + ":" + str(time.minute)
self.focus = False
# selectedTime= self.text
print(self.text)
self.ids.tiempoActiv.text = self.text
def add_panels(self):
for i in range(5):
IngActivWindow.ids.panel_container.add_widget(
MDExpansionPanel(
icon="actividades.png",
content=MyContent(),
panel_cls=MDExpansionPanelOneLine(
text="Text",
)
)
)
class MyContent(MDBoxLayout):
pass
class WindowManager(ScreenManager):
ScreenManager().add_widget(LoginWindow(name='login'))
ScreenManager().add_widget(CreateAccountWindow(name='create'))
ScreenManager().add_widget(MainWindow(name='main'))
ScreenManager().add_widget(IngActivWindow(name='ingActiv'))
ScreenManager().add_widget(TravelManagerWindow(name='travelManager'))
class powerApp1(MDApp):
def build(self):
self.theme_cls.primary_palette = "Teal"
return WindowManager()
if __name__ == "__main__":
powerApp1().run()
The relevant kv code is the following:
(I have an image on the background and an Action Bar before the MDBoxLayout where I am trying to add the Expansion Panel)
<WindowManager>:
LoginWindow:
CreateAccountWindow:
MainWindow:
IngActivWindow:
TravelManagerWindow:
<IngActivWindow>:
name: 'ingActiv'
panel_container: panel_container
FloatLayout:
cols:1
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'ingActiv_background.png'
ActionBar:
pos_hint: {'top':1}
ActionView:
use_separator: True
ActionPrevious:
title: '---------'
with_previous: False
ActionButton:
icon: 'homeIcon.png'
on_press:
root.manager.current = 'main'
root.manager.transition.direction = 'right'
ActionGroup:
text: 'Gastos de Viaje'
mode: 'spinner'
size_hint:(None,None)
size: root.width/5,root.height/12
ActionButton:
text: 'Solicitar'
on_release:
root.manager.current = 'solicitud'
root.manager.transition.direction = 'left'
ActionButton:
text: 'Comprobar'
on_release:
root.manager.current = 'comprobar'
root.manager.transition.direction = 'left'
ActionGroup:
text: 'Opciones'
mode: 'spinner'
size_hint:(None,None)
size: root.width/5,root.height/12
ActionButton:
text: 'Ingresar Actividad'
on_release:
root.manager.current = 'ingActiv'
root.manager.transition.direction = 'left'
ActionButton:
text: 'Enviar Reporte'
ActionButton:
text: 'Cerrar Sesion'
on_release:
root.manager.current = 'login'
root.manager.transition.direction = 'down'
MDBoxLayout:
cols:1
size_hint: 1,0.6
pos_hint: {"center_x": 0.5, "center_y": 0.5}
ScrollView:
GridLayout:
id: panel_container
cols: 1
pos_hint: {"center_x": 0.5, "center_y": 0.5}
<MyContent>:
size_hint: 1, None
height: self.minimum_height
orientation: 'horizontal'
Button:
size_hint: None, None
Thanks a lot in advance for your support,
Have a great day.

Kivy-If A button bind with callback(instance), how to call other functions

What i want is that:
Press button A >> Open choose folder dialog >> Select folder A >> Show path A in label A;
Press button B >> Open choose folder dialog >> Select folder B >> Show path B in label B.
If you run my code, it works very good. But if you read the code carefully, you will find a issue.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.properties import ObjectProperty
from kivy.lang import Builder
Builder.load_string('''
#:kivy 1.11.0
<Root>:
loadFolderA: loadFolderA
loadFolderB: loadFolderB
cols: 2
rows: 2
Button:
id: selectFolderA
text: 'select folder A'
pos_hint: {'center_x': 0.21, 'center_y': .5}
on_release: root.show_select_folder_dialogA()
Button:
id: selectFolderB
text: 'select folder B'
pos_hint: {'center_x': 0.785, 'center_y': .5}
on_release: root.show_select_folder_dialogB()
Label:
id: loadFolderA
text: 'path A'
font_size: 30
pos_hint: {'center_x': 0.21, 'center_y': .5}
Label:
id: loadFolderB
text: 'path B'
font_size: 30
pos_hint: {'center_x': 0.785, 'center_y': .5}
<SelectFolder>:
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserListView:
id: filechooser
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Choose"
on_release: root.choose(filechooser.path, filechooser.selection)
''')
class Root(GridLayout):
posixPathA = StringProperty('')
posixPathB = StringProperty('')
def dismiss_popup(self):
self._popup.dismiss()
def show_select_folder_dialogA(self):
content = SelectFolder(choose=self.chooseA, cancel=self.dismiss_popup)
self._popup = Popup(title="Select Folder", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def show_select_folder_dialogB(self):
content = SelectFolder(choose=self.chooseB, cancel=self.dismiss_popup)
self._popup = Popup(title="Select Folder", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def chooseA(self, path, filename):
self.posixPathA = path
self.loadFolderA.text = self.posixPathA
self.dismiss_popup()
def chooseB(self, path, filename):
self.posixPathB = path
self.loadFolderB.text = self.posixPathB
self.dismiss_popup()
class SelectFolder(FloatLayout):
choose = ObjectProperty(None)
cancel = ObjectProperty(None)
class DropApp(App):
def build(self):
return Root()
if __name__ == '__main__':
DropApp().run()
The function 'show_select_folder_dialog' I write in two times, one for binding with button A, one for button B. And same to the function 'choose'. It really makes me unhappy. So I want to know if I can make the Code know which button is pressed, then pass the folder path to the right label. Hope someone can help.
#
#
It takes all night, now i come to this place:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.properties import ObjectProperty
from kivy.lang import Builder
Builder.load_string('''
#:kivy 1.11.0
<Root>:
loadFolderA: loadFolderA
loadFolderB: loadFolderB
cols: 2
rows: 2
NewButton:
id: selectFolderA
text: 'select folder A'
pos_hint: {'center_x': 0.21, 'center_y': .5}
on_release: self.callback()
NewButton:
id: selectFolderB
text: 'select folder B'
pos_hint: {'center_x': 0.785, 'center_y': .5}
on_release: self.callback()
Label:
id: loadFolderA
text: 'path A'
font_size: 30
pos_hint: {'center_x': 0.21, 'center_y': .5}
Label:
id: loadFolderB
text: 'path B'
font_size: 30
pos_hint: {'center_x': 0.785, 'center_y': .5}
<SelectFolder>:
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserListView:
id: filechooser
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Choose"
on_release: root.choose(filechooser.path, filechooser.selection)
''')
class NewButton(Button):
def __init__(self, **kwargs):
super(Button, self).__init__(**kwargs)
def callback(instance):
print('The button <%s> is being pressed' % instance.text)
Root.show_select_folder_dialog(Root)
class Root(GridLayout):
posixPathA = StringProperty('')
posixPathB = StringProperty('')
def dismiss_popup(self):
self._popup.dismiss()
def show_select_folder_dialog(self):
content = SelectFolder(choose=self.choose, cancel=self.dismiss_popup)
self._popup = Popup(title="Select Folder", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def choose(self, path, filename):
self.posixPathA = path
self.loadFolderA.text = self.posixPathA
self.dismiss_popup()
class SelectFolder(FloatLayout):
choose = ObjectProperty(None)
cancel = ObjectProperty(None)
class DropApp(App):
def build(self):
return Root()
if __name__ == '__main__':
DropApp().run()
My plan is that use Callback(instance) to know which button was calling the function. Then call the Function to open choose folder dialog. It's strange but this line works here:
Root.show_select_folder_dialog(Root)
But I cannot go further. Now the function report error:'TypeError: choose() missing 1 required positional argument: 'filename''
Hope someone can help me!
You can remove both the duplicate A/B methods by sending an idx with the button press:
Builder.load_string('''
#:kivy 1.11.0
<Root>:
loadFolderA: loadFolderA
loadFolderB: loadFolderB
cols: 2
rows: 2
Button:
id: selectFolderA
text: 'select folder A'
pos_hint: {'center_x': 0.21, 'center_y': .5}
on_release: root.show_select_folder_dialog("A") # send an idx here
Button:
id: selectFolderB
text: 'select folder B'
pos_hint: {'center_x': 0.785, 'center_y': .5}
on_release: root.show_select_folder_dialog("B") # send an idx here
Label:
id: loadFolderA
text: 'path A'
font_size: 30
pos_hint: {'center_x': 0.21, 'center_y': .5}
Label:
id: loadFolderB
text: 'path B'
font_size: 30
pos_hint: {'center_x': 0.785, 'center_y': .5}
<SelectFolder>:
BoxLayout:
size: root.size
pos: root.pos
orientation: "vertical"
FileChooserListView:
id: filechooser
BoxLayout:
size_hint_y: None
height: 30
Button:
text: "Cancel"
on_release: root.cancel()
Button:
text: "Choose"
on_release: root.choose(filechooser.path, filechooser.selection)
''')
from functools import partial
class Root(GridLayout):
posixPathA = StringProperty('')
posixPathB = StringProperty('')
def dismiss_popup(self):
self._popup.dismiss()
def show_select_folder_dialog(self, idx):
# pass the idx to the choose method
content = SelectFolder(choose=partial(self.choose, idx),
cancel=self.dismiss_popup)
self._popup = Popup(title="Select Folder", content=content,
size_hint=(0.9, 0.9))
self._popup.open()
def choose(self, idx, path, filename):
if idx == "A": # The "A" button was selected
self.posixPathA = path
self.loadFolderA.text = self.posixPathA
else: # You can use more elifs if you like
self.posixPathB = path
self.loadFolderB.text = self.posixPathB
self.dismiss_popup()
class SelectFolder(FloatLayout):
choose = ObjectProperty(None)
cancel = ObjectProperty(None)
class DropApp(App):
def build(self):
return Root()
if __name__ == '__main__':
DropApp().run()
Hey Friend i have got a simple solution for your code which makes it easier to recognise which button you have chosen for that you can use easygui module:
choice = easygui.buttonbox(title="Choose button",msg="Choose a button",choices=["Button 1","Button 2",])
which opens a box like:
And now you can use your if condition to and do the rest of the process.

Making MDTextField from KivyMD works correct

Hello everyone who have faced the KivyMD lib!
The problem is that I can't make MDTextField works as I need to.
Thats the task that it shall perform:
User inputting the key in the MDTextField and after that press the
button
If the key is correct - something goes after that (for example - toast("Key is CORRECT!"))
If the key is incorrect - that shall be an ERROR (for example - toast('KEY IS INCORRECT!'))
If there are too much characters (for example - more than 5), it have to display something (for example - toast('Too much text!'))
There is main.py:
from kivy.app import App
from kivymd.theming import ThemeManager
from kivymd.label import MDLabel
from kivy.uix.screenmanager import Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.metrics import dp, sp, pt
from kivymd.toast.kivytoast import toast
from kivymd.textfields import MDTextField
class keyinput(MDTextField):
pass
def toast(text):
toast(text)
class MyScreen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.menu_items = [
{
"viewclass": "MDMenuItem",
"text": "text%d" % i,
"callback": self.callback,
}
for i in range(1, 3)
]
self.menu_button = None
def change_variable(self, value):
print("\nvalue=", value)
self.VARIABLE = value
print("\tself.VARIABLE=", self.VARIABLE)
def callback(self, *args):
toast(args[0])
class MainApp(App):
title = "KivyMD MDDropdownMenu Demo"
theme_cls = ThemeManager()
def build(self):
return MyScreen()
def keycheck(self):
if keyinput.text == '12345':
toast('KEY IS CORRECT')
elif len(keyinput.text) > 5:
toast('Too much text!')
else:
toast('KEY IS INCORRECT!')
if __name__ == "__main__":
MainApp().run()
There is main.kv:
#:import MDDropdownMenu kivymd.menus.MDDropdownMenu
#:import MDRaisedButton kivymd.button.MDRaisedButton
#:import MDLabel kivymd.label.MDLabel
<OptionalLabel#MDLabel>:
halign: 'center'
font_size: dp(12)
<MDRB#MDRaisedButton>:
size_hint: None, None
size: 3 * dp(48), dp(48)
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
opposite_colors: True
<keyinput>:
size_hint_x: 0.5
halign: 'center'
pos_hint: {'center_x': .5, 'center_y': .5}
max_text_length: 5
<MDMenuItem>:
on_release:
app.root.change_variable(self.text)
app.root.menu_button.text = self.text
<MyScreen>:
name: 'myscrn'
AnchorLayout:
anchor_y: 'center'
BoxLayout:
orientation: 'vertical'
size_hint: 0.5, 0.5
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
spacing: dp(10)
MDRB:
text: 'check the key'
on_release:
app.keycheck()
keyinput:
hint_text: "print the key here"
You need to assign your keyinput widget an id. Right now it looks like
in your keycheck function, it probably doesn't know what keyinput
means. kivy.org/doc/stable/guide/lang.html#referencing-widgets
Erik

Search Box with suggestions in kivy?

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!

Categories

Resources