Python tkinter-两个异物的联合焦点?

Python tkinter-两个异物的联合焦点?,python,python-3.x,tkinter,ttk,Python,Python 3.x,Tkinter,Ttk,我正在尝试用自动完成来改进Entry的实现。目前,代码使用带有跟踪的StringVar来跟踪更改,并在条目的正下方发布一个列表框。为了使Listbox能够正确地绘制其他小部件,我必须将其主窗口设置为根/顶级窗口 现在我想在条目失去焦点时关闭listbox。问题是,当用户从列表框中选择项目时,条目也会失去焦点。 FocusOut首先发生,这会在做出选择之前关闭列表框 现在我已经解决了这个问题,在FocusOut发生时设置一个延迟调用(50ms),但这个解决方案并不好。 还有别的办法吗?也许可以将列

我正在尝试用自动完成来改进Entry的实现。目前,代码使用带有跟踪的StringVar来跟踪更改,并在条目的正下方发布一个列表框。为了使Listbox能够正确地绘制其他小部件,我必须将其主窗口设置为根/顶级窗口

现在我想在条目失去焦点时关闭listbox。问题是,当用户从列表框中选择项目时,条目也会失去焦点。 FocusOut首先发生,这会在做出选择之前关闭列表框

现在我已经解决了这个问题,在FocusOut发生时设置一个延迟调用(50ms),但这个解决方案并不好。 还有别的办法吗?也许可以将列表框视为条目的一部分

这是我当前用于定时解决方案的代码:

# Based on: http://code.activestate.com/recipes/580770-combobox-autocomplete/ by Miguel Martinez Lopez
# Modified to work with toplevel windows and some other adjustments

from tkinter import *
from tkinter.ttk import *

def autoscroll(sbar, first, last):
    """Hide and show scrollbar as needed."""
    first, last = float(first), float(last)
    if first <= 0 and last >= 1:
        sbar.grid_remove()
    else:
        sbar.grid()
    sbar.set(first, last)


