wx.DestroyChildren in event handler cause segmentation fault on osx - python

The following Python code runs fine on windows, but cause a segmentation fault on
osx. Any suggestions why? It does not make a difference to use CallAfter...
import wx
class myFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(wx.StaticText(self, -1, 'Static text'))
self.sizer.Add(wx.Button(self, -1, 'Button'))
self.SetSizer(self.sizer)
self.Bind(wx.EVT_BUTTON, self.on_button)
def on_button(self, event):
self.sizer.Clear()
self.DestroyChildren()
app = wx.App()
frame = myFrame()
frame.Show()
app.MainLoop()

It is because there are still system messages pending for the destroyed widgets, (mouse motion in this case) as well as the possibility that the code that is run after the return from the event handler will try to use the destroyed widgets (calling methods or accessing attributes.)
Using CallAfter to delay the destruction does solve the problem for me, which version of wxPython are you using? If this doesn't work for you then you may want to try using wx.CallLater instead, with a small timeout value.
import wx
class myFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(wx.StaticText(self, -1, 'Static text'))
self.sizer.Add(wx.Button(self, -1, 'Button'))
self.SetSizer(self.sizer)
self.Bind(wx.EVT_BUTTON, self.on_button)
def on_button(self, event):
wx.CallAfter(self.doit)
def doit(self):
self.sizer.Clear()
self.DestroyChildren()
app = wx.App()
frame = myFrame()
frame.Show()
app.MainLoop()

Related

wxPython update StaticText every x seconds/minutes using timer

I'm trying to update some static text using a timer and the output of a function.
The code is here: code.
I know very little about wxPython, it's one of the many things that I just don't get and this is maddening, if I print the output of apper to console it works perfectly, all I want to do is have what prints out to the console applied to the text.
What am I doing wrong?
Timers can be a pain to use, an easier way is to use the functions wx.CallAfter and/or wx.CallLater - also these functions are thread-safe and can be used to invoke functions on the GUI thread from other worker threads. Here is a sample...
import random
import wx
class Frame(wx.Frame):
def __init__(self):
super(Frame, self).__init__(None)
self.SetTitle('Title')
panel = wx.Panel(self)
style = wx.ALIGN_CENTRE | wx.ST_NO_AUTORESIZE
self.text = wx.StaticText(panel, style=style)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.AddStretchSpacer(1)
sizer.Add(self.text, 0, wx.EXPAND)
sizer.AddStretchSpacer(1)
panel.SetSizer(sizer)
self.on_timer()
def on_timer(self):
self.text.SetLabel(str(random.randint(0, 100)))
wx.CallLater(1000, self.on_timer)
if __name__ == '__main__':
app = wx.App()
frame = Frame()
frame.Show()
app.MainLoop()

Restoring windows in wxpython

I am having a hard time restoring a window after it has been minimized.
Minimize works fine, but i am trying to open the window back up.. self restores but Vodka_Frame doesn't.
Here is my code:
def minimizeProgram(event):
self.Iconize()
Vodka_Frame.Iconize()
def maximizeProgram(event):
if self.IsIconized()=='True' or Vodka_Frame.IsIconized()=='True':
self.Iconize(False)
Vodka_Frame.Iconize(False)
self.Show(True)
Vodka_Frame.Show(True)
self.Raise()
Vodka_Frame.Raise()
#### Catch the minimize event and minimize both windows.
self.Bind(wx.EVT_ICONIZE,minimizeProgram)
#### Catch the maximize event and maximize both windows.
self.Bind(wx.EVT_LEFT_DCLICK,maximizeProgram)
What am i doing wrong? How can i get my windows back! :)
I'm not sure what you're doing wrong without a small runnable example. However, I created the following simple script that works for me:
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="Test")
panel = MyPanel(self)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.toggleIconize, self.timer)
self.timer.Start(5000)
self.Show()
#----------------------------------------------------------------------
def toggleIconize(self, event):
""""""
if self.IsIconized() == True:
print "raising..."
self.Iconize(False)
self.Raise()
else:
print "minimizing!"
self.Iconize()
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
Basically it just minimizes and raises itself every 5 seconds. I am using Python 2.6.6 and wxPython 2.8.12.1 on Windows 7 Pro.
The relationship between your frames is not clear, but if you make the other frame child of the main one (i.e. specify the main frame as its parent when creating it), then it will be minimized and restored automatically when the main frame is minimized or restored, without you having to do anything special.

wxPython - How to force UI refresh?

