Sizers API

pobrien@orbtech.com wrote:

I'd like to propose a change to the current API for sizers. Since the
current API isn't documented in the wxWindows reference, this change
shouldn't be too dramatic. I'll outline the existing API followed by
my proposed change.

[...]

Issues

None of the wxPython variants are documented in the wxWindows manual,
so no changes are needed there.

This change only effects code that uses the wxPython variants, and I
wasn't able to find any examples that did. For instance, the examples
on the wiki use Add(...), not AddWindow(...) or AddSizer(...).

Boa doesn't use sizers, so this change won't effect Boa.

I don't know what the other designers do with sizers. Anyone?
What do the rest of you think about this proposal?
What issues have I missed?

1) wxDesigner uses the explicit names AddWindow/Spacer/Sizer.
   (I see I wasn't fast enough with this message; Robin's already
   noted this in the list!)

   Therefore there is presumably a lot of code out there using
   them that you wouldn't want to break. I recommend contacting
   its author, Robert Roebling (Robert.Roebling@t-online.de)
   about these changes. (And talk to him about true/false vs.
   True/False whilst you're at it. :wink:

2) I personally just discovered that "Add()" was "overloaded"
   when I modified a demo in the suite. Because I have been
   using wxDesigner for the bulk of my layout work, and because
   I knew that Add was overloaded in C++, and that generally
   wxPython had equivalents to handle the overloading, it never
   occurred to me that Robin might have tweaked Add() to do
   runtime type checking to "do the right thing."

   That having been said, I find the AddWindow/Spacer/Sizer
   rather helpful from a readability standpoint, particularly when
   the rest of the arguments are used positionally vs. being
   specified with keywords. (I guess I find overloading
   to be obfuscating rather than elegant.) Given the polymorphism
   of Python, these kind of names make it much easier (imho)
   to understand how you are laying things out, and also easier
   to detect when you've accidentally made a mistake.

3) Conceptually, the names of the methods used to add stuff to
   them don't confuse me. However, I find:

   a) how the values of the arguments (eg "proportion=0|1|2|...",
   wxEXPAND, wxGROW, wxALIGN_CENTER_VERTICAL, etc.) affect the
   resulting layout,

   b) how the whole window Layout/SetAutoLayout/Fit/FitInside
      stuff works and relates to sizers,

   c) how nested panels/sizers interact
  
   are the tricky things about sizers.

   For example, I find it odd that you can add stuff to a sizer,
   and then ask for sizer.GetSize(), and get (0,0), but call
   sizer.GetMinSize() and you get the size that its layout will
   eventually have once it's rendered... Do sizers have a size
   or not, and if so, when?
   When should you call sizer.Fit(window) to tell the specified
   window to "size itself around the sizer?" Do you/why do you
   need to, particularly if you've just called window.SetSizer(sizer)?
   (etc.)
   IMO, anything that can be done to make these aspect of sizers
   clearer/cleaner would be an improvement.

4) Donnal Walter suggested:
   > But the second source of confusion is that if one uses nested
   > windows (panels) and nested sizers to any degree, one ends up
   > with two entirely different containment hierarchies. The way
   > I have handled this is to build sizers into my panels, so that
   > adding a component to the panel automatically adds it to the
   > sizer. No muss, no fuss.
  
   I often use nested sizers on a single panel to lay things out.
   (I got into this habit using wxDesigner, which forces you to
   do things this way.) In this scenario, having a single
   sizer for the panel is not sufficient. You could, I suppose,
   define set of nested subpanels within the main one, but I submit
   this isn't what I'd want, as I think of all of the actual
   controls visible from the main panel as being parented to that
   panel, regardless of the layout. Sizers just enforce a
   layout *on* that panel. (IMO, I think it is precisely this
   "dual containment" hierarchy that makes sizers so powerful, if
   somewhat hard to understand (see (3).)
   
(My $0.04,)
/Will Sadkin
Parlance Corporation

3) Conceptually, the names of the methods used to add stuff to
   them don't confuse me. However, I find:

My biggest annoyance is that there is NO documentation about
the Add (and friends) method. The doc for wxSizer says
there is one, and that subclasses use the arguments.

In the doc for the subclasses (eg wxBoxSizer, wxGridSizer)
there is no mention of what their implementation of the
Add methods do.

Specifically I would like to see mention of what fields
are paid attention to, and for the more complicated ones
what keyword args are available (eg row, col, column, rowspan
etc).

Roger

Will Sadkin <wsadkin@nameconnector.com> writes:

