wxBoxSizer not destroyed automatically

The underlying C++ wxBoxSizer is not automatically freed when the corresponding Python object disappears (reaches a reference count of zero), unless the sizer has been added to another sizer or set as the sizer for a wxWindow with SetSizer. The attached sample demonstrates this: a memory leak is reported when the program ends. Calling Destroy() on the sizer prevents the leak, but this is undocumented.

It seems to me that the Python wxSizer's __del__ method ought to destroy the C++ object. Also, if wxSizer needs to be Destroy()-ed unless its window or parent sizer does so, then both that fact and the Destroy method should be documented.

I am using wxPython 2.3.2.1, Python 1.5.2, on MS Windows 98SE.

David

P.S. the reason I have an extra, unused sizer in the first place is that
I have a subclass of wxDialog which I further subclass to create several variants on the same dialog. My base class's __init__ method creates a wxBoxSizer and passes it to a more_buttons method. If more_buttons returns true, then the sizer is added to the main sizer for the dialog. That way, the variants can override more_buttons to add controls to the dialog. I can rewrite my code so that the sizer isn't created unless one of the subclasses actually adds more controls.

leak.py (470 Bytes)

Hi David...

I am new here but I see this problem about memory leaks alot. What I tend to
do is call del on the variable I want to clean. It is cheaper and cleaner. It
is better integrated into the python core. I dont normally use Destroy and I
have not noticed memory leaks as yet. Then again, I could be wrong.

Seeya

Jag

The underlying C++ wxBoxSizer is not automatically freed when the
corresponding Python object disappears (reaches a reference count of
zero), unless the sizer has been added to another sizer or set as
the sizer for a wxWindow with SetSizer. The attached sample
demonstrates this: a memory leak is reported when the program ends.
Calling Destroy() on the sizer prevents the leak, but this is undocumented.

It seems to me that the Python wxSizer's __del__ method ought to
destroy the C++ object. Also, if wxSizer needs to be Destroy()-ed
unless its window or parent sizer does so, then both that fact and
the Destroy method should be documented.

I am using wxPython 2.3.2.1, Python 1.5.2, on MS Windows 98SE.

David

P.S. the reason I have an extra, unused sizer in the first place is that
I have a subclass of wxDialog which I further subclass to create
several variants on the same dialog. My base class's __init__
method creates a wxBoxSizer and passes it to a more_buttons method.
If more_buttons returns true, then the sizer is added to the main
sizer for the dialog. That way, the variants can override
more_buttons to add controls to the dialog. I can rewrite my code
so that the sizer isn't created unless one of the subclasses
actually adds more controls.

Bilal A.R. Jagot
CeTAS
University of the Witwaterrand
Johannesburg
+27 11 717 7226
+27 83 556 3927

Cliff Wells wrote:

The underlying C++ wxBoxSizer is not automatically freed when the corresponding Python object disappears (reaches a reference count of zero), unless the sizer has been added to another sizer or set as the sizer for a wxWindow with SetSizer. The attached sample demonstrates this: a memory leak is reported when the program ends. Calling Destroy() on the sizer prevents the leak, but this is undocumented.

I think this is standard for all of the wxPython widgets. For instance,
if you create a window in a function, referencing it via a local
variable, the window will continue to exist even after the variable goes

