This only happens on Linux (possible OS X also, can’t test atm), works fine on Windows.
I have a wx.ProgressDialog that is spawned with the main thread. I send the work off to another thread, and it periodically calls back to a callback function in the main thread that will update the ProgressDialog or, at the end of the work, destroy it. However, I get an interesting message on Linux when this happens:
(python:12728): Gtk-CRITICAL **: IA__gtk_window_set_modal: assertion ‘GTK_IS_WINDOW (window)’ failed
The dialog does close, but if I try to spawn it again it looks like it’s already almost finished. Sometimes a seg fault will follow this message as well.
I’ve tried to simulate it with a stripped down version here:
import wxversion
wxversion.select("2.8")
import wx
import sys
import threading
MAX_COUNT = 100
## This class is in a different area of the codebase and
class WorkerThread(threading.Thread):
def __init__(self, callback):
threading.Thread.__init__(self)
self.callback = callback
def run(self):
# simulate work done. IRL, this calls another function in another
# area of the codebase. This function would generate an XML document,
# which loops through a list of items and creates a set of elements for
# each item, calling back after each item. Here, we simply set up a for
# loop and simulate work with wx.MilliSleep
for i in xrange(MAX_COUNT):
print i
wx.MilliSleep(30)
wx.CallAfter(self.callback, i)
# Send done signal to GUI
wx.CallAfter(self.callback, -1)
class Frame(wx.Frame):
def __init__(self, title):
wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(350,200))
panel = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
m_btn = wx.Button(panel, wx.ID_ANY, "Run Stuff")
m_btn.Bind(wx.EVT_BUTTON, self.OnRunButton)
box.Add(m_btn, 0, wx.ALL, 10)
panel.SetSizer(box)
panel.Layout()
def OnRunButton(self, event):
self.progressDialog = wx.ProgressDialog("Doing work",
"Doing Work",
maximum=MAX_COUNT, parent=self,
style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)
self.worker(self.threadCallback)
self.progressDialog.ShowModal()
def worker(self, callback):
# This bit is in another part of the codebase originally. In the test,
# I could have added it to OnRunButton, but I wanted function calls to
# be similar between test and actual code
thread = WorkerThread(callback)
thread.start()
def threadCallback(self, info):
# We update based on position, or destroy if we get a -1
if info == -1:
self.progressDialog.Destroy()
else:
self.progressDialog.Update(info)
app = wx.App(redirect=False)
top = Frame("ProgressDialog Test")
top.Show()
app.MainLoop()
``
(we select 2.8, but ideally any fix should work in both 2.8 and 3.0. I actually haven’t been able to test it in 3.0 in linux due to a bad 3.0 build)
This does a good job at representing the issue: works fine in Windows, but seg fault when it tries to destroy the progress dialog. However, I can’t get the example to show the GTK_IS_WINDOW
Ive tried searching for solutions. I’ve read that it might be due to the fact that the worker thread finishes too quickly, and thus leaves the GUI with some messages in it’s queue. I’m not sure I completely understand this (never got the hang of Yields and messages, etc), but what I believe this to mean is that when the worker is at 100%, the ProgressDialog (being slower), might only be at 75%, and still has the extra 25% of messages to use to “Update” the GUI, but instead gets destroyed.
I’d like some clarification on if I’m understanding that correctly or not.
Also, I believe .Hide()
works as a work around on Linux (But not windows with wx3), but I’d like to Destroy it instead because that’s the proper thing to do.
Regardless, any help would be greatly appreciated. =)