带有LIKE搜索的Python Tkinter自动完成组合框? [英] Python Tkinter Autocomplete combobox with LIKE search?

查看:111
本文介绍了带有LIKE搜索的Python Tkinter自动完成组合框?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用预定义的值填充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%在数据库中

但是我不知道您是否获得了很好的效果,因为此 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