Containers

Where wxPython uses sizers for layout, Wax uses containers, which are basically frames and panels with sizers built in. Laying out an application, while still non-trivial, becomes a lot easier.

Containers are abstract classes. They are not meant to be instantiated directly; rather, other classes derive from them. For example, Panel and Frame both derive from the base Container class.

There are various types of containers, each with their own methods:

In the following examples, we'll use the base Container object, which is probably the most commonly used. The other containers are used in much the same way, although some methods may differ (e.g. GridContainer's AddComponent takes different parameters than Container's AddComponent). To find out these differences, and to learn how these containers are used, we refer to the separate container pages. [todo: make sure they're linked above]

Frame and Panel

The Wax Frame and Panel classes are used in much the same way. You use them to add other widgets to it. The difference is that Frame is used for toplevel windows, while Panel is used inside frames or other panels.

Both classes derive from the appropriate wxPython class (wx.Frame and wx.Panel, respectively), *and* from the Container class (as defined in wax/containers.py). You will usually find Frame and Panel pairs that both derive from a certain container; for example, GridFrame and GridPanel derive from GridContainer, etc.

As such, Frame and Panel both have methods derived from wxPython classes (e.g. SetSize()), but they also have methods derived from Container.

To see what this means in practice, here's a simple program that adds a button to a frame.

# button-1.py

from wax import *

class MainFrame(Frame):
    def Body(self):
        b = Button(self, "Click me", event=self.OnButtonClick)
        self.AddComponent(b, border=5, expand='both')
        self.Pack()
        self.Size = (100, 100)
    def OnButtonClick(self, event):
        print "Oh, ya!"

app = Application(MainFrame, title='button-1')
app.Run()

To properly add components to a container, we need to do three things.

1. Create the component with the appropriate parents. Usually, this will be the container the component will be added to (also known as the component's parent). In this example, it's self (MainFrame). Most often, the first parameter in the component's constructor is the parent; other parameters are used to configure the control.

b = Button(self, "Click me", event=...)

2. Add the component to the container. For the base Container class, this is done with the AddComponent method.

self.AddComponent(widget, ...options...)

The first parameter is the control we're adding. Other parameters are optional, and are used to set the layout (for example, to set a border, or to indicate whether the control expands if the parent expands, etc).

self.AddComponent(widget, border=5, expand='both')

In this particular example, we state that we want a border around the button, 5 pixels wide. We also state that when the parent (in this case, the MainFrame) expands, the button should expand as well. (Try resizing the window and see what happens.)

[other options can be found on Container page]

3. When you've added all widgets to the container, call the Pack() method. This methods makes sure everything (widgets and container) gets the right size.

Note that Pack() resizes things, so setting a widget's size before packing may have a different result than doing so afterwards.

Adding multiple controls

The previous example wasn't very realistic; it only added one button. Let's try adding multiple widgets to the frame.

# containers-1.py

from wax import *

class MainFrame(Frame):
    def Body(self):
        b1 = Button(self, "Harry")
        self.AddComponent(b1)
        b2 = Button(self, "Ron")
        self.AddComponent(b2)
        b3 = Button(self, "Hermione")
        self.AddComponent(b3)
        self.Pack()

app = Application(MainFrame, title='containers-1')
app.Run()

Note that we didn't pass any options to AddComponent. As a result, when we resize the window, the buttons don't move or expand.

Also note that by default, the buttons are added horizontally. Frame and Panel can be configured to add widgets in two directions, horizontally and vertically; as you see, the default is horizontal. This can be changed easily by passing the direction parameter to the container's constructor:

# containers-2.py

from wax import *

class MainFrame(Frame):
    def Body(self):
        b1 = Button(self, "Harry")
        self.AddComponent(b1)
        b2 = Button(self, "Ron")
        self.AddComponent(b2)
        b3 = Button(self, "Hermione")
        self.AddComponent(b3)
        self.Pack()

app = Application(MainFrame, title='containers-1', direction='vertical')
app.Run()

By passing the appropriate parameters to AddComponent, we can change the behavior of individual widgets:

# containers-3.py

from wax import *

class MainFrame(Frame):
    def Body(self):
        b1 = Button(self, "Harry")
        self.AddComponent(b1, expand='h')
        b2 = Button(self, "Ron")
        self.AddComponent(b2, expand='both')
        b3 = Button(self, "Hermione")
        self.AddComponent(b3, expand='v')
        self.Pack()

app = Application(MainFrame, title='containers-1', direction='vertical')
app.Run()

When you resize the window, you will see that the first button expands horizontally, the third button expands vertically, and the second button expands in both directions.

[again, for more options, see the base container page]

Nested containers

Containers can be nested. This isn't really very difficult, since containers are widgets too, so it doesn't really matter if you add a Button to a container, or a Panel. You do have to make sure you use the right parents and widgets. (Getting this wrong is the cause of many layout problems.)

# containers-4.py

from wax import *

class MainFrame(Frame):
    def Body(self):
        b1 = Button(self, "Harry")
        self.AddComponent(b1, expand='h')
        b2 = Button(self, "Ron")
        self.AddComponent(b2, expand='both')
        b3 = Button(self, "Hermione")
        self.AddComponent(b3, expand='v')

        # create panel and add controls to it
        p = Panel(self, direction='v')
        b4 = Button(p, "Snape")
        p.AddComponent(b4, expand='h')
        b5 = Button(p, "Dumbledore")
        p.AddComponent(b5, expand='both')
        p.Pack()

        # add panel to main frame
        self.AddComponent(p, border=10, expand='both')

        self.Pack()

app = Application(MainFrame, title='containers-4', direction='vertical')
app.Run()

What happens here is the following.

1. Create a Panel. Its parent must be self, because that's what we want to add it to. (In the example, we call this panel p.)

2. The widgets that need to be added to p, will need to have p as their parent (not self!).

3. Call p.AddComponent() to add these widgets to the new panel.

4. When everything is added, pack the panel: p.Pack().

5. Finally, add this panel to the main frame, like any other component: self.AddComponent(p, ...). (In this case, we add a border around it, to make the panel stand out from the other controls.)

It's not uncommon to have three or more levels of nesting. In such cases, it's sometimes clearer to create a separate method that creates the panel, then returns it:

# containers-4a.py

from wax import *

class MainFrame(Frame):
    def Body(self):
        b1 = Button(self, "Harry")
        self.AddComponent(b1, expand='h')
        b2 = Button(self, "Ron")
        self.AddComponent(b2, expand='both')
        b3 = Button(self, "Hermione")
        self.AddComponent(b3, expand='v')

        # create panel and add controls to it
        p = self.MakePanel(self)
        self.AddComponent(p, border=10, expand='both')

        self.Pack()

    def MakePanel(self, parent):
        p = Panel(parent, direction='v')
        b4 = Button(p, "Snape")
        p.AddComponent(b4, expand='h')
        b5 = Button(p, "Dumbledore")
        p.AddComponent(b5, expand='both')
        p.Pack()
        return p

app = Application(MainFrame, title='containers-4a', direction='vertical')
app.Run()

(Note: Strictly spoken, in this example, it's not necessary to pass the parent as a parameter to MakePanel. In general, it's a good habit to add it though, in case the panel gets a different parent in future code.) This way, MakePanel returns a panel that can be added to any container, rather than just MainFrame.

Troubleshooting

Common causes of problems:

- Forgetting Pack()

- Parent/AddComponent don't match up, and/or adding things to the wrong object

- Incorrect options (e.g. forgot expand, etc)