True, and not only in wxPython (though in C++ you wouldn't expect a pointer to be automatically deleted). However, wxSizer is a wxObject, not a wxWindow, and there is no *documented* Destroy method in wxSizer or wxObject. wxWindow.Destroy() is documented.

out of scope. In fact, it isn't even necessary to *ever* have a python
reference to a wxPython object. Just saying wxFrame(None, -1,
"").Show(1) in your code is enough to create a frame, even though it is
never referenced by *any* Python variable.

Good point. I didn't think of that because I never use that construct.

David

···

On Wed, 2002-12-25 at 08:19, David C. Fox wrote:

Jag wrote:

Hi David...

I am new here but I see this problem about memory leaks alot. What I tend to do is call del on the variable I want to clean. It is cheaper and cleaner. It is better integrated into the python core. I dont normally use Destroy and I have not noticed memory leaks as yet. Then again, I could be wrong.

Generally, you don't need to call Destroy explicitly, because most of the things which need to be destroyed are child wxWindows, and their parent wxWindow will call Destroy when it closes. Top-level windows without parents are usually Destroy'ed by the wxCloseEvent handler when the user closes them explicitly.

The most common exceptions are wxDialogs shown with ShowModal, which are not automatically destroyed when they are closed, because that would make it much more difficult to retrieve the data the user had entered in the dialog.

Thanks for the suggestion about del. However, when applied to a variable, del just removes the local reference to that variable. A variable local to some function/method will have its reference removed automatically when that function/method ends, so all del does is to remove that reference earlier. So, if you have

class SomeClass:
   def __del__(self):
     print "SomeClass instance at %d being deleted" % id(self)

def somefunc():
   x = SomeClass()
   del x
   print "somefunc ending"

running somefunc() will print

   SomeClass instance at 9203429 being deleted
   somefunc ending

whereas if you omit the del x from somefunc, you'll get

   somefunc ending
   SomeClass instance at 9203429 being deleted

In particular, doing a del on the wxBoxSizer doesn't make a difference, because my local Python reference to the wxBoxSizer would be deleted anyway when the __init__ method ended.

···

Seeya

Jag

The underlying C++ wxBoxSizer is not automatically freed when the corresponding Python object disappears (reaches a reference count of zero), unless the sizer has been added to another sizer or set as the sizer for a wxWindow with SetSizer. The attached sample demonstrates this: a memory leak is reported when the program ends. Calling Destroy() on the sizer prevents the leak, but this is undocumented.

It seems to me that the Python wxSizer's __del__ method ought to destroy the C++ object. Also, if wxSizer needs to be Destroy()-ed unless its window or parent sizer does so, then both that fact and the Destroy method should be documented.

I am using wxPython 2.3.2.1, Python 1.5.2, on MS Windows 98SE.

David

Cliff Wells wrote:

Cliff Wells wrote:

The underlying C++ wxBoxSizer is not automatically freed when the corresponding Python object disappears (reaches a reference count of zero), unless the sizer has been added to another sizer or set as the sizer for a wxWindow with SetSizer. The attached sample demonstrates this: a memory leak is reported when the program ends. Calling Destroy() on the sizer prevents the leak, but this is undocumented.

I think this is standard for all of the wxPython widgets. For instance,
if you create a window in a function, referencing it via a local
variable, the window will continue to exist even after the variable goes

True, and not only in wxPython (though in C++ you wouldn't expect a pointer to be automatically deleted). However, wxSizer is a wxObject, not a wxWindow, and there is no *documented* Destroy method in wxSizer or wxObject. wxWindow.Destroy() is documented.

But the sizer does have a Destroy method, correct? So part of the
problem is a doc bug?

Yes, Destroy exists, at least in wxPython (haven't checked wxWindows).

Yes, unless/until it is feasible to automatically destroy the sizer, the solution is to update the documentation to reflect the current state.

out of scope. In fact, it isn't even necessary to *ever* have a python
reference to a wxPython object. Just saying wxFrame(None, -1,
"").Show(1) in your code is enough to create a frame, even though it is
never referenced by *any* Python variable.

Good point. I didn't think of that because I never use that construct.

I don't think anyone in their right mind would, I was merely using it as
an example.

Now that I think about it, that is probably the reason why deleting the reference to the Python wrapper wxBoxSizer doesn't destroy the underlying C++ object: There is probably no good way for the Python wrapper object to tell that you've called window.SetSizer(box_sizer) or big_sizer.Add(box_sizer), unless both those methods increment a reference counter in box_sizer.

But this is getting a bit speculative, since I haven't read the relevant parts of the wxPython and wxWindows source code.

BTW, a solution to your immediate problem might be to let more_buttons()
return a sizer (or a panel with a sizer) or None instead of true of
false. That way the sizer only gets created if it's actually going to
be added and you can still test for whether to add it to your main
sizer.

Thanks, that's exactly what I did after sending the original email. I just wanted to report the possible bug and/or

David

···

On Thu, 2002-12-26 at 07:22, David C. Fox wrote:

On Wed, 2002-12-25 at 08:19, David C. Fox wrote:

David C. Fox wrote:

If I'm understanding you correction, only the wxPython version of wxBoxSizer has Destroy() method, which is why it isn't (and shouldn't be) listed in the wxWindows documentation?

Yes and no. It is wxPython specific, but I added it to the wxObject class, so it is inherited. Many of the classes that don't derive from wxObject also have it.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robb Shecter wrote:

Ok, I'm more interested in the design of the library than the dead object issue, so now that's gotten down to this:

BTW, a solution to your immediate problem might be to let more_buttons()
return a sizer (or a panel with a sizer) or None...

Thanks, that's exactly what I did after sending the original email...

I don't see why you'd want to do this when make a Dialog framework. In my case, at least, I want really locked down and consistent design of the UI's. I think I'll attach two examples. I used the Template Method pattern with subclassing. Here's part of the abstract base class. (This API is a work in progress... you can see that there's a couple different styles of interaction here:)

In my case, I had a single dialog for correcting a misrecognized phrase in a speech-recognition application:


Later, I realized that in some cases the user might want to correct several phrases in a row. Most of the dialog would be the same, but I would need some extra buttons to let the user to move from one phrase to the next/previous one, or to accept or discard changes to the current phrase before selecting another phrase to correct. I played around with a few different layout ideas, but decided the simplest thing was to add an extra row of buttons:


If you are really curious, the dialogs are from the VoiceCode programming-by-voice project:

http://voicecode.iit.nrc.ca/VoiceCode/

I just wanted a quick way of adding the extra buttons and corresponding event handlers while inheriting the rest of the controls and behavior of the original dialog. Having a subclass override a more_buttons method and return a sizer containing the buttons isn't at all a generic dialog framework, which is what you seem to be attempting to do below. It just seemed the quickest way to accomplish what I was trying to do in this case.

   #
   # Methods that must be implemented by subclasses:
   #

   def makeDataPanel(self):
       """
       Return a panel which will be the main user interface
       of the dialog. It must be a subclass of wxWindow
       and have two methods: setValues() and getValues() for
       setting and retrieving information.
       """
       raise NotImplementedError()

   def getDialogName(self):
       """
       Return my name, which will be used as one part of the
       frame title.
       """
       raise NotImplementedError()

   #
   # Optional methods to be implemented by subclasses:
   #

   def onClose(self):
       """
       Called when the dialog is closed for any reason.
       """
       pass

   def isReentrant(self):
       """
       Return true if a dialog instance can be used
       repeatedly. Default is to return true. Subclasses
       can override this to disallow multiple ShowModal()
       calls to one instance.
       """
       return 1

   def useCloseAsOk(self):
       """
       Return true if the 'OK' button should have the
       text, 'Close'. Only has an effect if no Cancel
       button is present.
       Default is false.
       """
       return 0

   def getCancelHandler(self):
       """
       Subclasses must implement this if they want a Cancel
       button to appear on the dialog. The 'None' object
       can be returned if no cleanup behavior is desired.
       """
       raise NotImplementedError()

   def getResetHandler(self):
       """
       Subclasses must implement this if they want a Reset
       button to appear on the dialog.
       """
       raise NotImplementedError()
     
Thanks, it is always useful to see how other people design GUI code.

David

David C. Fox wrote:

http://www.ataword.com/png/correction.png ...

Thanks, that's interesting stuff.

Thanks, it is always useful to see how other people design GUI code.

I agree! In my case, I found myself needing to write a dozen or so different dialogs, and I wanted to (1) avoid redundant work and coding, and (2) enforce UI standards, like the spelling of "OK/Ok"... or what exactly should appear in the title bar of the dialog. (Currently this is "Appname: dialog name").

Is that a custom list widget that you've made with the numbers alongside the rows?

Robb

Robb Shecter wrote:

David C. Fox wrote:

http://www.ataword.com/png/correction.png ...

Is that a custom list widget that you've made with the numbers alongside the rows?

No, the text choices are just a wxListBox. The numbers are just a bunch of static text controls, aligned right and bottom without borders in a vertical wxBoxSizer. Then I use a wxFlexGridSizer to align the numbers and the list box vertically, and to align the list box, the text control above it, and the static text label above that horizontally. The vertical alignment isn't perfect, because the list box has a top border while the numbers don't. Also, I'm relying on the fact that the fonts are the same for the list box and the static text control.

Ideally, I'd like to underline the numbers and make them accelerators for selecting the corresponding choice, but I haven't been able to do that except by adding a bunch of hidden text controls, which is rather messy and may not be so robust to future changes in wxWindows.

I experimented a little bit with using a borderless wxTextCtrl for the text of each choices. This improved the vertical alignment and allowed me to put in the accelerators for the numbers without as much hassle, but I couldn't get it to look right - there was always too much space between the rows.

David

David C. Fox wrote:

No, the text choices are just a wxListBox. The numbers are just a bunch of static text controls, aligned right and bottom without borders in a vertical wxBoxSizer...

Hmm - well here's some random ideas:

* What about the wxHtmlWindow widget; I think it lets you make a hyperlink and then react to the click as a normal event. You could create the UI you want with an HTML table with 2 columns.

* A two-column wxListCtrl or two-column wxGrid...