class Combobox_Autocomplete(Entry, object):

    def __init__(self, master, list_of_items=None, autocomplete_function=None, listbox_width=None, listbox_height=7,
                 ignorecase_match=True, startswith_match=True, vscrollbar=True, hscrollbar=True, **kwargs):
        if hasattr(self, "autocomplete_function"):
            if autocomplete_function is not None:
                raise ValueError("Combobox_Autocomplete subclass has 'autocomplete_function' implemented")
        else:
            if autocomplete_function is not None:
                self.autocomplete_function = autocomplete_function
            else:
                if list_of_items is None:
                    raise ValueError("If not guiven complete function, list_of_items can't be 'None'")

                if not ignorecase_match:
                    if startswith_match:
                        def matches_function(entry_data, item):
                            return item.startswith(entry_data)
                    else:
                        def matches_function(entry_data, item):
                            return item in entry_data

                    self.autocomplete_function = lambda entry_data: [item for item in self.list_of_items if
                                                                     matches_function(entry_data, item)]
                else:
                    if startswith_match:
                        def matches_function(escaped_entry_data, item):
                            if re.match(escaped_entry_data, item, re.IGNORECASE):
                                return True
                            else:
                                return False
                    else:
                        def matches_function(escaped_entry_data, item):
                            if re.search(escaped_entry_data, item, re.IGNORECASE):
                                return True
                            else:
                                return False

                    def autocomplete_function(entry_data):
                        escaped_entry_data = re.escape(entry_data)
                        return [item for item in self.list_of_items if matches_function(escaped_entry_data, item)]

                    self.autocomplete_function = autocomplete_function

        self._listbox_height = int(listbox_height)
        self._listbox_width = listbox_width

        self.list_of_items = list_of_items

        self._use_vscrollbar = vscrollbar
        self._use_hscrollbar = hscrollbar

        kwargs.setdefault("background", "white")

        if "textvariable" in kwargs:
            self._entry_var = kwargs["textvariable"]
        else:
            self._entry_var = kwargs["textvariable"] = StringVar()

        Entry.__init__(self, master, **kwargs)

        self._trace_id = self._entry_var.trace('w', self._on_change_entry_var)

        self._listbox = None


        # self.bind("<Tab>", self._on_tab)
        self.bind("<Control-space>",lambda event: self.post_listbox())
        self.bind("<Up>", self._previous)
        self.bind("<Down>", self._next)
        self.bind('<Control-n>', self._next)
        self.bind('<Control-p>', self._previous)

        self.bind("<Return>", self._update_entry_from_listbox)
        self.bind("<Escape>", lambda event: self.unpost_listbox())
        # self.bind("<FocusOut>", lambda event: self._update_entry_from_listbox(event, False))
        self.bind("<FocusOut>", lambda event: self.after(50, self._on_focus_out, event))
        parent = self.master
        while not isinstance(parent, (Toplevel, Tk)):
            parent = parent.master

        self.parent_window = parent

    def _on_focus_out(self, event):
        if self._listbox:
            focused = self._listbox.focus_get()
            if focused and self._listbox.winfo_name() in str(focused):
                return
        self.unpost_listbox()

    # def _on_focus_out_list(self, event):
    #     focused = self.focus_get()
    #     if focused and str(focused).endswith(self.winfo_name()):
    #         return
    #     self.unpost_listbox()

    # def _on_tab(self, event):
    #     self.post_listbox()
    #     return "break"

    def _on_change_entry_var(self, name, index, mode):

        entry_data = self._entry_var.get()

        if entry_data == '':
            self.unpost_listbox()
            # self.focus()
        else:
            values = self.autocomplete_function(entry_data)
            if values:
                if self._listbox is None:
                    self._build_listbox(values)
                else:
                    self._listbox.delete(0, END)

                    height = min(self._listbox_height, len(values))
                    self._listbox.configure(height=height)

                    for item in values:
                        self._listbox.insert(END, item)

            else:
                self.unpost_listbox()
                self.focus()

    def _build_listbox(self, values):
        listbox_frame = Frame(self.parent_window, padding=0)

        if self._listbox_width:
            width = self._listbox_width
        else:
            width = self.winfo_width()

        self._listbox = Listbox(listbox_frame, background="white", selectmode=SINGLE, activestyle="none",
                                exportselection=False)
        self._listbox.grid(row=0, column=0, sticky='nsew', padx=0, pady=0)

        self._listbox.bind("<ButtonRelease-1>", self._update_entry_from_listbox)
        self._listbox.bind("<Return>", self._update_entry_from_listbox)
        self._listbox.bind("<Escape>", lambda event: self.unpost_listbox())
        # self._listbox.bind("<FocusOut>", lambda event: self.after(50, self._on_focus_out_list, event))
        self._listbox.bind('<Control-n>', self._next)
        self._listbox.bind('<Control-p>', self._previous)

        if self._use_vscrollbar:
            vbar = Scrollbar(listbox_frame, orient=VERTICAL, command=self._listbox.yview)
            vbar.grid(row=0, column=1, sticky='ns')

            self._listbox.configure(yscrollcommand=lambda f, l: autoscroll(vbar, f, l))

        if self._use_hscrollbar:
            hbar = Scrollbar(listbox_frame, orient=HORIZONTAL, command=self._listbox.xview)
            hbar.grid(row=1, column=0, sticky='ew')

            self._listbox.configure(xscrollcommand=lambda f, l: autoscroll(hbar, f, l))

        listbox_frame.grid_columnconfigure(0, weight=1)
        listbox_frame.grid_rowconfigure(0, weight=1)

        listbox_frame.place(in_=self, x=0, y=self.winfo_height(), width=width)

        height = min(self._listbox_height, len(values))
        self._listbox.configure(height=height)

        for item in values:
            self._listbox.insert(END, item)

    def post_listbox(self):
        if self._listbox is not None: return

        entry_data = self._entry_var.get()
        if entry_data == '': return

        values = self.autocomplete_function(entry_data)
        if values:
            self._build_listbox(values)

    def unpost_listbox(self):
        if self._listbox is not None:
            self._listbox.master.destroy()
            self._listbox = None

    def get_value(self):
        return self._entry_var.get()

    def set_value(self, text, close_dialog=False):
        self._set_var(text)

        if close_dialog:
            self.unpost_listbox()

        self.icursor(END)
        self.xview_moveto(1.0)

    def _set_var(self, text):
        self._entry_var.trace_vdelete("w", self._trace_id)
        self._entry_var.set(text)
        self._trace_id = self._entry_var.trace('w', self._on_change_entry_var)

    def _update_entry_from_listbox(self, event, focus=True):
        if self._listbox is not None:
            current_selection = self._listbox.curselection()

            if current_selection:
                text = self._listbox.get(current_selection)
                self._set_var(text)

            self._listbox.master.destroy()
            self._listbox = None

            if focus:
                self.focus()
                self.icursor(END)
                self.xview_moveto(1.0)

        return "break"

    def _previous(self, event):
        if self._listbox is not None:
            current_selection = self._listbox.curselection()

            if len(current_selection) == 0:
                self._listbox.selection_set(0)
                self._listbox.activate(0)
            else:
                index = int(current_selection[0])
                self._listbox.selection_clear(index)

                if index == 0:
                    index = END
                else:
                    index -= 1

                self._listbox.see(index)
                self._listbox.selection_set(first=index)
                self._listbox.activate(index)

        return "break"

    def _next(self, event):
        if self._listbox is not None:

            current_selection = self._listbox.curselection()
            if len(current_selection) == 0:
                self._listbox.selection_set(0)
                self._listbox.activate(0)
            else:
                index = int(current_selection[0])
                self._listbox.selection_clear(index)

                if index == self._listbox.size() - 1:
                    index = 0
                else:
                    index += 1

                self._listbox.see(index)
                self._listbox.selection_set(index)
                self._listbox.activate(index)
        return "break"


