The following program has a memory leak (I verified this on a linux system, with the top command)
import wx
from random import randint
class MyPanel(wx.Panel):
def __init__(self,parent):
wx.Panel.__init__(self,parent)
self._parent = parent
self._nb_buttons = 0
self._main_sizer = wx.BoxSizer(wx.VERTICAL)
self._ctrl_sizer = wx.BoxSizer(wx.HORIZONTAL)
self._btns_sizer = wx.BoxSizer(wx.VERTICAL)
self._btn_tim = wx.Button(self,label="Start auto")
self._btn_tim.Bind(wx.EVT_BUTTON, self.OnStartStopEvent)
self._ctrl_sizer.Add(self._btn_tim, 0, wx.CENTER|wx.ALL, 5 )
self._main_sizer.Add(self._ctrl_sizer, 0, wx.CENTER )
self._main_sizer.Add(self._btns_sizer, 0, wx.CENTER|wx.ALL,10)
self.SetSizer(self._main_sizer)
self._timer1 = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self._timer1)
self._timer_running = False
def OnStartStopEvent(self,event):
if self._timer_running: # toggle state of Timer
self._timer1.Stop()
self._btn_tim.SetLabel("Start")
self._timer_running = False
else:
self._timer1.Start(200,False)
self._btn_tim.SetLabel("Stop")
self._timer_running = True
def AddWidget(self):
self._nb_buttons += 1
label = "Button %d" % self._nb_buttons
name = "button%d" % self._nb_buttons
new_button = wx.Button(self,label=label, name=name)
self._btns_sizer.Add( new_button, 0, wx.ALL, 5 )
self._parent._my_sizer.Layout()
self._parent.Fit()
def RemoveWidget(self):
if self._btns_sizer.GetChildren():
self._btns_sizer.Hide(self._nb_buttons -1 )
self._btns_sizer.Remove(self._nb_buttons -1 )
self._nb_buttons -= 1
self._parent._my_sizer.Layout()
self._parent.Fit()
def OnTimer(self,event):
n = randint(-3,3)
if self._nb_buttons < 2: n += randint(0,2)
if self._nb_buttons > 10: n -= randint(0,3)
while n > 0:
self.AddWidget()
n -= 1
while n < 0:
self.RemoveWidget()
n += 1
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,parent=None, title="Add remove buttons")
self._my_sizer = wx.BoxSizer(wx.VERTICAL)
panel1 = MyPanel(self)
self._my_sizer.Add(panel1, 1, wx.EXPAND)
self.SetSizer(self._my_sizer)
self.Fit()
self.Show()
def main():
app = wx.App(False)
frame1 = MyFrame()
app.MainLoop()
if __name__ == '__main__':
main()
The problem is that in RemoveWidget, the button is removed from the Sizer, but not from the parent wx.Panel.
What is the way to remove the wx.Button from the parent (wx.Panel in this case)?
When I searched for "remove wxPython widget", the answers I found only told how to remove from sizers. (In fact, my code is a variation from http://www.blog.pythonlibrary.org/2012/05/05/wxpython-adding-and-removing-widgets-dynamically/ ).
There is a RemoveChild method in the wx.Window class, but documentation says it is internal and should not be called by user code (https://wxpython.org/Phoenix/docs/html/wx.Window.html#wx.Window.RemoveChild)
I think I figured it out. I'm on windows 10, but I observed the same memory leak. Modifying the RemoveWidget method to this solved it on my computer. See code comments for an explanation
Edited to reflect inputs from RobinDunn
def RemoveWidget(self):
if self._btns_sizer.GetChildren():
# GetItem returns a SizerItem, to get the actual button we have to call GetWindow
window = self._btns_sizer.GetItem(self._nb_buttons - 1).GetWindow()
# Calling Destroy removes widget from parent and sizer
window.Destroy()
self._nb_buttons -= 1
self._parent._my_sizer.Layout()
self._parent.Fit()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With