Search code examples
tkinterpython-3.7tkinter-entry

Winfo_y does not return correct value


I am trying to create a custom entry widget in tkinter. When this entry has focus it should display a listbox right under the entry widget with some options to the user. When it loses focus the listbox should delete by itself.

This is what I have:

class TestEntry(tk.Entry):
    def __init__(self, top):
        self.window = top

        tk.Entry.__init__(self, self.window)
        self.listshown = False

        self.bind('<FocusIn>', self.showlist)
        self.bind('<FocusOut>', self.hidelist)

    def showlist(self, event):

        if not self.listshown:
            x = self.winfo_x()
            y = self.winfo_y()
            h = self.winfo_height()
            w = self.winfo_width()
    
            # Print bottom left x and y of the entry
            print(x, y, h)

            self.listbox = tk.Listbox(self.master, highlightbackground='blue', highlightthickness=1)
            self.listshown = True
            self.listbox.place(x=x, y=y + h, width=w,anchor='nw')
            self.listbox.update()

            # Print top left x,y of listbox
            x = self.listbox.winfo_x()
            y = self.listbox.winfo_y()

            print(x, y)

    def hidelist(self, event):
        if self.listshown:
            self.listbox.destroy()
            self.listshown = False


if __name__ == "__main__":
    root = tk.Tk()
    root.geometry('500x500')

    frame = tk.LabelFrame(root, text='Frame')
    frame.pack(padx=30, pady=30, fill='both', expand=True)
    TestEntry(frame).pack(side='left', padx=20, pady=20)
    tk.Entry(frame).pack(side='left', padx=20, pady=20)


   

This is the output that I see:

22 218 19
24 254

As you can see in the image below, instead of appearing at the left bottom corner of the entry box, listbox appears further below. Why is this happening?

EDIT: This issue only happens when I put my widget in a label frame as shown in the code above. When I put it in a regular frame this does not happen.


Solution

  • You can avoid all of the math by using relative placement. When you use place, you can specify that the listbox is relative to the entry widget.

    For example:

    self.listbox.place(in_=self, relx=0, rely=1.0, bordermode="outside", relwidth=1.0, anchor='nw')
    
    • in_ specifies which widget to be relative to
    • relx is a floating point value representing the relative x position. 0 == left edge, 1.0 == right edge
    • rely is a floating point value represeenting the relative y position. A value of 1 means at the bottom of the widget
    • bordermode means to treat the coordinates relative to the outside border than inside the border
    • relwidth means to make the widget as wide as the entry widget.

    screenshot