Python ttk.combobox 强制打开/显示
我正在尝试扩展ttk的下拉框类,让它可以自动建议。现在的代码运行得不错,但我希望在用户输入一些文字后,能够自动显示下拉列表,而不需要移开输入框的焦点。
我遇到的问题是,找不到一种方法来强制显示下拉列表。在Python的文档中,我没有找到相关的说明,不过在tk的文档中,我发现了一个方法,似乎可以做到这一点,但在Python的封装中似乎没有实现。
我还尝试在自动建议出现后生成一个向下箭头的按键事件,虽然这样可以显示下拉列表,但会导致输入框失去焦点,而在这个事件后再尝试设置焦点似乎也不管用(焦点没有恢复)。
有没有人知道可以用来实现这个功能的函数?
我使用的代码是针对Python 3.3,并且只用了标准库:
class AutoCombobox(ttk.Combobox):
def __init__(self, parent, **options):
ttk.Combobox.__init__(self, parent, **options)
self.bind("<KeyRelease>", self.AutoComplete_1)
self.bind("<<ComboboxSelected>>", self.Cancel_Autocomplete)
self.bind("<Return>", self.Cancel_Autocomplete)
self.autoid = None
def Cancel_Autocomplete(self, event=None):
self.after_cancel(self.autoid)
def AutoComplete_1(self, event):
if self.autoid != None:
self.after_cancel(self.autoid)
if event.keysym in ["BackSpace", "Delete", "Return"]:
return
self.autoid = self.after(200, self.AutoComplete_2)
def AutoComplete_2(self):
data = self.get()
if data != "":
for entry in self["values"]:
match = True
try:
for index in range(0, len(data)):
if data[index] != entry[index]:
match = False
break
except IndexError:
match = False
if match == True:
self.set(entry)
self.selection_range(len(data), "end")
self.event_generate("<Down>",when="tail")
self.focus_set()
break
self.autoid = None
2 个回答
1
你不需要去继承 ttk.Combobox 来处理这个事件;只需使用 event_generate 来强制显示下拉菜单:
box = Combobox(...)
def callback(box):
box.event_generate('<Down>')
2
下面展示了一种使用工具提示来实现这种用户体验的方法。这个例子是用 PySimpleGUI
实现的,但也很容易改成“纯” tkinter 的方式。
from functools import partial
from typing import Callable, Any
from fuzzywuzzy import process, fuzz
import PySimpleGUI as sg
# SG: Helper functions:
def clear_combo_tooltip(*_, ui_handle: sg.Element, **__) -> None:
if tt := ui_handle.TooltipObject:
tt.hidetip()
ui_handle.TooltipObject = None
def show_combo_tooltip(ui_handle: sg.Element, tooltip: str) -> None:
ui_handle.set_tooltip(tooltip)
tt = ui_handle.TooltipObject
tt.y += 40
tt.showtip()
def symbol_text_updated(event_data: dict[str, Any], all_values: list[str], ui_handle: sg.Element) -> None:
new_text = event_data[ui_handle.key]
if new_text == '':
ui_handle.update(values=all_values)
return
matches = process.extractBests(new_text, all_values, scorer=fuzz.ratio, score_cutoff=40)
sym = [m[0] for m in matches]
ui_handle.update(new_text, values=sym)
# tk.call('ttk::combobox::Post', ui_handle.widget) # This opens the list of options, but takes focus
clear_combo_tooltip(ui_handle=ui_handle)
show_combo_tooltip(ui_handle=ui_handle, tooltip="\n".join(sym))
# Prepare data:
all_symbols = ["AAPL", "AMZN", "MSFT", "TSLA", "GOOGL", "BRK.B", "UNH", "JNJ", "XOM", "JPM", "META", "PG", "NVDA", "KO"]
# SG: Layout
sg.theme('DarkAmber')
layout = [
[
sg.Text('Symbol:'),
sg.Combo(all_symbols, enable_per_char_events=True, key='-SYMBOL-')
]
]
# SG: Window
window = sg.Window('Symbol data:', layout, finalize=True)
window['-SYMBOL-'].bind("<Key-Down>", "KeyDown")
# SG: Event loop
callbacks: dict[str: Callable] = {
'-SYMBOL-': partial(symbol_text_updated, all_values=all_symbols, ui_handle=window['-SYMBOL-']),
'-SYMBOL-KeyDown': partial(clear_combo_tooltip, ui_handle=window['-SYMBOL-']),
}
unhandled_event_callback = partial(lambda x: print(f"Unhandled event key: {event}. Values: {x}"))
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Exit'):
break
callbacks.get(event, unhandled_event_callback)(values)
# SG: Cleanup
window.close()