Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing a widget from its wxPython parent

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)

like image 277
Sci Prog Avatar asked Apr 07 '26 07:04

Sci Prog


1 Answers

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()
like image 133
user2682863 Avatar answered Apr 09 '26 21:04

user2682863



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!