python - 如果在StringVar()上调用set(),则会调用绑定到tkinter小部件两次

时间:2017-09-24 19:46:33

标签: python data-binding tkinter binding listbox

我有一个条目,一个列表框(下拉列表)和另一个列表框。只要在条目中输入超过3个字符。查找完成列表并将其插入下拉列表并显示下拉列表。如果从下拉列表中选择了某个项目。它的值应该填入条目,条目应该再次获得焦点,光标应该到达条目的末尾。然后,当按下Enter键时,应将条目的值插入另一个列表框。

我在this实用程序的帮助下开发了一个代码,代码运行得非常好。除此之外,我意识到每当我从下拉列表中选择一个选项时,相应的方法被调用两次(我在控制台中从同一个东西得到两个打印件)。但是,如果我选择下拉列表的第一个选项,它会被调用一次(这是另一种情况下实际应该发生的事情),但焦点不会转到条目(这是一个问题)。

这是我的代码:

from tkinter import *


class Autocomplete(Frame, object):
    def __init__(self, width, height, entries, *args, **kwargs):
        super(Autocomplete, self).__init__(*args, **kwargs)
        self._entries = entries
        self.listbox_height = height
        self.entry_width = width
        self.text = StringVar()
        self.entry = Entry(
            self,
            textvariable=self.text,
            width=self.entry_width
        )
        self.frame = Frame(self)
        self.listbox = Listbox(
            self.frame,
            height=self.listbox_height,
            width=self.entry_width
        )
        self.dropdown = Listbox(
            self.frame,
            height=self.listbox_height,
            width=self.entry_width,
            background="#cfeff9"
        )

    def build(self):
        self.text.trace("w", lambda name, index, mode, text=self.text: self._update_autocomplete())
        self.entry.bind("<Return>", lambda event,: self._add_course())
        self.entry.focus_set()
        self.entry.pack()
        self.frame.pack()
        self.listbox.grid(column=0, row=0, sticky=N)
        self.dropdown.bind("<<ListboxSelect>>", self._select_entry)
        self.dropdown.grid(column=0, row=0, sticky=N)
        self.dropdown.grid_forget()
        return self

    def _update_autocomplete(self):
        self.dropdown["height"] = self.listbox_height
        self.dropdown.delete(0, END)
        text = self.text.get()
        if len(text) < 3:
            self.dropdown.grid_forget()
            return
        else:
            for entry in self._entries:
                if text.lower() in entry.strip().lower():
                    self.dropdown.insert(END, entry)
        listbox_size = self.dropdown.size()
        if not listbox_size:
            self.dropdown.insert(END, "No results found for '{}'")
            self.dropdown["height"] = 1
        else:
            if listbox_size <= self.dropdown["height"]:
                self.dropdown["height"] = listbox_size
        self.dropdown.grid(column=0, row=0, sticky=N)

    def _select_entry(self, event):
        widget = event.widget
        value = widget.get(int(widget.curselection()[0]))
        print(value)
        self.text.set(value)
        self.entry.focus_set()
        self.entry.icursor(END)

    def _add_course(self):
        self.listbox.insert(END, self.text.get())

那我在这里错过了什么?

顺便说一下,对代码的任何一般改进也将非常受欢迎。

以下是我的称呼方式:

from tkinter import *
from autocomplete import Autocomplete
from main import *

courses = load_courses_from_file("courses.txt")
root = Tk()
autocomplete_frame = Autocomplete(
    60,
    10,
    list(set(course.name + ", " + course.instructor for course in courses))
).build().pack()
mainloop()

1 个答案:

答案 0 :(得分:1)

单击项目时,列表框的选择会更改 - 这是列表框的默认行为。这会导致条目窗口小部件值发生更改,从而触发对_update_autocomplete的调用。该函数删除列表框中的所有内容,导致选择再次更改。