带有LIKE搜索的Python Tkinter自动完成组合框? [英] Python Tkinter Autocomplete combobox with LIKE search?
问题描述
我正在尝试使用预定义的值填充Tkinter组合框。它正在填充,我能够输入并获得建议。但是,为了做到这一点,我必须明确地知道前几个字符。如果我知道字符串中间或结尾的一些文本,则没有用,因为组合框仅执行'LIKE%'搜索,而不是'%LIKE%'搜索。
I am trying to populate a Tkinter combobox with pre-defined values to select from. It is populating and I am able to type in and get suggestions. However, in order to do this I have to definitely know the first few characters. If I know some text in the middle or end of the string, its of no use because the combobox does only a 'LIKE%' search and not a '%LIKE%' search.
预期的输出(键入单词 Ceramic将获取包含字符串的所有名称。注意:这不是Tkinter屏幕截图):
Expected Output (Typing the word "Ceramic" fetches all names containing the string. Note: This is not a Tkinter screenshot):
这是我迄今为止对代码的修改,如果有人可以建议的话如何修改 AutocompleteCombobox 类以进行LIKE搜索,就很好了。
This is my adaptation of the code till now, if anyone can suggest how to modify the AutocompleteCombobox class to do a LIKE search, it would be great.
下面的代码示例,的值是蔓越莓和草莓,我的要求是键入浆果并获得两种水果的建议。
The below working piece of code, as an example, has values "Cranberry" and "Strawberry" , my requirement is to type "berry" and get suggestions of both fruits.
import Tkinter
import ttk
import sqlite3
class AutocompleteCombobox(ttk.Combobox):
def set_completion_list(self, completion_list):
"""Use our completion list as our drop down selection menu, arrows move through menu."""
self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list
self._hits = []
self._hit_index = 0
self.position = 0
self.bind('<KeyRelease>', self.handle_keyrelease)
self['values'] = self._completion_list # Setup our popup menu
def autocomplete(self, delta=0):
"""autocomplete the Combobox, delta may be 0/1/-1 to cycle through possible hits"""
if delta: # need to delete selection otherwise we would fix the current position
self.delete(self.position, Tkinter.END)
else: # set position to end so selection starts where textentry ended
self.position = len(self.get())
# collect hits
_hits = []
for element in self._completion_list:
if element.lower().startswith(self.get().lower()): # Match case insensitively
_hits.append(element)
# if we have a new hit list, keep this in mind
if _hits != self._hits:
self._hit_index = 0
self._hits=_hits
# only allow cycling if we are in a known hit list
if _hits == self._hits and self._hits:
self._hit_index = (self._hit_index + delta) % len(self._hits)
# now finally perform the auto completion
if self._hits:
self.delete(0,Tkinter.END)
self.insert(0,self._hits[self._hit_index])
self.select_range(self.position,Tkinter.END)
def handle_keyrelease(self, event):
"""event handler for the keyrelease event on this widget"""
if event.keysym == "BackSpace":
self.delete(self.index(Tkinter.INSERT), Tkinter.END)
self.position = self.index(Tkinter.END)
if event.keysym == "Left":
if self.position < self.index(Tkinter.END): # delete the selection
self.delete(self.position, Tkinter.END)
else:
self.position = self.position-1 # delete one character
self.delete(self.position, Tkinter.END)
if event.keysym == "Right":
self.position = self.index(Tkinter.END) # go to end (no selection)
if len(event.keysym) == 1:
self.autocomplete()
# No need for up/down, we'll jump to the popup
# list at the position of the autocompletion
def test(test_list):
"""Run a mini application to test the AutocompleteEntry Widget."""
root = Tkinter.Tk(className='AutocompleteCombobox')
combo = AutocompleteCombobox(root)
combo.set_completion_list(test_list)
combo.pack()
combo.focus_set()
# I used a tiling WM with no controls, added a shortcut to quit
root.bind('<Control-Q>', lambda event=None: root.destroy())
root.bind('<Control-q>', lambda event=None: root.destroy())
root.mainloop()
if __name__ == '__main__':
test_list = ('apple', 'banana', 'Cranberry', 'dogwood', 'alpha', 'Acorn', 'Anise', 'Strawberry' )
test(test_list)
推荐答案
我怀疑您是否需要
if self.get().lower() in element.lower():
而不是
if element.lower().startswith(self.get().lower()):
获取类似%LIKE%$的数据c $ c>在数据库中
但是我不知道您是否获得了很好的效果,因为此 Combobox
用建议替换文本,因此,如果键入 be
,它将找到s Cranberry
并放在 be
位置,就不能写 ber
。
But I don't know if you get good effect because this Combobox
replaces text with suggestion so if you type be
then it finds Cranberry
and put in place be
and you can't write ber
.
也许您应该将蔓越莓
显示为分隔(下拉)列表或弹出提示。
Maybe you should display Cranberry
as separated (dropdown) list, or popup tip.
或者也许您将不得不使用 string.find()
突出显示 Cranberry
中的正确位置,然后继续在正确的位置键入 ber
。
Or maybe you will have to use string.find()
to highlight correct place in Cranberry
and continue to type ber
in correct place.
编辑:示例如何使用条目
和列表框
显示过滤列表
example how to use Entry
and Listbox
to display filtered list
在 listbox_update
我添加了排序列表(比较小写字符串)
In listbox_update
I added sorting list (comparing lower case strings)
#!/usr/bin/env python3
import tkinter as tk
def on_keyrelease(event):
# get text from entry
value = event.widget.get()
value = value.strip().lower()
# get data from test_list
if value == '':
data = test_list
else:
data = []
for item in test_list:
if value in item.lower():
data.append(item)
# update data in listbox
listbox_update(data)
def listbox_update(data):
# delete previous data
listbox.delete(0, 'end')
# sorting data
data = sorted(data, key=str.lower)
# put new data
for item in data:
listbox.insert('end', item)
def on_select(event):
# display element selected on list
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print('---')
# --- main ---
test_list = ('apple', 'banana', 'Cranberry', 'dogwood', 'alpha', 'Acorn', 'Anise', 'Strawberry' )
root = tk.Tk()
entry = tk.Entry(root)
entry.pack()
entry.bind('<KeyRelease>', on_keyrelease)
listbox = tk.Listbox(root)
listbox.pack()
#listbox.bind('<Double-Button-1>', on_select)
listbox.bind('<<ListboxSelect>>', on_select)
listbox_update(test_list)
root.mainloop()
从完整列表开始
仅包含过滤后的项目
编辑:2020.07.21
如果要使用< KeyPress>
,则必须更改 on_keyrelease
并使用 event.char
, event.keysym
和/或 event.keycode
,因为 KeyPress
是在 tkinter之前执行的
更新条目
中的文本,您必须向<$ c $中的文本添加 event.char
c>条目(或在按退格键
时删除最后一个字符)
If you want to use <KeyPress>
then you have to change on_keyrelease
and use event.char
, event.keysym
and/or event.keycode
because KeyPress
is executed before tkinter
update text in Entry
and you have to add event.char
to text in Entry
(or remove last char when you press backspace
)
if event.keysym == 'BackSpace':
value = event.widget.get()[:-1] # remove last char
else:
value = event.widget.get() + event.char # add new char at the end
其他特殊键可能需要进行其他更改 Ctrl + A
, Ctrl + X
, Ctrl + C
, Ctrl + E
等,这会带来很大的问题。
It may need other changes for other special keys Ctrl+A
, Ctrl+X
, Ctrl+C
, Ctrl+E
, etc. and it makes big problem.
#!/usr/bin/env python3
import tkinter as tk
def on_keypress(event):
print(event)
print(event.state & 4) # Control
print(event.keysym == 'a')
# get text from entry
if event.keysym == 'BackSpace':
# remove last char
value = event.widget.get()[:-1]
elif (event.state & 4): # and (event.keysym in ('a', 'c', 'x', 'e')):
value = event.widget.get()
else:
# add new char at the end
value = event.widget.get() + event.char
#TODO: other special keys
value = value.strip().lower()
# get data from test_list
if value == '':
data = test_list
else:
data = []
for item in test_list:
if value in item.lower():
data.append(item)
# update data in listbox
listbox_update(data)
def listbox_update(data):
# delete previous data
listbox.delete(0, 'end')
# sorting data
data = sorted(data, key=str.lower)
# put new data
for item in data:
listbox.insert('end', item)
def on_select(event):
# display element selected on list
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print('---')
# --- main ---
test_list = ('apple', 'banana', 'Cranberry', 'dogwood', 'alpha', 'Acorn', 'Anise', 'Strawberry' )
root = tk.Tk()
entry = tk.Entry(root)
entry.pack()
entry.bind('<KeyPress>', on_keypress)
listbox = tk.Listbox(root)
listbox.pack()
#listbox.bind('<Double-Button-1>', on_select)
listbox.bind('<<ListboxSelect>>', on_select)
listbox_update(test_list)
root.mainloop()
BTW:
您还可以使用 textvariable
项中的 ,其中 StringVar
和 trace
在 StringVar
更改内容时执行功能。
You can also use textvariable
in Entry
with StringVar
and trace
which executes function when StringVar
changes content.
var_text = tk.StringVar()
var_text.trace('w', on_change)
entry = tk.Entry(root, textvariable=var_text)
entry.pack()
#!/usr/bin/env python3
import tkinter as tk
def on_change(*args):
#print(args)
value = var_text.get()
value = value.strip().lower()
# get data from test_list
if value == '':
data = test_list
else:
data = []
for item in test_list:
if value in item.lower():
data.append(item)
# update data in listbox
listbox_update(data)
def listbox_update(data):
# delete previous data
listbox.delete(0, 'end')
# sorting data
data = sorted(data, key=str.lower)
# put new data
for item in data:
listbox.insert('end', item)
def on_select(event):
# display element selected on list
print('(event) previous:', event.widget.get('active'))
print('(event) current:', event.widget.get(event.widget.curselection()))
print('---')
# --- main ---
test_list = ('apple', 'banana', 'Cranberry', 'dogwood', 'alpha', 'Acorn', 'Anise', 'Strawberry' )
root = tk.Tk()
var_text = tk.StringVar()
var_text.trace('w', on_change)
entry = tk.Entry(root, textvariable=var_text)
entry.pack()
listbox = tk.Listbox(root)
listbox.pack()
#listbox.bind('<Double-Button-1>', on_select)
listbox.bind('<<ListboxSelect>>', on_select)
listbox_update(test_list)
root.mainloop()
这篇关于带有LIKE搜索的Python Tkinter自动完成组合框?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!