WxPython App/Frame Goes to Background after ProgressDialog is closed

I have a wx application & use a ProgressDialog to inform the user when some tasks are running.
when the task is finished it goes back to the main application window.
This worked fine in Python2.7 (wx 3.0.2.0) under windows 10.
I am now using Python 3.7.9 & wxPython 4.1.1 and I have a strange behaviour I do not understand.

When the progress dialog is closed, the main application window seems to drop 1 level in the background in windows (i.e. underneath Folders or applications which were open at the same time.). E.g. If I run the application from PyCharm, once the ProgressDialog is closed, the wx window goes behind PyCharm.
The same if I run from the command line, it will go behind any windows it was previously above.
To restore it to the foreground, I have to select it from the windows taskbar again.

How do I keep the wx application window in the same hierarchical position as it was before?

With the following code I see the same behaviour but I do not understand what I am doing wrong? (or what is different to python27).
Python 27 is OK but Python 37 & 39 both have the same issue.

import wx
import time

class Application(wx.Frame):
    def __init__(self, parent, title):   
        wx.Frame.__init__(self, parent, title=title)
        self.SetMinSize(wx.Size(1050, 580))
        self.Show()

        self.progress_dlg = wx.ProgressDialog("Update", "Updating SVN Folders...", maximum=100)
        # Update SVN
        for ctr in range (5):
            self.progress_dlg.Update(ctr+1, "SVN Updating:...")
            time.sleep(1)

        self.progress_dlg.Update(100)


app = wx.App(False)
frame = Application(None, "SVN Update")
app.MainLoop()

I found a temporary solution using self.Raise() but again this has some strange behaviour:
e.g.
The Raise() has to be immediately after the Update(MaxValue) which closes the progressDialog.
If there is another task in between, the main application stay hidden behind other windows.

# This works
self.progress_dlg.Update(100)
self.Raise()

# This does not
self.progress_dlg.Update(100)
time.sleep(3)
self.Raise()

does anyone have a similar experience on Windows10 & wxPython 4.1.1 ?
Thanks in advance for any help,

GabboCH


You can see the behaviour in the attached ScreenToGif.
when the dialog is closed, the application dissapears & has to be restored to the foreground with the windows tab manager.

Hi Gabbo,

You should call self.progress_dlg.Destroy().
The default style PD_AUTO_HIDE hides the dialog but doesn’t destroy it automatically.

See also: wx.ProgressDialog — wxPython Phoenix 4.1.1 documentation

Normaly, it is not necessary to keep the instance. I prefer the following style:

        with wx.ProgressDialog(
                "Update",
                "Updating SVN Folders...",
                style=wx.PD_APP_MODAL|wx.PD_AUTO_HIDE, #<-- modify as you like
                maximum=100) as dlg:
            # Update SVN
            for ctr in range (5):
                dlg.Update(ctr+1, "SVN Updating:...")
                time.sleep(1)
            dlg.Update(100)

