Next Previous Contents

9. Container widgets in GTK--

Some GUI toolkits focus on the ability to place arbitrary widgets on a display area. Container widgets are often something of an afterthought in these toolkits; they require special handling and special, sometimes obtuse, programming techniques.

GTK takes a different approach. Not only does it give you container widgets, almost every widget in it is a container of some sort. Widget containment and hierarchy are fundamental parts of the GTK design; understanding how GTK containment widgets work is therefore essential to understanding the GTK-- toolkit.

We've already covered many things about this aspect of GTK/GTK--. Now it's time to discuss GTK--'s container widget system in detail.

9.1 The GTK-- container widget system

Building a hierarchical widget system is not easy. Making it simple to use is downright difficult. GTK-- breaks the problem down by defining two basic types of containers:

It's common for newcomers to GTK-- to wonder what the purpose of the Gtk::Bin class is. Why not use multi-item containers for everything? Knowing the answer to this question is an important key to understanding GTK/GTK--.

First, let's consider the alternative: making all containers multi-item. This approach is taken by most toolkits that implement some sort of hierarchy for their widgets. This is possible because, in general, such toolkits only implement a single placement policy for their widgets: what, in GTK--, is called the fixed policy, which leaves it up to the programmer to place widgets at the proper screen coordinates.

GTK--, by contrast, provides multiple placement policies, implemented primarily by its multiple-item container widgets. There are many of these: GTK-- provides horizontal boxes, vertical boxes, tables, packboxes, fixed layout areas, and others. This helps the programmer to easily construct clean-looking interfaces, without even using a form-painter.

Now, if all GTK-- containers were multiple-item, we'd have to answer some questions about each one, such as:

.. and so forth. These are just a few of the problems that might arise if we tried to dispense with the concept of single-item containers. The existence of the Gtk::Bin class enables GTK to sidestep these thorny issues.

A single-item container in GTK-- is not a second-class citizen, and it's not really handicapped in any way. In fact, a Gtk::Bin container isn't really limited to containing just one widget. If we place, in a Gtk::Bin, one of the multiple-item containers, our Gtk::Bin can contain more than one widget after all. Furthermore, we can select the placement policy for those widgets by merely using the proper multi-item container widget.

Making a Gtk::Bin widget is much easier than making a multi-item container. We need deal with only one child, so we don't have to write code to handle multiple children, or provide any sort of access to them. We don't need to worry about placing or displaying the children; that will be taken care of by a multi-item child.

This means that it's practical to make many widgets containers which in other toolkits wouldn't be. Take the lowly button widget, for example. Very few - if any - toolkits consider buttons to be a container widget. GTK--, however, does, and it's almost too easy not to: by simply making a button a derivative of Gtk::Bin, it doesn't even have to provide code for displaying a label or picture; this can be done by a child pixmap or label widget. A Gtk::Button can even contain both at the same time, using any placement policy the programmer desires. Consider how impractical this would be without Gtk::Bin!

Of course, sometimes you don't want multiple children in a single-item container widget; one, or even zero, is enough. Most buttons only contain one child, that being a label or a picture. Because it doesn't require its container widgets to support multiple children, GTK lets you avoid paying for what you don't use.

Now consider the GTK-- window widget. We've already seen that GTK windows can contain at most one child. Now you know that, far from being a limitation, this is actually a strength of GTK, thanks to the Gtk::Bin concept.

We summarise this section with a profound statement that composer Brian Eno once made:

"Let your limitations be your secret strengths."

... which GTK certainly does, in this case.

9.2 Using Gtk::Bin widgets

Gtk::Bin derives from a class called Gtk::Container, which is a base class for all widgets which contain other widgets. It provides virtual methods for operations such as adding and removing children and obtaining contained widgets, among other things.

Unlike human parents, Gtk::Bin-derived widgets don't have much to take care of when it comes to their only children. Gtk::Bin widgets use the following two methods to add and remove their child widgets:

void Gtk::Bin::add(Gtk::Widget& p0);
void Gtk::Bin::remove();

Gtk::Bin also provides some convenience functions for adding labels and pixmaps (a common thing to do with Gtk::Bin widgets):

void add_label(const string &label, gfloat x=0.0, gfloat y=0.5);
void add_pixmap(const Gdk_Pixmap &pixmap, const Gdk_Bitmap &bitmap);
void add_pixlabel(const Gdk_Pixmap &pixmap, const Gdk_Bitmap &bitmap,
                  const string &label, gfloat x=0.0, gfloat y=0.5);
void add_pixlabel(const string &pixfile, 
                  const string &label, gfloat x=0.0, gfloat y=0.5);

These are similar to the constructors for the Pixmap and Label widgets; see those sections for details.

9.3 Understanding the GTK-- multiple-item widgets

Multiple-item widgets inherit from Gtk::Container; just as with Gtk::Bin, you use the add() and remove() methods to add and remove contained widgets. Unlike Gtk::Bin::remove(), however, the remove() method for Gtk::Container takes an argument:

void Gtk::Container::add(Gtk::Widget& p0);
void Gtk::Container::remove(Gtk::Widget& p0);

The argument for remove() specifies which widget to remove, since there can be more than one.

Multiple-item containers have a bit more to do than Gtk::Bin widgets, since they can have more than one child (something which those of you with families can no doubt appreciate). The add() and remove() methods give you a way to put things in and take things out, but what if you need to access the contained widgets (which you probably will)?

