User Tools

Site Tools

study:python-topic:20241112-007:index

How to return variables from Tkinter windows? (2024-11-12)

Local Backup

  • Say I have a function ask_for_number() which is called when a button is pressed. It creates a Toplevel window with a label that says 'Enter a number', an Entry widget, and a button that says 'Validate' which calls a function called validate_entry. validate_entry checks if a number is entered, using
  • try:
        int(entry.get())
    except TypeError:
        # handle the error
  • but because this is a different function the only way to access entry that I know of is to make it global first. I can't use 'return entry' and re-use the variable in validate_entry, because return doesn't work within tkinter mainloop (callbacks don't and can't return anything). What is the 'proper' way to do it without using globals?

Solution

  • Method 1: use an instance variable in the toplevel class. Requires that the popup is “modal” (disables the main window).
    • import tkinter as tk
      
      class Main(tk.Frame):
          def __init__(self, master=None, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the main frame\n")
              lbl.pack()
      
              btn = tk.Button(self, text='click me', command=self.open_popup)
              btn.pack()
      
              self.output = tk.Label(self)
              self.output.pack()
      
          def open_popup(self):
              print("runs before the popup")
              p = Popup(self)
              print("runs after the popup closes")
              self.output.config(text=f"Got data returned:\n {p.result!r}")
      
      class Popup(tk.Toplevel):
          """modal window requires a master"""
          def __init__(self, master, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the modal window popup\ntype something")
              lbl.pack()
      
              self.ent = tk.Entry(self)
              self.ent.insert(0, "Hello world")
              self.ent.pack()
      
              btn = tk.Button(self, text="OK", command=self.on_ok)
              btn.pack()
      
              # The following commands keep the popup on top.
              # Remove these if you want a program with 2 responding windows.
              # These commands must be at the end of __init__
              self.transient(master) # set to be on top of the main window
              self.grab_set() # hijack all commands from the master (clicks on the main window are ignored)
              master.wait_window(self) # pause anything on the main window until this one closes
      
          def on_ok(self):
              self.result = self.ent.get() # save the return value to an instance variable.
              self.destroy()
      
      def main():
          root = tk.Tk()
          window = Main(root)
          window.pack()
          root.mainloop()
      
      if __name__ == '__main__':
          main()
  • Method 2: Send a callback from the main code to the toplevel window:
    • import tkinter as tk
      
      class Main(tk.Frame):
          def __init__(self, master=None, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the main frame\n")
              lbl.pack()
      
              btn = tk.Button(self, text='click me', command=self.open_popup)
              btn.pack()
      
              self.output = tk.Label(self)
              self.output.pack()
      
          def open_popup(self):
              Popup(self, self.update_main) # send the method that should be called at the end
      
          def update_main(self, result):
              self.output.config(text=f"Got data returned:\n {result!r}")
      
      class Popup(tk.Toplevel):
          def __init__(self, master, callback, **kwargs):
              super().__init__(master, **kwargs)
              self.callback = callback
      
              lbl = tk.Label(self, text="this is the modal window popup\ntype something")
              lbl.pack()
      
              self.ent = tk.Entry(self)
              self.ent.insert(0, "Hello world")
              self.ent.pack()
      
              btn = tk.Button(self, text="OK", command=self.on_ok)
              btn.pack()
      
          def on_ok(self):
              self.callback(self.ent.get()) # send the back to the other class.
              self.destroy()
      
      def main():
          root = tk.Tk()
          window = Main(root)
          window.pack()
          root.mainloop()
      
      if __name__ == '__main__':
          main()
  • Method 3: pass along a tkinter variable and just have all places that need it linked. Immediate updates if used as a textvariable.
    • import tkinter as tk
      
      class Main(tk.Frame):
          def __init__(self, master=None, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the main frame\n")
              lbl.pack()
      
              btn = tk.Button(self, text='click me', command=self.open_popup)
              btn.pack()
      
              self.txtvar = tk.StringVar(value="Hello World")
              self.output = tk.Label(self, textvariable=self.txtvar)
              self.output.pack()
      
          def open_popup(self):
              Popup(self, self.txtvar)
      
      class Popup(tk.Toplevel):
          def __init__(self, master, txtvar, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the modal window popup\ntype something")
              lbl.pack()
      
              self.ent = tk.Entry(self, textvariable=txtvar) # could also use txtvar.set() in the ok method.
              self.ent.pack()
      
              btn = tk.Button(self, text="OK", command=self.destroy)
              btn.pack()
      
      def main():
          root = tk.Tk()
          window = Main(root)
          window.pack()
          root.mainloop()
      
      if __name__ == '__main__':
          main()
  • Method 4: use a tkinter Event to signal when things should happen. I used a global data structure here but you could also pass a mutable around instead.
    • import tkinter as tk
      
      data_structure = "" # any kind of data structure
      
      class Main(tk.Frame):
          def __init__(self, master=None, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the main frame\n")
              lbl.pack()
      
              btn = tk.Button(self, text='click me', command=self.open_popup)
              btn.pack()
      
              self.output = tk.Label(self)
              self.output.pack()
              master.bind("<<mcoombesEvent>>", self.update_main)
      
          def open_popup(self):
              Popup(self, self.update_main)
      
          def update_main(self, event=None):
              self.output.config(text=f"Got data returned:\n {data_structure!r}")
      
      class Popup(tk.Toplevel):
          def __init__(self, master, callback, **kwargs):
              super().__init__(master, **kwargs)
              self.callback = callback
      
              lbl = tk.Label(self, text="this is the modal window popup\ntype something")
              lbl.pack()
      
              self.ent = tk.Entry(self)
              self.ent.insert(0, "Hello world")
              self.ent.pack()
      
              btn = tk.Button(self, text="OK", command=self.on_ok)
              btn.pack()
      
          def on_ok(self):
              global data_structure
              data_structure = self.ent.get() # save the data.
              self.master.event_generate("<<mcoombesEvent>>") # trigger actions elsewhere
              self.destroy()
      
      def main():
          root = tk.Tk()
          window = Main(root)
          window.pack()
          root.mainloop()
      
      if __name__ == '__main__':
          main()
  • method 5: Similar to event based, use a loop to wait for new data. Allows new data to come from many places. Again could use global data structure, but for contrast will use mutable instance variable:
    • import tkinter as tk
      from queue import Queue
      
      class Main(tk.Frame):
          def __init__(self, master=None, **kwargs):
              super().__init__(master, **kwargs)
      
              self.data_structure = Queue()
      
              lbl = tk.Label(self, text="this is the main frame\n")
              lbl.pack()
      
              btn = tk.Button(self, text='click me', command=self.open_popup)
              btn.pack()
      
              self.output = tk.Label(self)
              self.output.pack()
      
              self.event_monitor() # start the loop to monitor for new data
      
          def open_popup(self):
              Popup(self)
      
          def event_monitor(self):
              if not self.data_structure.empty():
                  result = self.data_structure.get()
                  self.output.config(text=f"Got data returned:\n {result!r}")
              self.after(100, self.event_monitor)
      
      class Popup(tk.Toplevel):
          """modal window requires a master"""
          def __init__(self, master, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the modal window popup\ntype something")
              lbl.pack()
      
              self.ent = tk.Entry(self)
              self.ent.insert(0, "Hello world")
              self.ent.pack()
      
              btn = tk.Button(self, text="OK", command=self.on_ok)
              btn.pack()
      
          def on_ok(self):
              self.master.data_structure.put(self.ent.get()) # send the data to the queue
              self.destroy()
      
      def main():
          root = tk.Tk()
          window = Main(root)
          window.pack()
          root.mainloop()
      
      if __name__ == '__main__':
          main()
  • Method 6: trace a variable:
    • import tkinter as tk
      
      class Main(tk.Frame):
          def __init__(self, master=None, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the main frame\n")
              lbl.pack()
      
              btn = tk.Button(self, text='click me', command=self.open_popup)
              btn.pack()
      
              self.txtvar = tk.StringVar(value="Hello World")
              self.txtvar.trace_add("write", self.update_main)
              self.output = tk.Label(self)
              self.output.pack()
      
          def open_popup(self):
              print("runs before the popup")
              Popup(self, self.txtvar)
              print("runs after the popup closes")
      
          def update_main(self, *event):
              self.output.config(text=f"Got data returned:\n {self.txtvar.get()!r}")
      
      class Popup(tk.Toplevel):
          def __init__(self, master, txtvar, **kwargs):
              super().__init__(master, **kwargs)
      
              lbl = tk.Label(self, text="this is the modal window popup\ntype something")
              lbl.pack()
      
              self.ent = tk.Entry(self, textvariable=txtvar) # or use txtvar.set() from the ok button callback
              self.ent.pack()
      
              btn = tk.Button(self, text="OK", command=self.destroy)
              btn.pack()
      
      def main():
          root = tk.Tk()
          window = Main(root)
          window.pack()
          root.mainloop()
      
      if __name__ == '__main__':
          main()

Permalink study/python-topic/20241112-007/index.txt · Last modified: 2024/11/12 13:00 by jethro

oeffentlich