Allan Lago
Allan Lago

Reputation: 309

How to check if widget is descendant of another widget with tkinter

I am creating a pop-up message that disappears if the user clicks outside of the pop-up's frame. To check if the user clicks outside the frame, the code looks somewhat like this:

import tkinter as tk

def build_popup(self, root):
  popup_frame = tk.Frame(root)
  popup_frame.grid(row=0, column=0)

  # binding to check if click is outside the frame
  self.popup_frame_funcid = root.bind_all('<Button-1>', self.delete_popup)

  my_canvas = tk.Canvas(popup_frame, width=200, height=200)
  my_canvas.grid(row=0, column=0)

def delete_popup(self, root, event):
  # if location clicked is not that of a child of the frame destroy popup
  if root.winfo_containing(event.x_root, event.y_root) not in popup_frame.winfo_children():
    popup_frame.destroy()
    root.unbind('<Button-1>', self.popupframe_funcid)

I arrive at a problem however when a widget is added to my_canvas, for instance an entry, and its parent is declared as my_canvas. When I click the added widget, popup_frame.winfo_children() (rather reasonably) does not identify the added widget as a child of popup_frame and destroys the frame.

Is there a function within tkinter that I can use to check if a widget is a descendant of another widget or am I forced to manually keep track of each widget I add to popup_frame?

If there is a simpler/alternative way to achieve the same result, I would also be very glad to hear it.

Upvotes: 0

Views: 1513

Answers (3)

Allan Lago
Allan Lago

Reputation: 309

I messed around for a bit and found an alternative solution to the ones already mentioned.

str(my_widget) returns the string path of my_widget

Hence, you can check if, for instance, my_canvas is a descendant of popup_frame by simply checking if my_canvas's path starts with popup_frame's path.

In python this is simply:

str(my_canvas).startswith(str(popup_frame))

Upvotes: 1

Mike - SMT
Mike - SMT

Reputation: 15226

I use winfo_children() and winfo_parent() to ID children and parent widgets/containers. Note that the single . means root window.

import tkinter as tk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        frame1 = tk.Frame(self)
        btn1 = tk.Button(self)
        btn2 = tk.Button(self)
        btn3 = tk.Button(frame1)

        print('Root children widget are: {}'.format(self.winfo_children()))
        print('frame1 children widget is: {}'.format(frame1.winfo_children()))
        print('Button 1 parent is: {}'.format(btn1.winfo_parent()))
        print('Button 2 parent is: {}'.format(btn2.winfo_parent()))
        print('Button 3 parent is: {}'.format(btn3.winfo_parent()))


if __name__ == '__main__':
    App().mainloop()

Results:

enter image description here

Upvotes: 0

Bryan Oakley
Bryan Oakley

Reputation: 385970

You can use winfo_parent to get the parent of a widget. You can then call that on the parent, and the parent's parent, and so on, to get the ancestry of a widget. winfo_parent returns a string rather than the parent object, but tkinter has a way to convert the name to the widget.

For example, to get the parent widget of a widget named w, you could do this:

parent = w.nametowidget(w.winfo_parent())

With that, you can work your way up the hierarchy of widgets, stopping when you get to the root window.

Upvotes: 1

Related Questions