If you're an accomplished C++ programmer, you'll be happy to hear that most of the GTK-- widgets which maintain lists of things do so STL-style (if you don't know STL, learn it!). They don't necessarily use actual STL containers (there are good reasons for this), but the lists they use look, feel, and act, for the most part, just like STL container classes.

There is, however, a major difference between GTK-- containers and STL containers. Normally, when you use a vector, for example, you expect that whatever you put in, you'll get out, unmodified. You wouldn't make a vector<int> and expect to get doubles out of it. Strangely enough, GTK-- containers don't always work like that. In fact, it's perfectly normal to put one kind of object into a GTK-- container, and to later get a different kind out. Why this odd behaviour?

Consider a menu widget, which must maintain a hierarchical list of menus and menu items. Menus can't just contain any widget at all; they can only contain certain objects, like menu items, separators, and submenus. To ensure consistency, what's needed is a "filter" to keep out illegal objects. Also, since only a few types of objects are allowed, convenience functions can be provided to make it easy to build up menus from scratch.

GTK-- takes care of both requirements in one stroke using special helper objects, or elements. Helper objects are temporary. They're constructed and passed to a list insertion function (typically in the same call); the list insertion function uses the information in the helper object to construct the real object, which is then inserted into the list.

As an example, let's look at the Notebook widget (explained in the section on Notebook widgets). As we'll see later on, notebook widgets consist of a series of "pages", which are "stacked" on top of each other, so that only one is visible at a time. The pages are selected by clicking on marked "tabs".

Each page in a notebook requires, at minimum, the following information:

(The GTK-- notebook widget keeps other data for each page as well.)

To insert a new page in a notebook, we can use one of the notebook helper classes, like this:

notebook->pages().push_back(
          Gtk::Notebook_Helpers::TabElem(*frame,bufferl));

(This line comes from the notebook example program.) Let's see what's going on here. Assume we have a pointer to a Notebook widget called notebook; we go from that to a member function called pages(), which returns an STL-like list object. On this we call the function push_back() (this should be familiar to those of you who know STL).

The object that the pages() method returns is called a Notebook_Helpers::PageList. It's one of the STL-like containers that we keep referring to. Let's take a look at some of the parts of this class (this has been heavily edited for clarity; see <gtk--/notebook.h> for the actual definition):

namespace Notebook_Helpers
{
    class PageList
    {
    public:
             . . .
        void push_back(const Element& e);
             . . .
        Page* operator[](size_type l);
    };
};

There are two important things to notice here:

So what does a PageList actually hold? It holds (as far as the programmer is concerned) Pages, objects which contain all the necessary data about a notebook page. The twist with using the Notebook widget (and most other GTK-- multi-item containers) is that you aren't allowed to construct Page objects, and then insert them; you have to insert Element objects instead. The reasons for this are numerous, and mainly involve the technical aspects of wrapping the GTK+ lists in C++ classes; the specifics don't concern us here.

This scheme of storing different objects than we put in has some important advantages:

Not bad, eh? Perhaps the design isn't so odd after all!

9.4 Using the GTK-- multiple-item widgets

Each multiple-item container in GTK-- (with a few exceptions) contains a GTK-- list, which holds information for the contained elements. These lists work almost exactly like ordered STL containers - so much so, in fact, that rather than explaining them in detail, we can refer you to the STL documentation for most of their methods (saving ourselves a lot of typing in the process!).

At minimum, GTK-- container lists support iterators and the usual complement of insertion, deletion, and addition methods. You can always expect the following methods to be available for GTK-- lists:

Also, the [] operator is overloaded; note that it's usually order N, so if performance is a consideration, or the list has a large number of elements, think carefully before using it.

The element objects and list objects are defined, for each container, in a namespace whose name ends in _Helpers. For example, the helper namespace for the notebook widget is called Notebook_Helpers. Several element types are usually provided; the Notebook widget provides three:

TabElem and MenuElem inherit from Element; the insertion functions take an Element reference as argument. All multi-item containers have an Element object in their helper namespaces; usually there are additional "sub-element" objects available (like TabElem and MenuElem) which derive from Element. Element classes vary from container to container, since each container contains different kinds of objects; we'll document these as we come to them.

A common point of confusion with new GTK-- users is over the nature of Elements. It's very important to remember that Elements are not "real" objects; they exist only temporarily, and they're never stored by multi-item widgets. They are used only as temporary "parameter-holders". Therefore, the following segment of code is illegal:

MenuElem *m= new MenuElem("hello");
m->right_justify();
items().push_back(*m);

We constructed a new MenuElem helper object, and then tried to invoke right_justify() on it before adding it to the menu. The trouble is that there is no right_justify() method in the MenuElem class! The correct way to accomplish this would be:

items().push_back(MenuElem("hello"));
items().back()->right_justify();

Here, we've constructed a MenuElem and inserted it into the menu by passing it to push_back(), causing the real menu item to be created. We've then called right_justify() on the object retrieved from the list. This is correct; the object retrieved from the list is not a MenuElem, but a MenuItem, which is a "real" menu item object, and therefore supports the right_justify() method as expected. (Fortunately, the right way is actually shorter than the wrong way, in this case!)

9.5 Container widgets: a rogues' gallery

In the next two chapters, we'll be looking at each of GTK--'s container widgets in detail. The Gtk::Bin container widgets we'll be discussing are:

We've already encountered some Gtk::Bin widgets: buttons (all types), and windows.

Panes are a special case; they actually contain two child widgets, but since they don't really maintain a list of children, we'll discuss them along with the single-child widgets.

The multiple-child containers we'll discuss are:

Of that list, there are three widgets that do not use STL-like container lists: button boxes, the Layout widget, and the Fixed widget.


Next Previous Contents