Based on user interaction, I would like to dynamically add and remove controls to a panel in a wxPython notebook. The approach I’ve tried most thoroughly is to call .Clear() on the panel’s sizer and add all new controls. However, on both Windows 7 and Linux desktops, rendering artifacts and stale controls remain visible under the new contents. How can I completely remove the old controls and add new controls without these artifacts?
Below is a sample program that reproduces the issue on Windows 7. Note the two different .update() methods of StaticPanel and DynamicPanel:
#!/usr/bin/python
import wx
import sys
class StaticPane(wx.Panel):
"""A panel that contains simple text that is updated
when the .update() method is called. The text is updated
using .SetText(), and the text control sticks around
between calls to .update()."""
def __init__(self, *args, **kwargs):
super(StaticPane, self).__init__(*args, **kwargs)
self._sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self._sizer)
self._counter = 0
self._base_text = "Some Text"
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
def update(self):
self._counter += 1
self._text.SetValue(self._base_text + "!" * self._counter)
class DynamicPane(wx.Panel):
"""A panel that contains simple text that is updated
when the .update() method is called. The text is updated
by removing the existing text control, and adding a new one
with the updated text string."""
def __init__(self, *args, **kwargs):
super(DynamicPane, self).__init__(*args, **kwargs)
self._sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self._sizer)
self._counter = 0
self._base_text = "Some Text"
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
def update(self):
self._counter += 1
self._sizer.Clear()
self._text = wx.TextCtrl(self, -1,
self._base_text + "!" * self._counter,
style=wx.TE_READONLY)
self._sizer.Add(self._text, -1, wx.EXPAND)
self.Layout()
class TestViewer(wx.Frame):
"""A Frame with a button and a notebook. When the button is pressed,
each of the two pages in the notebook recieve a call to .update().
"""
def __init__(self, parent):
super(TestViewer, self).__init__(parent, -1, "Test Viewer")
self.Bind(wx.EVT_CLOSE, self.OnClose)
self._panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self._panel.SetSizer(vbox)
update_button = wx.Button(self._panel, wx.ID_CLOSE, "Update")
update_button.Bind(wx.EVT_BUTTON, self.update)
vbox.Add(update_button, 0, wx.EXPAND)
self._nb = wx.Notebook(self._panel)
self._view_one = StaticPane(self._nb, -1)
self._view_two = DynamicPane(self._nb, -1)
self._nb.AddPage(self._view_one, "One")
self._nb.AddPage(self._view_two, "Two")
vbox.Add(self._nb, 1, wx.EXPAND | wx.ALL)
self.Layout()
def update(self, e):
self._view_one.update()
self._view_two.update()
def OnClose(self, event):
sys.exit(0)
if __name__ == "__main__":
app = wx.App(False)
frame = TestViewer(None)
frame.Show()
app.MainLoop()
Click the button “Update” to update the text in the two panels. The first panel updates the text control by using .SetText(), while the second panel replaces the TextCtrl with a new one. Note that as you resize the window or mouseover the second panel after a few button clicks, there are overlapping controls and other artifacts.
Here are screenshots showing the stacked controls. Both images were taken after the same number of button clicks, they just show the two different panels at the same state. I expected the text to be exactly the same.


Simply clearing the sizer will only remove the references to its contents, not the widgets. When you create a widget, it will register with the parent window you supplied. Consider the following code:
The parent window (Frame in this example) will take ownership of the TextCtrl and even if you set it to None, the Frame will keep it alive until it’s is destroyed. To remove the TextCtrl, you have to destroy it explicitly:
If you want to remove all child widgets at once, you can use: