'wxPython How to correctly close wxProgressDialog when set to Pulse()

I have recently migrated an application from python2.7.15 (wx 3.0.2.0) to python 3.7.9 (wx 4.1.1)

The application uses a wx.ProgressDialog set to Pulse() while waiting for some tasks to complete and then uses Destroy() to attempt to close the ProgressDialog window.

It runs without errors on Python27 but it crashes on Python37. In both ProgressDialog.Destroy() does not actually seem to close the dialog box but it is closed when the next wx element is displayed.

import wx  
from time import sleep

def main():
    app = wx.App()

    progress_dlg = wx.ProgressDialog("Model Manager", "Connecting...")
    progress_dlg.Pulse()
    sleep(5)      # some long tasks are done here
    progress_dlg.Destroy()
    print("waiting")
    sleep(5)

    extracted_names = ["ONE", "TWO", "THREE"]
    CANDialog = wx.SingleChoiceDialog(None, "Please select:", "Selection", extracted_names)
    CANDialog.SetSelection(0)
    if CANDialog.ShowModal() == wx.ID_OK:
        choosen_can = CANDialog.GetSelection()
    else:
        return
    print(extracted_names[choosen_can])

if __name__ == '__main__':
    main()

In python37 this gives the following error:

    if CANDialog.ShowModal() == wx.ID_OK:
wx._core.wxAssertionError: C++ assertion ""wxEventLoopBase::GetActive() == m_tempEventLoop"" failed at ..\..\src\generic\progdlgg.cpp(718) in wxGenericProgressDialog::~wxGenericProgressDialog(): current event loop must not be changed during wxGenericProgressDialog lifetime

If I replace the Destroy() with Update() then the dialog is correctly closed automatically:

progress_dlg = wx.ProgressDialog("Model Manager", "Connecting...", maximum=1)
progress_dlg.Pulse()
sleep(5)
progress_dlg.Update(1)

Whilst I can solve my problem, I would like to understand if this is the right way to do it & why Destroy() is not working?

Thanks in advance for any advice.

Gabbo CH



Solution 1:[1]

I don't think that Destroy should be called on the ProgressDialog. Update() with the maximum value is the right way. (With style PD_AUTO_HIDE, which is the default.) Afterwards, delete the reference to the dialog. Best way is to use the context manager.

If you look at the wxPython demo and the wxWidgets C++ examples, there's no Show(), ShowModal() or Destroy(). Recently there was a related ticket for wxPython where I have also added a usage example.

https://github.com/wxWidgets/Phoenix/issues/1610#issuecomment-1090664834

https://docs.wxwidgets.org/trunk/classwx_generic_progress_dialog.html

Solution 2:[2]

As Dietmar pointed you in the right direction, I'll concentrate on the other issues.
You code doesn't create the wx MainLoop and you are not allowing for the gauge to be visually updated. While your code may freakishly work in that small code sample, there will be issues later on, when the code increases.

Here's your code with minor changes. It includes setting the MainLoop and falling back to that loop during the long task to allow the progress gauge to update.

import wx  
from time import sleep

def main():
    progress_dlg = wx.ProgressDialog("Model Manager", "Connecting...", maximum=100)
    for i in range(5):
        progress_dlg.Pulse()
        sleep(1)      # some long tasks are done here
        wx.GetApp().Yield()
    progress_dlg.Update(100)
    print("waiting")
    wx.GetApp().Yield()
    sleep(5)

    extracted_names = ["ONE", "TWO", "THREE"]
    CANDialog = wx.SingleChoiceDialog(None, "Please select:", "Selection", extracted_names)
    CANDialog.SetSelection(0)
    if CANDialog.ShowModal() == wx.ID_OK:
        choosen_can = CANDialog.GetSelection()
    else:
        return
    print(extracted_names[choosen_can])

if __name__ == '__main__':
    app = wx.App()
    main()
    app.MainLoop()

See: https://docs.wxpython.org

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dietmar Schwertberger
Solution 2