if __name__ == '__main__':
    list_of_items = ["Cordell Cannata", "Lacey Naples", "Zachery Manigault", "Regan Brunt", "Mario Hilgefort",
                     "Austin Phong", "Moises Saum", "Willy Neill", "Rosendo Sokoloff", "Salley Christenberry",
                     "Toby Schneller", "Angel Buchwald", "Nestor Criger", "Arie Jozwiak", "Nita Montelongo",
                     "Clemencia Okane", "Alison Scaggs", "Von Petrella", "Glennie Gurley", "Jamar Callender",
                     "Titus Wenrich", "Chadwick Liedtke", "Sharlene Yochum", "Leonida Mutchler", "Duane Pickett",
                     "Morton Brackins", "Ervin Trundy", "Antony Orwig", "Audrea Yutzy", "Michal Hepp",
                     "Annelle Hoadley", "Hank Wyman", "Mika Fernandez", "Elisa Legendre", "Sade Nicolson", "Jessie Yi",
                     "Forrest Mooneyhan", "Alvin Widell", "Lizette Ruppe", "Marguerita Pilarski", "Merna Argento",
                     "Jess Daquila", "Breann Bevans", "Melvin Guidry", "Jacelyn Vanleer", "Jerome Riendeau",
                     "Iraida Nyquist", "Micah Glantz", "Dorene Waldrip", "Fidel Garey", "Vertie Deady",
                     "Rosalinda Odegaard", "Chong Hayner", "Candida Palazzolo", "Bennie Faison", "Nova Bunkley",
                     "Francis Buckwalter", "Georgianne Espinal", "Karleen Dockins", "Hertha Lucus", "Ike Alberty",
                     "Deangelo Revelle", "Juli Gallup", "Wendie Eisner", "Khalilah Travers", "Rex Outman",
                     "Anabel King", "Lorelei Tardiff", "Pablo Berkey", "Mariel Tutino", "Leigh Marciano", "Ok Nadeau",
                     "Zachary Antrim", "Chun Matthew", "Golden Keniston", "Anthony Johson", "Rossana Ahlstrom",
                     "Amado Schluter", "Delila Lovelady", "Josef Belle", "Leif Negrete", "Alec Doss", "Darryl Stryker",
                     "Michael Cagley", "Sabina Alejo", "Delana Mewborn", "Aurelio Crouch", "Ashlie Shulman",
                     "Danielle Conlan", "Randal Donnell", "Rheba Anzalone", "Lilian Truax", "Weston Quarterman",
                     "Britt Brunt", "Leonie Corbett", "Monika Gamet", "Ingeborg Bello", "Angelique Zhang",
                     "Santiago Thibeau", "Eliseo Helmuth"]

    root = Tk()
    root.geometry("300x200")
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)
    frm_form = Frame(root)
    frm_form.grid(row=0, column=0)
    lbl_names = Label(frm_form, text='Name:')
    lbl_names.grid(row=0, column=0, padx=5, pady=5)
    entry_name = Combobox_Autocomplete(frm_form, list_of_items=list_of_items, startswith_match=False)
    entry_name.grid(row=0, column=1, padx=5)

    entry_name.focus()

    lbl_names = Label(frm_form, text='Food:')
    lbl_names.grid(row=1, column=0, padx=5, pady=5)
    entry_food = Combobox_Autocomplete(frm_form, list_of_items=['Apples', 'Oranges', 'Avocados'], startswith_match=False)
    entry_food.grid(row=1, column=1, padx=5)

    root.mainloop()
