Home > Back-end >  In the example code p = ProgressDialog will not appear unless root.withdraw() is removed. Is there a
In the example code p = ProgressDialog will not appear unless root.withdraw() is removed. Is there a

Time:12-13

The following code is from https://github.com/PacktPublishing/Modern-Python-Standard-Library-Cookbook/blob/master/Chapter13/gui_03.py.

Using Python 3.9.

With root.withdraw() The idea is to show a widget without the default tk window appearing. This works with other widgets. However, in this case, if left in p = ProgressDialog does not appear either.

I do not have enough experience to be confident that this warrants a bug report. Am I missing something? Is there a workaround?

import tkinter
from tkinter import simpledialog
from tkinter import ttk

from queue import Queue

class ProgressDialog(simpledialog.SimpleDialog):
    def __init__(self, master, text='', title=None, class_=None):
        super().__init__(master=master, text=text, title=title, class_=class_)
        self.default = None
        self.cancel = None

        self._queue = Queue()
        self._bar = ttk.Progressbar(self.root, orient="horizontal", 
                                    length=200, mode="determinate")
        self._bar.pack(expand=True, fill=tkinter.X, side=tkinter.BOTTOM)
        self.root.attributes("-topmost", True)
        self.root.after(200, self._update)

    def set_progress(self, value):
        self._queue.put(value)

    def _update(self):
        while self._queue.qsize():
            try:
                self._bar['value'] = self._queue.get(0)
            except Queue.Empty:
                pass
        self.root.after(200, self._update)




if __name__ == '__main__':
    root = tkinter.Tk()
    root.withdraw() # The idea is to show the widget without default tk window appearing.
    # However if left in p = ProgressDialog does not appear either.
    p = ProgressDialog(master=root, text='Downloading Something...',
                       title='Download')

    import threading
    def _do_progress():
        import time
        for i in range(1, 11):
            time.sleep(0.5)
            p.set_progress(i*10)
        p.done(0)
    t = threading.Thread(target=_do_progress)
    t.start()
    
    p.go()
    print('Download Completed!')

CodePudding user response:

It is because SimpleDialog makes itself a transient window of its parent, so it will be hidden if its parent is hidden.

One work around is to override SimpleDialog._set_transient():

import tkinter
from tkinter import simpledialog
from tkinter import ttk
from queue import Queue

class ProgressDialog(simpledialog.SimpleDialog):
    def __init__(self, master, text='', title=None, class_=None):
        super().__init__(master=master, text=text, title=title, class_=class_)
        self.default = None
        self.cancel = None
        self._queue = Queue()

    def _set_transient(self, master, relx=0.5, rely=0.3):
        self.root.withdraw()
        # add the progress bar
        self._bar = ttk.Progressbar(self.root, orient="horizontal",
                                    length=200, mode="determinate")
        self._bar.pack(expand=True, fill=tkinter.X, side=tkinter.BOTTOM)
        # put the dialog at desired position on screen based on relx and rely arguments
        master.update_idletasks()
        m_width, m_height = master.winfo_screenwidth(), master.winfo_screenheight()
        w_width, w_height = self.root.winfo_width(), self.root.winfo_height()
        x = int(m_width*relx) - w_width//2
        y = int(m_height*rely) - w_height//2
        self.root.geometry(f' {x} {y}')
        self.root.attributes("-topmost", True)
        self.root.deiconify()
        # start the update loop
        self.root.after(200, self._update)

    def set_progress(self, value):
        self._queue.put(value)

    def _update(self):
        while self._queue.qsize():
            try:
                self._bar['value'] = self._queue.get(0)
            except Queue.Empty:
                pass
        self.root.after(200, self._update)


if __name__ == '__main__':
    root = tkinter.Tk()
    root.withdraw() # The idea is to show the widget without default tk window appearing.
    p = ProgressDialog(master=root, text='Downloading Something...',
                       title='Download')

    import threading
    def _do_progress():
        import time
        for i in range(1, 11):
            time.sleep(0.5)
            p.set_progress(i*10)
        p.done(0)
    t = threading.Thread(target=_do_progress)
    t.start()

    p.go()
    print('Download Completed!')
  • Related