4

I need my program to spawn multiple message boxes. They have to be spawned in cascade at once. (think of it as mimicry of malicious activity)

I tried do this using Tkinter:

import Tkinter
import tkMessageBox

for i in range(0,5):
    tkMessageBox.showerror("", "oops")

but it seems program waits for user interaction with each message before showing next which is not quite what I need and optional there is an empty form at top left corner. any idea to get rid of it?

  • I think the feature you wan't isn't possible w/o editing Tcl source codes. – Nae Feb 28 '18 at 15:36
  • You can take a look [here](https://stackoverflow.com/questions/21085618/how-can-i-spawn-multiple-tkmessagebox-showerror-at-the-same-time). – Vasilis G. Feb 28 '18 at 15:36
  • Soln for the optional: Add `root = Tkinter.Tk(); root.withdraw();` to hide the Toplevel that comes along with Tcl interpreter. – Nae Feb 28 '18 at 15:37

2 Answers2

0

The solutions might be to use TopLevel() here. This will allow all windows to pop up and you will be able to set a customer messagebox style as well.

Here is a simple example that will open all the windows at once while also hiding the root window. The below will stack all the windows on top of each other and you can move them. You can also provide some tracked variables to open each windows in a different location if you like.

#For python 3 imports:
import tkinter as tk
from tkinter import ttk
# for python 2 imports:
# import Tkinter as tk
# import ttk

root = tk.Tk()
root.withdraw()

for i in range(0,5):
    x = tk.Toplevel(root)
    x.title("Error Box!")
    x.geometry("150x75+0+0")
    x.resizable(False, False)
    ttk.Label(x, text = "oops").pack()
    ttk.Button(x, text = " OK ", command = x.destroy).pack(side=tk.BOTTOM)

root.mainloop()

In response to your comment on using a counter see below code:

#For python 3 imports:
import tkinter as tk
from tkinter import ttk
# for python 2 imports:
# import Tkinter as tk
# import ttk

root = tk.Tk()
root.withdraw()
counter = 0

def close_window(top_window):
    global counter
    top_window.destroy()
    counter -= 1
    if counter == 0:
        print("destroying root window")
        root.destroy()

for i in range(0,5):
    counter += 1
    x = tk.Toplevel(root)
    x.title("Error Box!")
    x.geometry("150x75+0+0")
    x.resizable(False, False)
    ttk.Label(x, text="oops").pack()
    ttk.Button(x, text=" OK ", command=lambda tw=x: close_window(tw)).pack(side=tk.BOTTOM)
    # this protocol() method is used to re-rout the window close event to a customer function.
    # this will allow us to keep our counter and close windows as needed.
    x.protocol("WM_DELETE_WINDOW", lambda tw=x: close_window(tw))

root.mainloop()

Better yet here is an example that places the items inside of a list so we do not need a counter.

#For python 3 imports:
import tkinter as tk
from tkinter import ttk
# for python 2 imports:
# import Tkinter as tk
# import ttk

root = tk.Tk()
root.withdraw()

list_of_windows = []

def close_window(tw):
    i = list_of_windows.index(tw)
    list_of_windows[i].destroy()
    del list_of_windows[i]
    if len(list_of_windows) == 0:
        root.destroy()
        print("root destroyed!")

for i in range(0,5):
    x = tk.Toplevel(root)
    x.title("Error Box!")
    x.geometry("150x75+0+0")
    x.resizable(False, False)
    ttk.Label(x, text="oops").pack()
    ttk.Button(x, text=" OK ", command=lambda tw=x: close_window(tw)).pack(side=tk.BOTTOM)
    x.protocol("WM_DELETE_WINDOW", lambda tw=x: close_window(tw))
    list_of_windows.append(x)

root.mainloop()
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • 1
    Thanks, Mike! Seems like more refined approach. The only issue in this specific code, is the root doesn't get destroyed even after I close the last message box. Do I have to attach some sort of a counter or is there a better solution? – random-name-239487 Mar 14 '18 at 10:19
-1

My conclusion: Using tk message boxes wasn't best approach to the task, because message boxes are modal, and there is no direct way to change that. So instead I've just got a form shaped like a message box and spawned them with desirable quantity. Ended up with following code:

from Tkinter import *

di = {}

for i in range(5):
    di[i] = Tk()
    offset = 300 + i*10
    di[i].geometry('150x50+'+str(offset)+'+'+str(offset))
    di[i].title('')
    di[i].resizable(False, False)
    la = Label(di[i],text = 'oops').pack()
    button = Button(di[i], text = 'OK', command=di[i].destroy).pack()

di[0].mainloop()    

And it serves my needs well. Thanks to Nae and Vasilis G. for their kind responses leading me to a working code.

  • Do not call `Tk()` more than once in a tkinter app. Instead if you need multiple windows use `Toplevel()` instead. See my answer for an example. – Mike - SMT Mar 07 '18 at 22:31
  • Is there any consequences with multiple Tk() calls? Because it works on mac just fine on first glance. – random-name-239487 Mar 14 '18 at 09:45
  • A tkinter application needs to contain one `Tk()` instance. For multiple windows you should be using `Toplevel()` instead as the `Toplevel()` method will open new windows for the given instance of `Tk()`. [This link](https://stackoverflow.com/questions/48045401/why-are-multiple-instances-of-tk-discouraged) goes into greater detail as to why you should not use multiple `Tk()` instances. In short its because each new `Tk()` instance cannot talk to the other. – Mike - SMT Mar 14 '18 at 12:28