I am trying to build a predictive text MDDropdown on a KivyMD application. I have an MDTextField in which the user will enter text. As the user writes down his/her search, the MDDropdown should show the items which include the text written down. On selection of any given item, the full text of the selection should be added to the TextField.
I have tried Python's filter method to do such task, however I have not succeeded. The MDDropdown is never opened when text is entered. Also, I have noticed that the filter function looks for exact matches. Is there a way to filter partial parts of a string?
The Python code is the following:
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
from kivymd.uix.button import MDIconButton, MDTooltip
from kivymd.uix.menu import MDDropdownMenu
from kivy.clock import Clock
class TooltipMDIconButton(MDIconButton, MDTooltip):
pass
# HOJA DE ACTIVIDADES ################################
class IngActivWindow(Screen):
menu_cliente = None
def cliente_dropdown(self):
clientes = ['Amazon', 'Adidas','Banana Republic', 'Calvin Klein','Coach', 'DKNY', 'Microsoft','Nike', 'Sony']
if len(self.ids.name_client.text) != 0:
def filtar_clientes(cliente):
filtrar_valor = self.ids.name_client.text
if cliente in filtrar_valor:
return True
else:
return False
filtered = filter(lambda l: str(self.ids.name_client.text) in l, clientes)
for valores in filtered:
print(valores)
# Create the drop down menu
for cliente in filtered:
menu_items = [{"text": f"{valores}"} for valores in filtered]
self.menu_cliente = MDDropdownMenu(
caller=self.ids.name_client,
items=menu_items,
width_mult=5,
)
self.menu_cliente.open()
self.menu_cliente.bind(on_release=self.set_item_cliente)
def open_clientes_list(self):
clientes = ['Amazon', 'Adidas','Banana Republic', 'Calvin Klein','Coach', 'DKNY', 'Microsoft','Nike', 'Sony']
# Create the drop down menu
menu_items = [{"text": f"{cliente}"} for cliente in clientes]
self.menu_cliente = MDDropdownMenu(
caller=self.ids.name_client,
items=menu_items,
width_mult=5,
)
self.menu_cliente.open()
self.menu_cliente.bind(on_release=self.set_item_cliente)
def set_item_cliente(self, instance_menu, instance_menu_item):
def set_item_cliente(interval):
self.ids.name_client.text = instance_menu_item.text
instance_menu.dismiss()
Clock.schedule_once(set_item_cliente, 0.5)
# WINDOW MANAGER ################################
class WindowManager(ScreenManager):
pass
# MAIN CLASS ################################
class PredictiveDropdown(MDApp):
def build(self):
return WindowManager()
if __name__ == "__main__":
PredictiveDropdown().run()
Kivy code:
<WindowManager>:
id: screen_manager
IngActivWindow:
id: ingActiv
name: 'ingActiv'
<IngActivWindow>:
MDBoxLayout:
orientation: 'horizontal'
size_hint: 1, 1
pos_hint: {'center_x':0.5 , 'center_y':0.5}
padding: '20dp'
MDTextField:
id: name_client
pos_hint: {"x":0, 'center_y': 0.5}
write_tab: False
size_hint: 0.45, None
halign: 'left'
valign: 'center'
hint_text:'Seleccionar Cliente o Prospecto'
on_text: root.cliente_dropdown()
TooltipMDIconButton:
id: client_list
pos_hint: {"x":0, 'center_y': 0.5}
valign: 'center'
icon: 'arrow-down-drop-circle-outline'
tooltip_text: 'Haga clic para visualizar la lista completa de clientes'
on_release: root.open_clientes_list()
Any suggestions of how to achieve the desired task? What am I doing wrong?
Thanks a lot in advance.
EDIT Is there a way to filter by position of the character as well? I changed the filtered variable code. Now when I type, I do get a printed list. However if I write 'a' I get all of the elements with an a, such as 'Calvin Klein'. I would like for the program to print only 'Amazon' and 'Adidas'. I already modified my code accordingly.
The DropDown
is never opened because you are using the iterator
that filter
produces when you print
the results of the filter
. Once an iterator
is used, it cannot be reused. To fix that, simply remove the lines:
for valores in filtered:
print(valores)
Consider using regular expressions. instead of filter
to get your filtered
list.
Two other issues. You are opening the DropDown
in a loop for valores in filtered:
, but I cannot see why you are doing that in a loop. And since you are triggering the cliente_dropdown()
method using on_text
in the kv
, it will be triggered again when your set_item_cliente()
method changes the text. On way to fix that is to disable the cliente_dropdown()
method when dropdown is changing the text.
Here is a modified version of your IngActivWindow
class that addresses those issues:
class IngActivWindow(Screen):
menu_cliente = None
do_dropdown = True
def cliente_dropdown(self):
# do not open a dropdown if the text was changed by the dropdown
if not self.do_dropdown:
return
if self.menu_cliente:
# if dropdown is already open, close it
self.menu_cliente.dismiss()
self.menu_cliente = None
clientes = ['Amazon', 'Adidas','Banana Republic', 'Calvin Klein','Coach', 'DKNY', 'Microsoft','Nike', 'Sony']
filtered_list = []
if len(self.ids.name_client.text) != 0:
prefix = self.ids.name_client.text.title()
filtered = filter(lambda l: l.startswith(prefix), clientes)
menu_items = [{"text": f"{valores}"} for valores in filtered]
if len(menu_items) < 1:
return
self.menu_cliente = MDDropdownMenu(
caller=self.ids.name_client,
callback=self.set_item_cliente, # use callback instead of bind
items=menu_items,
width_mult=5,
)
self.menu_cliente.open()
def open_clientes_list(self):
clientes = ['Amazon', 'Adidas','Banana Republic', 'Calvin Klein','Coach', 'DKNY', 'Microsoft','Nike', 'Sony']
# Create the drop down menu
menu_items = [{"text": f"{cliente}"} for cliente in clientes]
self.menu_cliente = MDDropdownMenu(
caller=self.ids.name_client,
items=menu_items,
width_mult=5,
)
self.menu_cliente.open()
self.menu_cliente.bind(on_release=self.set_item_cliente)
def set_item_cliente(self, instance_menu_item):
def set_item_cliente(interval):
self.do_dropdown = False
self.ids.name_client.text = instance_menu_item.text
self.menu_cliente.dismiss()
self.menu_cliente = None
self.do_dropdown = True
Clock.schedule_once(set_item_cliente, 0.5)