Adam Saunders
Adam Saunders

Reputation: 380

wxPython - Automatically closing nested modal dialogs

Python 2.7, WxPython 3.0.2

We are trying to automatically close an entire program under certain conditions. For various reasons, we can't just kill the process. We've had some level of success with it. We can close it if there's no modal dialogs, or a single modal dialog. Once we introduce the second modal dialog (nested), it fails to stop properly.

The actual error received appears to be:

wx._core.PyAssertionError: C++ assertion "IsRunning()" failed at ..\..\src\common\evtloopcmn.cpp(83) in wxEventLoopBase::Exit(): Use ScheduleExit() on not running loop

Here's a working example of our issue. The frame will automatically close after 5 seconds. Clicking the button will load a dialog. Clicking the button on the dialog will open another dialog. It works fine until the last dialog is opened.

from threading import Thread
from time import sleep
import wx


class MainFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="TEST", size=(400, 400))
        self.Show()
        self.__someDialog = None
        self.__myThread = None

        self.__okButton = wx.Button(self, -1, "Press me")
        self.Bind(wx.EVT_BUTTON, self.__onOK)

        self.__myThread = Thread(target=self.__waitThenClose, name="Closer")
        self.__myThread.setDaemon(True)
        self.__myThread.start()

    def __onOK(self, evt):
        self.__someDialog = SomeDialog(self)
        self.__someDialog.ShowModal()

    def closeOpenDialogs(self):
        lst = wx.GetTopLevelWindows()

        for i in range(len(lst) - 1, 0, -1):
            if isinstance(lst[i], wx.Dialog):
                print "Closing " + str(lst[i])
                lst[i].Close(True)
                #lst[i].Destroy()

    def __waitThenClose(self):

        for x in range(0, 5):
            print "Sleeping..."
            sleep(1)

        self.closeOpenDialogs()
        wx.CallAfter(self.Close, True)


class SomeDialog(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent, id=-1, title='Some Dialog')
        self.SetSize((300, 300))
        self.__anotherDialog = None
        self.__okButton = wx.Button(self, -1, "Press me")

        self.Bind(wx.EVT_BUTTON, self.__onOK)
        wx.EVT_CLOSE(self, self.__on_btn_cancel)

    def __onOK(self, evt):
        self.__anotherDialog = AnotherDialog(self)
        self.__anotherDialog.ShowModal()

    def __on_btn_cancel(self, event):
        self.EndModal(wx.ID_CANCEL)


class AnotherDialog(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, None, id=-1, title='Another Dialog')
        self.SetSize((200, 200))
        wx.EVT_CLOSE(self, self.__on_btn_cancel)

    def __on_btn_cancel(self, event):
        self.EndModal(wx.ID_CANCEL)


if __name__ == "__main__":

    app = wx.App()

    mainFrame = MainFrame()

    app.MainLoop()

Upvotes: 3

Views: 2213

Answers (2)

user2682863
user2682863

Reputation: 3217

I think what is happening here is that the first call to ShowModal() blocks the at the app level (not just the frame level) which is preventing the second dialog from becoming fully initialized. To work around this issue I would call Show() instead of ShowModal() and add wx.FRAME_FLOAT_ON_PARENT to the dialog style flags. You can also call Disable() on the parts of the program you don't want the user to interact with while the dialogs are open.

EDIT: Here is a working example:

from threading import Thread
from time import sleep
import wx


class MainFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="TEST", size=(400, 400))
        self.Show()
        self.__someDialog = None
        self.__okButton = wx.Button(self, -1, "Press me")
        self.Bind(wx.EVT_BUTTON, self.__onOK)

        self.__myThread = Thread(target=self.__waitThenClose, name="Closer")
        self.__myThread.setDaemon(True)
        self.__myThread.start()

    def __onOK(self, evt):
        self.__someDialog = SomeDialog(self)
        self.__someDialog.ShowModal()

    def closeOpenDialogs(self, evt=None):
        lst = wx.GetTopLevelWindows()
        for i in range(len(lst) - 1, 0, -1):
            dialog = lst[i]
            if isinstance(dialog, wx.Dialog):
                print "Closing " + str(dialog)
                # dialog.Close(True)
                wx.CallAfter(dialog.Close)
                # sleep(1)
                # dialog.Destroy()

    def __waitThenClose(self):
        for x in range(0, 10):
            print "Sleeping..."
            sleep(1)
        wx.CallAfter(self.closeOpenDialogs)
        wx.CallAfter(self.Close, True)


class SomeDialog(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent, id=-1, title='Some Dialog')
        self.SetSize((300, 300))
        self.__anotherDialog = None
        self.__okButton = wx.Button(self, -1, "Press me")
        self.Bind(wx.EVT_BUTTON, self.__onOK)
        wx.EVT_CLOSE(self, self.__on_btn_cancel)

    def __onOK(self, evt):
        self.__anotherDialog = AnotherDialog(self)
        self.__anotherDialog.SetWindowStyleFlag(
            wx.FRAME_FLOAT_ON_PARENT|wx.DEFAULT_DIALOG_STYLE)
        self.__anotherDialog.Show()

    def __on_btn_cancel(self, event):
        event.Skip()
        self.EndModal(wx.ID_CANCEL)


class AnotherDialog(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent, id=-1, title='Another Dialog')
        self.SetSize((200, 200))
        wx.EVT_CLOSE(self, self.__on_btn_cancel)
        parent.Disable()

    def __on_btn_cancel(self, event):
        event.Skip()
        self.GetParent().Enable()
        # self.EndModal(wx.ID_CANCEL)


if __name__ == "__main__":
    app = wx.App()
    mainFrame = MainFrame()
    app.MainLoop()

Upvotes: 3

VZ.
VZ.

Reputation: 22688

The only way to reliably gracefully close all the modal dialogs, whether they were explicitly opened by your own code or not, is to use wxModalDialogHook to remember all the opened dialogs and then close them all, in the reverse (i.e. LIFO) order, before quitting the application.

Unfortunately I don't know if wxModalDialogHook is available in Python.

Upvotes: 0

Related Questions