Thanks for the reply Komoto48g,
I’ve tried adding Destroy() but I’m not sure it actually does anything to ProgressDialog (this is why I added the “Update(max)” in my code.

        progress_dlg2 = wx.ProgressDialog("Update", "Updating SVN Folders...")
        progress_dlg2.Pulse()
        time.sleep(4)
        progress_dlg2.Destroy()
        print ("destroyed")
        time.sleep(5)
        print ("App init finished")

In the ScreenToGif below you can see that the progress dialog is not closed by the Destroy() but instead automatically when the end of the init is reached.
The application still goes to the background when it is closed.

I tested your code, and if you give wx.ProgressDialog option parent=self, “going to the background” won’t occur. However, you will find that the progress dialog doesn’t work. That’s because the main-thread is too busy (while sleep(4)) to update the child. EDIT My misunderstanding. Pulse makes just one pulse :slight_smile: wx.ProgressDialog — wxPython Phoenix 4.1.1 documentation

So, the workaround could be

        progress_dlg2 = wx.ProgressDialog("Update", "Updating SVN Folders...",
                                          parent=self)
        for ctr in range (100):
            progress_dlg2.Pulse()
            time.sleep(0.1)
            # wx.GetApp().Yield()
        progress_dlg2.Destroy()

Note that Yield allows the gui to update. Yield seems to be unnecessary in this case. :thinking: → :slight_smile:

what about style=0

Hello Komoto48g,
When you try my code, do you see the same behaviour where the main windows goes to the background when ProgressDialog is closed?

I already have a simple solution, to call self.Raise() directly after the ProgressDialog.Update(Max).

However, the way that ProgressDialog behaves with Python3 & wx4.1.1 seems like a bug if others have the same problem?
Is there a bug-tracker for wx I can check for a similar issue?

Whilst I can workaround my problem, as a relative newbie to Python I would like to understand if I have done something wrong in the basic framework of my app.

This is my working code:

import wx
import time

class Application(wx.Frame):
    def __init__(self, parent, title):   
        wx.Frame.__init__(self, parent, title=title)
        self.SetMinSize(wx.Size(1050, 580))
        self.Show()

        self.progress_dlg = wx.ProgressDialog("Update", "Updating SVN Folders...", maximum=100)
        # Update SVN
        for ctr in range (5):
            self.progress_dlg.Update(ctr+1, "SVN Updating:...")
            time.sleep(1)

        self.progress_dlg.Update(100)
        self.Raise()


app = wx.App(False)
frame = Application(None, "SVN Update")
app.MainLoop()

Thanks da-dada, with style=0 the behavior of the ProgressDialog window seems normal.
Everything stays at the same level in the visual hierarchy.

This is not really what I wanted but when I tried to enable the auto-hide with style=wx.PD_AUTO_HIDE then it works as expected with having to Raise() the main window again…

Therefore, the problem seems to come from the wx.PD_APP_MODAL. APP_MODAL &
AUTO_HIDE are enabled by default if I understand the documentation correctly.

I dont really understand what APP_MODAL does but changing the style seems like a Pythonic solution to me…!

Thanks a lot for everyones contribution…!

What is wrong with using the parent argument? This seems to solve the issue.

@Gabbo, your problem has been solved, so just a few comments.

Yes, when I ran your program, the main frame was hidden behind the command prompt (but no other windows).

It seems to be a glitch as wx.ProgressDialog shouldn’t behave that way with the default arguments.

There is an issue tracker here Issues · wxWidgets/Phoenix · GitHub, and I think this topic is valuable to report. Do you want to make “an issue”?

This flag seems meaningless to me because the progress dialog is always in modal mode.
Does anyone know why this flag exists? :thinking:

That flag determines if the dialog is modal to all windows in the application, or just the parent window.

BTW, I suspect that the original issue could be related to the fact that the MainLoop is not yet running when the progress dialog is shown. So some things may not be fully initialized yet, like what is the ‘top’ window that should be activated when the dialog is closed.

using style=~wx.PD_APP_MODAL the dialog is still ‘modal’ in the wx Dialog sense, but it hides at end of progress (no closing needed & , strangely, it is not destroyed)

@Robin: I’m seeing the same behaviour in the wxWidgets Dialogs sample, when I use parent=NULL.
With parent=this things are working fine.

I would guess that this is either some Windows specific behaviour or a minor issue in wxGenericProgressDialog::DisableOtherWindows / ReenableOtherWindows.

I missed the edit from komoto48g.
Actually with parent=self the ProgressDialog works exactly as intended (the same as in Python27).
Without wx.PD_APP_MODAL there is the behaviour that clicking back to the main window will allow the ProgressDialog to drop behind it (which is not what I want).
I want the ProgressDialog to block the main app while some tasks are performed.

With parent=self it always stays in the foreground & it is closed automatically without changing the position of the main window.

I also have this behaviour inside my larger application which has several panels, menubar & statusbar inside . I created this sample code to try to debug by problem but saw that the bahviour was the same.

Thank you, Robin. I understand what the flag PD_APP_MODAL is for.
I made a small code to test it and found another problem indicated by (*2) as below.

parent=None parent=self
style=PD_APP_MODAL A (*1) A
style=PD_AUTO_HIDE A (*2) B

A. modal to all windows
B. modal to the parent window
(*1) a bug: “going to background” that OP first pointed out.
(*2) a bug: “imperfect modal mode”, where it will freeze keyboard input if you move focus to another window while progressing.

So, I came to think that leaving default parent=None is not a good idea.

Code Example (click to expand)
import wx
import time

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        txt = wx.TextCtrl(self)
        btn = wx.Button(self, label="Test")
        btn.Bind(wx.EVT_BUTTON, lambda v: self.test())
        
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.AddMany([
            (btn, 0, wx.EXPAND | wx.ALL, 0),
            (txt, 1, wx.EXPAND | wx.ALL, 0),
        ])
        self.SetSizer(sizer)
        self.Show()

    def test(self):
        with wx.ProgressDialog(
                "Update",
                "Updating... {}".format(self.Title),
                ## style=wx.PD_APP_MODAL|wx.PD_AUTO_HIDE, #=> (default) go to background
                style=wx.PD_AUTO_HIDE,
                #parent=self,
                maximum=100) as dlg:
            for i in range (100):
                dlg.Update(i+1)
                time.sleep(0.05)
                #wx.GetApp().Yield() # (*) without this, it will freeze keyboard input...

if __name__ == "__main__":
    app = wx.App()
    frm = Frame(None, title="frame")
    frm2 = Frame(None, title="frame2")
    app.MainLoop()

the context manager is somewhat finished when the PD is called in a modal dialog: in the following example after the PD has ended the frame may be closed without closing the dialog first :joy:

import time
import wx

class Gui(wx.Frame):

    def __init__(self, parent):
        super().__init__(parent, title='Dialog in Dialog test')

        btn = wx.Button(self, label='left click me, please')
        btn.Bind(wx.EVT_BUTTON, self.test)

        self.Centre()
        self.Show()

    def test(self, _):
        with  Test(self, 'lalala') as dlg:
            dlg.ShowModal()

class Test(wx.TextEntryDialog):

    def __init__(self, parent, msg):
        super().__init__(parent, msg)

        with wx.ProgressDialog(
            "Update", "Updating SVN Folders...", maximum=100,
                                                                        parent=self) as dlg:
            dlg.Bind(wx.EVT_WINDOW_DESTROY,
                                        lambda _: print('PD detroyed'))
            # Update SVN
            for ctr in range (5):
                dlg.Update(ctr+1, "SVN Updating:...")
                time.sleep(1)
            dlg.Update(100)

app = wx.App()
Gui(None)
app.MainLoop()

It looks like an “imperfect modal mode”. But your test is quite challenging! :woozy_face: Borrowing Robin’s words, I guess that the modal dialog is not yet running when the progress dialog is shown. So some things may not be fully initialized yet, like what is the ‘top’ window that should be activated when the dialog is closed.

@komoto48g I think he had a lengthy initialization of the App itself in mind, when everything was much slower than today :thinking:

import time
import wx

class Gui(wx.Frame):

    def __init__(self, parent):
        super().__init__(parent, title='Dialog in Dialog test')

        btn = wx.Button(self, label='left click me, please')
        btn.Bind(wx.EVT_BUTTON, self.test)

        self.Centre()
        self.Show()

    def test(self, _):
        with  Test(self, 'lalala') as dlg:
            dlg.ShowModal()

class Test(wx.TextEntryDialog):

    def __init__(self, parent, msg):
        super().__init__(parent, msg)

        self.dlg = wx.ProgressDialog(
            "Update", "Updating SVN Folders...", maximum=100, parent=self)
        # Update SVN
        for ctr in range (5):
            self.dlg.Update(ctr+1, "SVN Updating:...")
            time.sleep(1)
        self.dlg.Update(100)

    def __del__(self):
        self.dlg.Destroy()

class WxApp(wx.App):
    def __init__(self):
        super().__init__()

    def OnPreInit(self):
        self.pd = wx.ProgressDialog(
            "Mywx application", "initializing...", maximum=100)

    def OnInit(self):
        try:
            # do a lot of stuff
            for ctr in range (5):
                self.pd.Update(ctr+1, "SVN initializing...")
                time.sleep(1)
            self.pd.Update(100)
            Gui(None)
            self.MainLoop()
        except Exception:
            wx.Bell()
            return False
        else:
            return True

WxApp()

Yes. I just wanted to mention something similar.
Although, as Dietmar said, the OP’s original problem doesn’t depend on the App’s initialization, your test - modal dialog of modal dialog without its initialization - is a bit complicated to me. :roll_eyes: