1

I am trying to create two kinds of popups using tinter with Python3. First is a simple wait till ok button pressed message. And another which runs a process in background and shows the popup till the process completes. I have the following code for the same:

from tkinter import *
import time

def open_window():
    popup = Toplevel()
    ok_button = Button(popup, text="OK", command=popup.destroy)
    ok_button.pack()
    popup.mainloop()


def open_event_window(message, on_event):
    NORM_FONT = ("Helvetica", 15)
    popup = Toplevel()
    label = Label(popup, text=message, font=NORM_FONT)
    label.pack(side="top", fill="x", pady=10)
    popup.after(1, lambda: popup.focus_force())
    popup.wm_attributes("-topmost", 1)
    popup.focus_force()
    popup.update()

    try:
        on_event()
    except:
        raise
    finally:
        try:
            popup.destroy()
        except:
            pass


def func():
    print(">>> ", 1)
    time.sleep(5)
    print(">>> ", 2)

root = Tk()
root.withdraw()

open_window()
time.sleep(5)
open_event_window("here", func)
print("-111111")
time.sleep(5)

With the above code the ok prompt works but the callback prompt doesn't show up. If I try to use Tk() directly instead of TopLevel() both the prompts work but then the first prompt only closes when the second prompt opens which is not what is desired. For the given code the flow I want is: open ok prompt and close on ok pressed -> sleep 5 sec -> open callback prompt and run for 5 seconds and close -> sleep 5 seconds -> exit.

Any guidance will be greatly appreciated. Thanks.

Note: the code with Tk() call in every function instead of TopLevel works properly with Python2 but I am trying to use Python3 where this happens.

Satya Prakash
  • 35
  • 1
  • 6
  • Whether you use `Tk()` or `Toplevel()` calls should not be affected by whether you using Python 2 or 3. – martineau Jul 21 '21 at 18:40
  • @martineau Yes, sorry I meant that the code with Tk call works with Python2 I have not checked this TopLevel code in Python2. But I don't know why neither is working in Python3. – Satya Prakash Jul 21 '21 at 18:43
  • It doesn't work in Python 3 but I don't think it has anything to do with what the fact that `Toplevel` is being used. – martineau Jul 21 '21 at 18:46
  • Remove `popup.mainloop()` and see if that helps – Henry Jul 21 '21 at 18:53
  • @Henry Without mainloop call the first popup doesn't show up when called, it shows up along with the second one and then closes when the program exits – Satya Prakash Jul 21 '21 at 18:59
  • Oh I see, I misunderstood you question a bit, sorry. – Henry Jul 21 '21 at 19:00

2 Answers2

2

I think this does what you say you wanted. There's only one call to mainloop() and the open_event_window() function now destroys root instead of just popup so mainloop() will return and the lines follow that execute. I added a switch_windows() function to transfer control between the two popups after a delay.

from tkinter import *
import time

def open_window():
    popup = Toplevel()
    ok_button = Button(popup, text="OK", command=lambda: switch_windows(popup))
    ok_button.pack()

def switch_windows(_from):
    _from.after(3000, open_event_window, "here", func)
    _from.withdraw()

def open_event_window(message, on_event):
    NORM_FONT = ("Helvetica", 15)
    popup = Toplevel()

    label = Label(popup, text=message, font=NORM_FONT)
    label.pack(side="top", fill="x", pady=10)

    popup.after(1, lambda: popup.focus_force())
    popup.wm_attributes("-topmost", 1)
    popup.focus_force()
    popup.update()

    try:
        on_event()
    except:
        raise
    finally:
        try:
            root.destroy()
        except:
            pass

def func():
    print(">>> ", 1)
    time.sleep(5)
    print(">>> ", 2)

root = Tk()
root.withdraw()
open_window()
root.mainloop()

print("-111111")
time.sleep(5)
martineau
  • 119,623
  • 25
  • 170
  • 301
  • 1
    @SatyaL You're welcome. Note that this works in both Python 2 & 3 if you change the Tkinter module's `import` statement — although the output from the `print`s is a little different. – martineau Jul 21 '21 at 20:59
2

You can't use time.sleep with tkinter as it blocks the GUI process. Use root.after(delay_in_milliseconds, callback) instead.

def close_window(popup):
    popup.destroy()
    root.after(5000, lambda:open_event_window("here", func)) #Wait 5 seconds and open next window

def open_window():
    popup = Toplevel(root)
    ok_button = Button(popup, text="OK", command=lambda:close_window(popup))
    ok_button.pack()
    popup.mainloop()


def open_event_window(message, on_event):
    NORM_FONT = ("Helvetica", 15)
    popup = Toplevel(root)
    label = Label(popup, text=message, font=NORM_FONT)
    label.pack(side="top", fill="x", pady=10)
    popup.after(1, lambda: popup.focus_force())
    popup.wm_attributes("-topmost", 1)
    popup.focus_force()
    popup.update()
    try:
        on_event()
    except:
        raise
    finally:
        popup.destroy()
    root.after(5000, root.destroy) #Wait 5 seconds before exiting


def func():
    print(">>> ", 1)
    time.sleep(5)
    print(">>> ", 2)

root = Tk()
root.withdraw()
open_window()
root.mainloop()

This now runs as described in your question.

Henry
  • 3,472
  • 2
  • 12
  • 36
  • 1
    Glad I could help :) – Henry Jul 21 '21 at 20:41
  • thanks! btw what should be done if we want to show popups multiple times in our application but we don't want to stop execution due to mainloop? It seems like we can't have multiple mainloops but that stops the main application from progressing. Do we need to run the application using a different thread? – Satya Prakash Jul 21 '21 at 22:45
  • Yes you could make use of threading. [This answer](https://stackoverflow.com/a/16747734) might help. – Henry Jul 22 '21 at 12:04