zvisofer
zvisofer

Reputation: 1368

Python Tkinter: different behavior when opening the same window from another window

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

Answers (1)

Bryan Oakley
Bryan Oakley

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

Related Questions