Reputation: 1368
I used Tkinter
to create a window with a custom auto-complete entry.
When running directly the window with the auto-complete entry (with the "direct" command-line argument), the entry works fine and when typing underscore the entry suggests the 4 hard-coded string.
When running this window after double-click event from another window (with the "indirect" command-line argument), the auto-complete entry doesn't work. UPDATE: more precisely, the autocomplete shows the options on the first window (instead of the window with the autocomplete entry).
What is causing this inconsistency? How can I make it work in both cases?
See MWE attached:
from Tkinter import *
from ttk import Frame, Label, Style
class AutocompleteEntry(Entry):
def __init__(self, contacts, mainComposeMailWindow, *args, **kwargs):
Entry.__init__(self, *args, **kwargs)
self.contacts = contacts
self.mainComposeMailWindow = mainComposeMailWindow
self.var = self["textvariable"]
if self.var == '':
self.var = self["textvariable"] = StringVar()
self.var.trace('w', self.changed)
self.bind("<Right>", self.selection)
self.bind("<Up>", self.up)
self.bind("<Down>", self.down)
self.lb_up = False
def changed(self, name, index, mode):
words = self.comparison()
if words:
if not self.lb_up:
self.lb = Listbox()
self.lb.bind("<Double-Button-1>", self.selection)
self.lb.bind("<Right>", self.selection)
self.lb.place(x=self.winfo_x(), y=self.winfo_y()+self.winfo_height())
self.lb_up = True
self.lb.delete(0, END)
for w in words:
self.lb.insert(END,w)
else:
if self.lb_up:
self.lb.destroy()
self.lb_up = False
def selection(self, event):
if self.lb_up:
self.var.set(self.lb.get(ACTIVE))
self.lb.destroy()
self.lb_up = False
self.icursor(END)
def up(self, event):
if self.lb_up:
if self.lb.curselection() == ():
index = '0'
else:
index = self.lb.curselection()[0]
if index != '0':
self.lb.selection_clear(first=index)
index = str(int(index)-1)
self.lb.selection_set(first=index)
self.lb.activate(index)
def down(self, event):
if self.lb_up:
if self.lb.curselection() == ():
index = '0'
else:
index = self.lb.curselection()[0]
if index != END:
self.lb.selection_clear(first=index)
index = str(int(index)+1)
self.lb.selection_set(first=index)
self.lb.activate(index)
def comparison(self):
return [w for w in self.contacts if w.lower().startswith(self.var.get().lower())]
def get_content(self):
return self.var.get()
class AutoCompleteWindow:
def __init__(self):
autocomplete_choices = ['_This', '_order', '_is', '_important']
self.root = Tk()
self.root.minsize(300,300)
Label(self.root, text="Enter text:").grid(row=0)
self.autocomplete_entry = AutocompleteEntry(autocomplete_choices, self, self.root, bd = 2, width=50)
self.autocomplete_entry.grid(row=0, column=1)
self.root.mainloop()
def on_open_window(event):
AutoCompleteWindow()
def makeWindow ():
global select
win = Tk()
frame3 = Frame(win)
frame3.pack()
scroll = Scrollbar(frame3, orient=VERTICAL)
select = Listbox(frame3, yscrollcommand=scroll.set, height=17, width=100)
select.bind("<Double-Button-1>" , on_open_window)
scroll.config (command=select.yview)
scroll.pack(side=RIGHT, fill=Y)
select.pack(side=LEFT, fill=BOTH, expand=1)
return win
def setSelect () :
scrollbar_choices = ["first", "second", "third"]
select.delete(0,END)
for a_choice in scrollbar_choices:
select.insert(END, a_choice)
def intro_window():
win = makeWindow()
setSelect ()
win.mainloop()
if __name__ == "__main__":
if sys.argv[1] == "indirect":
intro_window()
elif sys.argv[1] == "direct":
AutoCompleteWindow()
Upvotes: 0
Views: 440
Reputation: 385910
The problem is that you are creating more than one root window and running more than one event loop (though, only one is running at a time). Tkinter is designed to be run with exactly one instance of Tk
, with mainloop()
called exactly once. If you need additional windows you should create instances of Toplevel
.
Another part of the problem is that you don't give the listbox an explicit parent, so it will always appear in the root window. You need to give the listbox an explicit parent. Specifically, it should be the same parent as the entry widget.
Assuming that the first element of *args
is the parent (which is a bad assumption, but seems to hold up in this very specific case), a really quick fix is to do this:
class AutocompleteEntry(Entry):
def __init__(self, contacts, mainComposeMailWindow, *args, **kwargs):
self.parent = args[0]
...
def changed(...):
...
self.lb = Listbox(self.parent)
A better (read: more clear) fix would be to explicitly declare parent
as a keyword argument to __init__
, so you don't rely on a specific ordering of the arguments.
Upvotes: 1