3

What I mean by this is can you make the program open the window more than once by putting the mainloop() function in a for loop or a while loop? It would probably look something like this:

for i in range(n):
  window.mainloop()

Or this if we are using a while loop:

i = 0
while i < n:
  window.mainloop()

When I try either of these methods, it just opens one window. Am I doing something wrong or is mainloop() a function that cannot be put in a loop? If that is the case, is there any other method?

MathGeek
  • 141
  • 9

2 Answers2

3

Calling mainloop more than once can't open new windows. mainloop only processes events, it doesn't create or recreate any windows.

If you need multiple windows, the way to do that is to create instances of Toplevel for the second and subsequent windows. You then call mainloop exactly once and all of the windows will be visible assuming they've been set up to be visible.

If you want to create multiple instances of the same window, the normal pattern is to create your app in a class that inherits from Frame. You can then create as many windows as you want, and in each one you can create an instance of that frame.

Here is an example of the technique:

import tkinter as tk


class App(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.count = 0
        self.button = tk.Button(self, text="Click me!", command=self.click)
        self.label = tk.Label(self, text="", width=20)

        self.label.pack(side="top", fill="both", expand=True)
        self.button.pack(side="bottom", padx=4, pady=4)

        self.refresh_clicks()

    def click(self):
        self.count += 1
        self.refresh_clicks()

    def refresh_clicks(self):
        self.label.configure(text=f"Clicks: {self.count}")

apps = []
n = 5
for i in range(n):
    window = tk.Tk() if i == 0 else tk.Toplevel()

    app = App(window)
    app.pack(fill="both", expand=True)
    apps.append(app)

tk.mainloop()

It's important to note that if you delete the very first window, all of the other windows will be deleted. If you don't want that to happen, you can create and then hide the root window so that the user can only see the other windows. You'll then need to add some code to kill the root window when there are no longer any child windows.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Could you please elaborate on *why* the normal pattern is to pile it into a class that inherits from `Frame`? Seems like a minimal example could just create `tk.Tk`, `tk.Toplevel`, `tk.Label`, and `tk.Button` instances (and you could use stuff like partial application or closures to make sure the click handler can access the mutable counter and the label). So do the features provided by `tk.Frame` later turn out to be very useful in typical `tkinter` GUI apps, and so on? – mtraceur Jun 16 '22 at 06:29
  • @BryanOakley How do you hide the root window? Sorry, I'm just a beginner in tkinter. I some how managed to understand the code in your answer. – MathGeek Jun 16 '22 at 14:08
  • @MathGeek: inheriting from `Frame` is convenient, because it both encapsulates your application and it can easily be placed in a window or a notebook tab or swapped in and out through whatever means. – Bryan Oakley Jun 16 '22 at 14:16
  • @MathGeek: you can use the `wm_withdraw` method to hide the root window. – Bryan Oakley Jun 16 '22 at 14:17
  • @BryanOakley Thank you. Also, I did not ask about ```Frame```, it was mtraceur who asked about it, so you might want to direct your comment towards him. – MathGeek Jun 16 '22 at 14:25
2

The normal way within tkinter is to run all windows within one "mainloop". You can also run a mainloop in a separate process, and each process can run a mainloop independently.


The other answer now has an example of the "within one mainloop" way - maybe later I'll edit in my own example here.


Here's a minimal example of how run "mainloop" in separate processes:

import multiprocessing
import tkinter

# Note, due to a quirk of multprocessing and pickle,
# you can't just copy-paste this in the interactive
# Python REPL, it has to go in a file.
def ui():
    tkinter.Tk().mainloop()

if __name__ == '__main__':
    multiprocessing.Process(target=ui).start()
    # The above line did not block, because mainloop
    # ran in another process. So now we can run more
    # mainloops in parallel if we want:
    multiprocessing.Process(target=ui).start()

I wouldn't do this unless each window is its own independent app that just happens to be launched from one parent process for some reason, or you have some other really good reason to isolate it.


As for why it doesn't work:

window.mainloop is a blocking/ongoing operation - it kinda hints at this by having "loop" in the name - inside that function is what's called an "event loop", and the code just sits in that loop waiting for and handling window events until the window is closed.

You can think of it almost like this:

def mainloop():
    while window_exists:
        ...
        # this is why it doesn't work
        # it loops forever in here

for i in range(n):
    mainloop()

For more detail, you can dive into the tkinter documentation where you'll find similar explanations:

Tcl/Tk applications are normally event-driven, meaning that after initialization, the interpreter runs an event loop [...] This is different from many GUI toolkits where the GUI runs in a completely separate thread from all application code including event handlers.

In principle, multiple threads or processes could run one event loop each, but in this case, tkinter assumes that Tcl (and thus Tk) was not compiled with thread-safety (ruling out threads).

mtraceur
  • 3,254
  • 24
  • 33
  • So are there other GUI toolikits that allow this sort of thing? If so could you give an example of one please? – MathGeek Jun 15 '22 at 17:37
  • @MathGeek I don't know which ones do or don't off the top of my head, and my opinionated software design tastes make me somewhat uninterested in the capability so I don't want to go digging to find out, sorry. But I can at least give you the names of some widely used GUI libraries, and you can check their documentation to figure out if they let you do that: Qt, GTK, Cocoa, WinUI. I recommend turning back from any temptation to do it that way though, and instead meditate on [structured concurrency](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/). – mtraceur Jun 16 '22 at 06:18