Dialog.Destroy() hangs program in 3.0.2-OSX-Cocoa

We are working on bringing our application up to date with wxPython 3.0.2, however this is one of two major bugs that is still around in our code. I’m using OSX 10.10 to test with.

Background on example: on program start, we spawn a custom dialog telling the user that some things are loading. This dialog has an animation, so it’s important to keep the main thread with the GUI clear while we load data in the background thread. When that is done, we sent a command to a callback that Destroy()s the dialog, and the program is able to function as normal.

This works well in 2.8, but it seems to hang our app in 3.0. The dialog message disappears, but we cannot close the program or interact with the GUI, almost as if the GUI was still locked under Modal.

Here’s a test script that demonstrates, being as close to the original program as possible in the logic path it takes:

import wxversion
wxversion.select('3.0')
import wx
import time
import threading

class OpenThread(threading.Thread):
    def __init__(self, callback):
        threading.Thread.__init__(self)
        self.callback = callback
        self.start()

    def run(self):
        time.sleep(0.5)  # Give GUI some time to finish drawing

        for i in xrange(5):
            print i
            time.sleep(.3)
        print "ALL DONE"
        wx.CallAfter(self.callback)

class WaitDialog(wx.Dialog):
    def __init__(self, parent, title = "Processing"):
        wx.Dialog.__init__ (self, parent, id=wx.ID_ANY, title = title, size=(300,30),
                           style=wx.NO_BORDER)
        mainSizer = wx.BoxSizer( wx.HORIZONTAL )
        self.SetBackgroundColour(wx.WHITE)
        txt = wx.StaticText(self, wx.ID_ANY, u"Waiting...", wx.DefaultPosition, wx.DefaultSize, 0)
        mainSizer.Add( txt, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0 )
        self.SetSizer( mainSizer )
        self.Layout()
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.CenterOnParent()

    def OnClose(self, event):
        pass

class MainFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Test")
        self.waitDialog = None

        mainSizer = wx.BoxSizer( wx.HORIZONTAL )
        choice = wx.Choice(self, wx.ID_ANY, style=0)
        choice.Append("No Selection", 0)
        choice.Append("Selection 1", 1)
        choice.Append("Selection 2", 2)

        mainSizer.Add( choice , 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0 )
        self.SetSizer( mainSizer )

        self.Show()
        self.doThing()

    def doThing(self):
        self.waitDialog = WaitDialog(self, title="Opening previous fits")
        OpenThread(self.closeWaitDialog)
        self.waitDialog.ShowModal()

    def closeWaitDialog(self):
        self.waitDialog.Destroy()

test = wx.App(False)
MainFrame()
test.MainLoop()

``

You can comment out the self.waitDialog bits and see that it is the dialogs giving trouble. There are other places in the program that this happens in, always after we close out of a Dialog, so it’s not just constrained to this specific example. Everything works as it should on Linux and Windows as far as we can tell. Is there something I’m missing? Is there a workaround? We also have a few more dialogs that we utilize, so a workaround would ideally be a small fix rather than a huge refactoring.

What about changing it a bit:
def doThing(self):
with WaitDialog(self, title=“Opening previous fits”) as
self.waitDialog:
OpenThread(self.closeWaitDialog)
self.waitDialog.ShowModal()
def closeWaitDialog(self):
self.waitDialog.EndModal(0)
self.waitDialog.Close()
Don’t have a MAC to test on.
Werner

···

Hi Ryan,

  On 8/5/2015 20:40, Ryan Holmes wrote:
      We

are working on bringing our application up to date with
wxPython 3.0.2, however this is one of two major bugs that is
still around in our code. I’m using OSX 10.10 to test with.

      Background

on example: on program start, we spawn a custom dialog telling
the user that some things are loading. This dialog has an
animation, so it’s important to keep the main thread with the
GUI clear while we load data in the background thread. When
that is done, we sent a command to a callback that Destroy()s
the dialog, and the program is able to function as normal.

      This

works well in 2.8, but it seems to hang our app in 3.0. The
dialog message disappears, but we cannot close the program or
interact with the GUI, almost as if the GUI was still locked
under Modal.

Werner wrote:

What about changing it a bit:
def doThing(self):
    with WaitDialog(self, title="Opening previous fits") as

self.waitDialog:
OpenThread(self.closeWaitDialog)
self.waitDialog.ShowModal()
def closeWaitDialog(self):
self.waitDialog.EndModal(0)
self.waitDialog.Close()

Agreed. The destruction of modal dialogs should be the responsibility of the code that called ShowModal, and it should only be done after the ShowModal returns. (The dialog context manager shown in Werner’s code will call Destroy for you automatically as it exits the with statement.) In addition, the call to Close probably isn’t necessary, but if the dialog is remaining visible a little too long then doing Hide
there would be better.

···

Hi Ryan,

  On 8/5/2015 20:40, Ryan Holmes wrote:
      We

are working on bringing our application up to date with
wxPython 3.0.2, however this is one of two major bugs that is
still around in our code. I’m using OSX 10.10 to test with.

      Background

on example: on program start, we spawn a custom dialog telling
the user that some things are loading. This dialog has an
animation, so it’s important to keep the main thread with the
GUI clear while we load data in the background thread. When
that is done, we sent a command to a callback that Destroy()s
the dialog, and the program is able to function as normal.

      This

works well in 2.8, but it seems to hang our app in 3.0. The
dialog message disappears, but we cannot close the program or
interact with the GUI, almost as if the GUI was still locked
under Modal.


Robin Dunn

Software Craftsman

http://wxPython.org