3) Conceptually, the names of the methods used to add stuff to
   them don't confuse me. However, I find:

   a) how the values of the arguments (eg "proportion=0|1|2|...",
   wxEXPAND, wxGROW, wxALIGN_CENTER_VERTICAL, etc.) affect the
   resulting layout,

   b) how the whole window Layout/SetAutoLayout/Fit/FitInside
      stuff works and relates to sizers,

   c) how nested panels/sizers interact
  
   are the tricky things about sizers.

   For example, I find it odd that you can add stuff to a sizer,
   and then ask for sizer.GetSize(), and get (0,0), but call
   sizer.GetMinSize() and you get the size that its layout will
   eventually have once it's rendered... Do sizers have a size
   or not, and if so, when?
   When should you call sizer.Fit(window) to tell the specified
   window to "size itself around the sizer?" Do you/why do you
   need to, particularly if you've just called window.SetSizer(sizer)?
   (etc.)
   IMO, anything that can be done to make these aspect of sizers
   clearer/cleaner would be an improvement.

I'm working on some docs that should help explain these things. In
the mean time, a PyCrust session can shed some light:

    >>> import wx
    >>> f = wx.Frame(None, -1, 'Blue Red', size=(100, 100))
    >>> f.GetAutoLayout()
    0
    >>> f.GetSize()
    wxSize(100, 100)
    >>> f.GetBestSize()
    wxSize(100, 100)
    >>> f.GetAdjustedBestSize()
    wxSize(100, 100)
    >>> p1 = wx.Panel(f, -1, size=(200, 100), style=wx.SUNKEN_BORDER)
    >>> f.GetSize()
    wxSize(100, 100)
    >>> f.GetBestSize()
    wxSize(207, 114)
    >>> p2 = wx.Panel(f, -1, size=(100, 50), style=wx.SUNKEN_BORDER)
    >>> f.GetSize()
    wxSize(100, 100)
    >>> f.GetBestSize()
    wxSize(207, 114)
    >>> p1.SetBackgroundColour('BLUE')
    >>> p2.SetBackgroundColour('RED')
    >>> box = wx.BoxSizer(orient=wx.VERTICAL)
    >>> box.GetSize()
    wxSize(0, 0)
    >>> box.GetMinSize()
    wxSize(10, 10)
    >>> box.AddWindow(p1, option=2, flag=wx.EXPAND)
    >>> box.GetSize()
    wxSize(0, 0)
    >>> box.GetMinSize()
    wxSize(200, 100)
    >>> f.GetSize()
    wxSize(100, 100)
    >>> f.GetBestSize()
    wxSize(207, 114)
    >>> box.AddWindow(p2, option=1, flag=wx.EXPAND)
    >>> box.GetSize()
    wxSize(0, 0)
    >>> box.GetMinSize()
    wxSize(200, 150)
    >>> f.GetSize()
    wxSize(100, 100)
    >>> f.GetBestSize()
    wxSize(207, 114)
    >>> box.SetMinSize(f.GetSize()) # This sets min to (100, 100).
    >>> box.GetSize()
    wxSize(0, 0)
    >>> box.GetMinSize() # Box returns the larger of set min or calc'd min.
    wxSize(200, 150)
    >>> f.GetSize()
    wxSize(100, 100)
    >>> f.SetAutoLayout(True) # Doesn't change the frame size.
    >>> f.GetSize()
    wxSize(100, 100)
    >>> f.SetSizer(box) # Doesn't change the frame size.
    >>> f.GetSize()
    wxSize(100, 100)
    >>> box.SetSizeHints(f) # Changes the frame size.
    >>> f.GetSize()
    wxSize(200, 150)
    >>> box.GetSize() # Box size has never changed.
    wxSize(0, 0)
    >>> f.Layout() # This will (finally) change the box size.
    >>> box.GetSize()
    wxSize(200, 150)
    >>> box.GetMinSize()
    wxSize(200, 150)

Hope that helps.

···

--
Patrick K. O'Brien
Orbtech http://www.orbtech.com/web/pobrien
-----------------------------------------------
"Your source for Python programming expertise."
-----------------------------------------------

Yes, I would agree, and I underestand that not everyone uses
containment hierarchies the way I do. But I submit that for the
purposes of wrapping your mind around them, both containment
hierarchies should be combined conceptually. For example, I have
presenter class, Panel, that has a built-in sizer (which can be
easily changed from BoxSizer to GridSizer to FlexGridSizer with an
attribute change). Now, in the first place, I *do* often find it
convenient to define a set of nested panels within the main one.
*BUT* I also have a class, SubPanel, that looks and acts like a
Panel, but which is really just a sizer. Otherwise the API looks
and feels exactly the same. The point is, that one can mix and
match (and nest) Panels and Sizers with a single API and within
what appears to be a single containment hierarchy.

···

--- Will Sadkin <wsadkin@nameconnector.com> wrote:

I often use nested sizers on a single panel to lay things out.
(I got into this habit using wxDesigner, which forces you to
do things this way.) In this scenario, having a single
sizer for the panel is not sufficient. You could, I suppose,
define set of nested subpanels within the main one, but I
submit this isn't what I'd want, as I think of all of the actual
controls visible from the main panel as being parented to that
panel, regardless of the layout. Sizers just enforce a
layout *on* that panel. (IMO, I think it is precisely this
"dual containment" hierarchy that makes sizers so powerful, if
somewhat hard to understand (see (3).)

=====
Donnal Walter
Arkansas Children's Hospital