#基于:http://code.activestate.com/recipes/580770-combobox-autocomplete/ 米格尔·马丁内斯·洛佩兹
#修改后可用于顶级窗口和一些其他调整
从tkinter进口*
从tkinter.ttk导入*
def自动滚动(sbar、第一个、最后一个):
“”“根据需要隐藏和显示滚动条。”“”
first,last=浮动(first),浮动(last)
如果first=1:
sbar.grid_remove()
其他:
sbar.grid()
sbar.set(第一个,最后一个)
类组合框\自动完成(条目、对象):
def uuu init uuuu(self、master、列表项=None、自动完成功能=None、列表框宽度=None、列表框高度=7、,
ignorecase\u match=True,startswith\u match=True,vscrollbar=True,hscrollbar=True,**kwargs):
如果hasattr(self,“自动完成函数”):
如果“自动完成”功能不是“无”:
raise VALUERROR(“Combobox\u Autocomplete子类实现了“Autocomplete\u函数”)
其他:
如果“自动完成”功能不是“无”:
self.autocomplete\u函数=autocomplete\u函数
其他:
如果项目列表为“无”:
raise VALUE ERROR(“如果未指导完成函数,则项目列表不能为‘无’”)
如果不忽略案例匹配:
如果开始时与_匹配:
def匹配_功能(输入_数据,项目):
返回项目。开始使用(输入数据)
其他:
def匹配_功能(输入_数据,项目):
返回条目_数据中的项目
self.autocomplete_function=lambda entry_data:[self.list_中的项目,如果
匹配\功能(输入\数据,项目)]
其他:
如果开始时与_匹配:
def匹配_功能(转义_条目_数据,项目):
如果重新匹配(转义输入数据、项目、重新忽略案例):
返回真值
其他:
返回错误
其他:
def匹配_功能(转义_条目_数据,项目):
如果重新搜索(转义输入数据、项目、重新忽略案例):
返回真值
其他:
返回错误
def自动完成功能(输入数据):
转义输入数据=转义(输入数据)
return[如果与函数(转义\u条目\u数据,项)匹配,则返回\u项的self.list\u中的项的项]
self.autocomplete\u函数=autocomplete\u函数
self.\u listbox\u height=int(listbox\u height)
self.\u列表框\u宽度=列表框\u宽度
self.list\u of_items=list\u of_items
自我。使用
自我。\使用\hscrollbar=hscrollbar
kwargs.setdefault(“背景”、“白色”)
如果kwargs中有“textvariable”:
self.\u entry\u var=kwargs[“textvariable”]
其他:
self.\u entry\u var=kwargs[“textvariable”]=StringVar()
条目.uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
self.\u trace\u id=self.\u entry\u var.trace('w',self.\u on\u change\u entry\u var)
self.\u listbox=无
#self.bind(“,self.\u在\u选项卡上)
self.bind(“,lambda事件:self.post\u listbox())
self.bind(“,self.\u上一个)
self.bind(“,self.\u next)
self.bind(“”,self.\u下一步)
self.bind(“”,self.\u上一个)
self.bind(“,self.\u update\u entry\u from\u listbox)
self.bind(“,lambda事件:self.unpost\u listbox())
#self.bind(“,lambda事件:self.\u从\u列表框更新\u条目(事件,False))
self.bind(“,lambda事件:self.after(50,self.\u on\u focus\u out,event))
parent=self.master
虽然不存在(父级,(顶级,Tk)):
parent=parent.master
self.parent\u window=parent
定义打开焦点输出(自身、事件):
如果是self.\u列表框:
focused=self.\u listbox.focus\u get()
如果已聚焦且在str(聚焦)中有self.\u listbox.winfo\u name():
返回
self.unpost_listbox()
#定义在焦点列表上(自身、事件):
#聚焦=自聚焦
#如果聚焦和str(聚焦).endswith(self.winfo_name()):
#返回
#self.unpost_listbox()
#“定义”选项卡上的定义(自身、事件):
#self.post_listbox()
#返回“中断”
定义、更改、条目、变量(自身、名称、索引、模式):
entry\u data=self.\u entry\u var.get()
如果条目_数据=='':
self.unpost_listbox()
#self.focus()
其他:
值=self.autocomplete函数(输入数据)
如果值为:
如果self.\u列表框为无:
self.\u build\u列表框(值)
其他:
self.\u listbox.delete(0,结束)
高度=最小值(自身列表框高度,长度(值))
self.\u listbox.configure(高度=高度)