I've boiled my problem down to the example code shown in this post. Note that I'm not calling app.MainLoop() because this isn't an interactive window; I want it to pop up at the beginning, show some progress bars while work happens, and disappear when complete.
My (limited) understanding of wxPython and wx.Yield() led me to believe that calling wx.Yield() after some UI work would flush those changes to the display. That is not occurring -- when I run this script, there is a gray box where "Hello World" should be.
What am I doing wrong?
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, size=(400,400))
self.panel = wx.Panel(self, -1)
wx.StaticText(self.panel, -1, "Hello World", (20,20))
wx.Yield()
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
def run():
app = MyApp(redirect=False)
import time; time.sleep(5)
run()
You need to be yielding or updating on a regular basis, so that when your OS/window manager sends repaint messages to your app, it can handle them. I am not 100% sure about wxPython as I haven't used it recently but I don't think you can do what you want without the main loop to handle the messages appropriately.
You might find something useful here about threading the main loop, however (as well as explanation of why the main loop is important): http://wiki.wxpython.org/MainLoopAsThread
instead of wx.Yield()
just call self.Update()
Without the MainLoop no events will be fired and also .Refresh will not work.
I guess wxSplashscreen may be what you are looking for. Example: http://wiki.wxpython.org/SplashScreen
Not that it will do the original poster any good after all this time but wx.Yield() would have done the job. It just needs to be in the right place as does the self.Show()
The following outputs a progress bar which gets updated.
import wx
import time
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, size=(290,200))
self.panel = wx.Panel(self, -1)
wx.StaticText(self.panel, -1, "Hello World", (20,20))
self.gauge = wx.Gauge(self.panel, -1, 50, pos=(20,50), size=(250, 20))
self.Show()
n = 0
while n < 50:
n = n+1
self.gauge.SetValue(n)
wx.Yield()
time.sleep(1)
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, -1)
self.SetTopWindow(self.frame)
return True
def run():
app = MyApp()
run()

wxPython: Handling events in a widget that is inside a notebook

I have a wxPython notebook, in this case a wx.aui.AuiNotebook. (but this problem has happened with other kinds of notebooks as well.) In my notebook I have a widget, in this case a subclass of ScrolledPanel, for which I am trying to do some custom event handling (for wx.EVT_KEY_DOWN). However, the events are not being handled. I checked my code outside of the notebook, and the event handling works, but when I put my widget in the notebook, the event handler doesn't seem to get invoked when the event happens.
Does the notebook somehow block the event? How do I solve this?
I tried reproducing your problem but it worked fine for me. The only thing I can think of is that there is one of your classes that also binds to wx.EVT_KEY_DOWN and doesn't call wx.Event.Skip() in its callback. That would prevent further handling of the event. If your scrolled panel happens to be downstream of such an object in the sequence of event handlers it will never see the event.
For reference, here's an example that worked for me (on Windows). Is what you're doing much different than this?
import wx
import wx.aui, wx.lib.scrolledpanel
class AppFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
# The notebook
self.nb = wx.aui.AuiNotebook(self)
# Create a scrolled panel
panel = wx.lib.scrolledpanel.ScrolledPanel(self, -1)
panel.SetupScrolling()
self.add_panel(panel, 'Scrolled Panel')
# Create a normal panel
panel = wx.Panel(self, -1)
self.add_panel(panel, 'Simple Panel')
# Set the notebook on the frame
self.sizer = wx.BoxSizer()
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.SetSizer(self.sizer)
# Status bar to display the key code of what was typed
self.sb = self.CreateStatusBar()
def add_panel(self, panel, name):
panel.Bind(wx.EVT_KEY_DOWN, self.on_key)
self.nb.AddPage(panel, name)
def on_key(self, event):
self.sb.SetStatusText("key: %d [%d]" % (event.GetKeyCode(), event.GetTimestamp()))
event.Skip()
class TestApp(wx.App):
def OnInit(self):
frame = AppFrame(None, -1, 'Click on a panel and hit a key')
frame.Show()
self.SetTopWindow(frame)
return 1
if __name__ == "__main__":
app = TestApp(0)
app.MainLoop()

Why is wxGridSizer much slower to initialize on a wxDialog then on a wxFrame?

It seems that this is specific to windows, here is an example that reproduces the effect:
import wx
def makegrid(window):
grid = wx.GridSizer(24, 10, 1, 1)
window.SetSizer(grid)
for i in xrange(240):
cell = wx.Panel(window)
cell.SetBackgroundColour(wx.Color(i, i, i))
grid.Add(cell, flag=wx.EXPAND)
class TestFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
makegrid(self)
class TestDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent)
makegrid(self)
class Test(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
btn1 = wx.Button(self, label="Show Frame")
btn2 = wx.Button(self, label="Show Dialog")
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
sizer.Add(btn1, flag=wx.EXPAND)
sizer.Add(btn2, flag=wx.EXPAND)
btn1.Bind(wx.EVT_BUTTON, self.OnShowFrame)
btn2.Bind(wx.EVT_BUTTON, self.OnShowDialog)
def OnShowFrame(self, event):
TestFrame(self).Show()
def OnShowDialog(self, event):
TestDialog(self).ShowModal()
app = wx.PySimpleApp()
app.TopWindow = Test()
app.TopWindow.Show()
app.MainLoop()
I have tried this on the following configurations:
Windows 7 with Python 2.5.4 and wxPython 2.8.10.1
Windows XP with Python 2.5.2 and wxPython 2.8.7.1
Windows XP with Python 2.6.0 and wxPython 2.8.9.1
Ubuntu 9.04 with Python 2.6.2 and wxPython 2.8.9.1
The wxDialog wasn't slow only on Ubuntu.
I got a reply on the wxPython-users mailing list, the problem can be fixed by calling Layout explicitly before the dialog is shown.
This is really weird...
My guess is that this is due to
Windows and wxWidgets not dealing very
well with overlapping siblings, and so
when the sizer is doing the initial
layout and moving all the panels from
(0,0) to where they need to be that
something about the dialog is causing
all of them to be refreshed and
repainted at each move. If you
instead do the initial layout before
the dialog is shown then it is just as
fast as the frame.
You can do this by adding a call to window.Layout() at the end of
makegrid.
-- Robin Dunn

Categories