Chapter 3 introduced the basic essentials needed for creating basic GTK+ applications. It also introduced signals, callback methods, the Gtk.label class, the Gtk.Button class, and the Gtk.Container class.
In this chapter, you expand our knowledge of the Gtk.Container class. Then we show the two kinds of contained widgets: layout and decorator containers. Additionally, we cover a number of derived widgets, including boxes, notebooks, handle boxes, and expanders.
The last widget covered, Gtk.EventBox, allows widgets to take advantage of GDK events.
The purpose of the Gtk.Container class and its descendants
How to use layout containers, including boxes, tables, grid, and panes
When to used fixed containers
How to provide events to all widgets using event boxes
GTK.Container
The Gtk.Container class has been covered briefly in past sections, But we now cover the class in more depth. This is necessary so that you have the necessary base knowledge about containers so we may cover all the derived classes in subsequent sections.
The Gtk.Container class is an abstract class. Therefore you should never attempt to create an instance of this class, only of the derived classes.
The main purpose of a container class is to allow a parent widget to contain one or more child widgets. There are two type of container widgets in GTK+, those used for laying out children and decorators and those that add some sort of functionality beyond positioning children.
Decorator Containers
In Chapter 3, you were introduced to Gtk.ApplicationWindow, a window derived from Gtk.Window, which is derived from Gtk.Bin—a type of container class that has the capability of holding only one child widget. Widgets derived from this class are called decorator containers because they add some type of functionality to the child widget.
For example, a Gtk.Window provides it child with some extra functionality of being placed in a top level widget. Other example decorators include the Gtk.Frame widget, which draws a frame around it child, a Gtk.Button , which makes its child a clickable button, and a Gtk.Expander which can hide or show its child from the user. All there widgets use the add method for adding a child widget.
Widgets that derive from Gtk.Bin include windows, alignments, frames, buttons, combo boxes, event boxes, expanders, handle boxes, scrolled windows, and tool items. Many of these containers are covered in subsequent section of this chapter.
Layout Containers
Another type of container widget provided by GTK+ is called a layout container. These are widgets that are used to arrange multiple widgets. Layout containers can be recognized by the fact that they are derived directly from Gtk.Container.
As the name implies, the purpose of layout containers is to correctly arrange their children according to the user’s preferences, your instructions, and built-in rules. User preferences include the use of themes and font preferences. These can be overridden, but in most cases, you should honor the user’s preferences. There are also resizing rules that govern all container widgets, which is covered in the next section.
Layout containers include boxes, fixed containers, paned widgets, icon views, layouts, menu shells, notebooks, sockets, tables, text views, toolbars, and tree views. We are covering most of the layout widgets throughout this chapter and the rest of the book. More information on those we do not cover is available in the PyGObject API Reference ( http://lazka.github.io/pgi-docs/#Gtk-3.0 ) documentation.
Resizing Children
In addition to arranging and decorating children, containers are tasked with resizing child widgets. Resizing is performed in two phases: size requisition and size allocation. In short, these two steps negotiate the size that is available to a widget. This is a recursive process of communication between the widget, its ancestors, and its children.
Size requisition refers to the desired size of the child. The process begins at the top-level widget, which asks its children for their preferred sizes. The children ask their children and so on, until the last child is reached.
At this point, the last child decides what size it wants to be based on the space it needs to be shown correctly on the screen and any size requests from the programmer. For example, a Gtk.Label widget asks for enough space to fully display its text on the screen or more space if you requested it to have a larger size.
The child then passes this size to its ancestors until the top-level widget receives the amount of space needed based on its children’s requisitions.
Each widget stores its size preferences as width and height values in a Gtk.Requisition object. Keep in mind that a requisition is only a request; it does not have to be honored by the parent widget.
When the top-level widget has determined the amount of space it wants, size allocation begins. If you have set the top-level widget as nonresizable, the widget will never be resized; no further action occurs and requisitions are ignored; otherwise, the top-level widget resizes itself to the desired size. It then pass the amount of available space to its child widget. This process is repeated until all widgets have resized themselves.
Size allocations for every widget are stored in one instance of the Gtk.Allocation structure for each child. This structure is passed to child widgets for resizing with size_allocate(). This function can be called explicitly by the programmer as well, but doing so is not a good idea in the majority of cases.
In most situations, children are given the space they request, but there are certain circumstances when this cannot happen. For example, a requisition is not honored when the top-level widget cannot be resized.
Conversely, once a widget has been given a size allocation by its parent, the widget has no choice but to redraw itself with the new size. Therefore, you should be careful where you call size_allocate(). In most cases, set_size_request() is best to use for resizing widgets.
Container Signals
"add": A child widget was added or packed into the container. This signal is emitted even if you do not explicitly call add() but use the widget’s built-in packing functions instead.
"check_resize": The container is checking whether it needs to resize for its children before taking further action.
"remove": A child has been removed from the container.
"set_focus_child": A child of the container has received focus from the window manager. Now that you know the purpose of the Gtk.Container class, we will progress onto other types of container widgets. You have already learned about windows, a type of Gtk.Bin widget, so we will begin this chapter with a layout container called Gtk.Box.
Horizontal and Vertical Boxes
Gtk.Box is a container widget that allows multiple children to be packed in a one-dimensional, rectangular area. There are two types of boxes: a vertical box which packs children into a single column, and a horizontal box which packs them into a single row.
Note
In GTK+ 2.x, the Gtk.Box was an abstract class. The two subclasses Gtk.HBox and Gtk.VBox were used to create horizontal and vertical boxes respectively. In GTK+ 3.x these two classes have been deprecated and the Gtk.Box has become a real class from which both horizontal and vertical boxes can be created.
Vertical Boxes with Default Packing
In analyzing Listing 4-2, Gtk.Box uses the same set of methods. Gtk.Box uses the same set of methods.
The "spacing" keyword parameter places a default number of pixels of spacing between each child and its neighbor. This value can be changed for individual cells as children are added, if the box is not set as equally spaced.
Since you do not need further access to the labels in Listing 4-2 after they are added to the widget, the application does not store individual pointers to each object. They are all cleaned up automatically when the parent is destroyed. Each button is then added to the box using a method called the packing.Gtk.Box widget.
Packing boxes can be slightly unintuitive because of the naming of functions. The best way to think about it is in terms of where the packing begins. If you pack at the start position, children are added with the first child appearing at the top or left. If you pack at the end position, the first child appears at the bottom or right of the box.
Vertical_Boxes Specifying Packing Parameters
Expand and Fill Properties
expand | fill | Result |
---|---|---|
True | True | The cell expand so that it takes up additional space allocated to the box, and the child widget expand to fill that space. |
True | False | The cell expand so that it takes up additional space, but the widget not expand. Instead, the extra space is empty. |
False | True | Neither the cell nor the widget expand to fill extra space. This is the same thing as setting both properties to False. |
False | False | Neither the cell nor the widget expand to fill extra space. If you resize the window, the cell not resize itself. |
Note
If you have experience programming with other graphical toolkits, the size negotiation system provided by GTK+ may seem odd. However, you quickly learn its benefits. GTK+ automatically takes care of resizing everything if you change a user interface, instead of requiring you to reposition everything programmatically. You will come to view this as a great benefit as you continue learning GTK+.
By using this method, you can move a child widget to a new position in the Gtk.Box. The position of the first widget in a Gtk.Box container is indexed from zero. The widget is placed in the last position of the box if you specify a position value of –1 or a value greater than the number of children.
Horizontal and Vertical Panes
Gtk.Paned is a special type of container widget that holds exactly two widgets. A resize bar is placed between them, which allows the user to resize the two widgets by dragging the bar in one direction or the other. When the bar is moved, either by user interaction or programmatic calls, one of the two widgets shrinks while the other expands.
Note
In GTK+ 2.x, the Gtk.Paned was an abstract class. The two subclasses Gtk.HPaned and Gtk.VPaned were used to create horizontal and vertical boxes respectively. In GTK+ 3.x, these two classes have been deprecated and the Gtk.Paned has become a real class from which both horizontal and vertical panes can be created.
Horizontal Paned with Buttons
All of the Gtk.Paned functions then work with either type of paned widget.
The second parameter in pack1() and pack2() specifies whether the child widget should expand when the pane is resized. If you set this to False, no matter how much larger you make the available area, the child widget does not expand.
Resize and Shrink Properties
resize | shrink | Result |
---|---|---|
True | True | The widget takes up all available space when the pane is resized, and the user is able to make it smaller than its size requisition. |
True | False | The widget takes up all available space when the pane is resized, but available space must be greater than or equal to the widget’s size requisition. |
False | True | The widget will not resize itself to take up additional space available in the pane, but the user is able to make it smaller than its size requisition. |
False | False | The widget will not resize itself to take up additional space available in the pane, and the available space must be greater than or equal to the widget’s size requisition. |
Gtk.Paned provides multiple signals, but one of the most useful is move-handle, which tells you when the resizing bar has been moved. If you want to remember the position of the resize bar, this tells you when you need to retrieve a new value.
Grids
So far, all the layout container widgets I have covered only allow children to be packed in one dimension.
The Gtk.Grid widget, however, allows you to pack children in two-dimensional space.
One advantage of using the Gtk.Grid widget over using multiple Gtk.Box widgets is that children in adjacent rows and columns are automatically aligned with each other, which is not the case with boxes within boxes. However, this is also a disadvantage, because you will not always want everything to be lined up in this way.
Grids Displaying Name
Grid Spacing
Fixed Containers
The Gtk.Fixed widget is a type of layout container that allows you to place widgets by the pixel. There are many problems that can arise when using this widget, but before we explore the drawbacks, let’s look at a simple example.
Specifying Exact Locations
The top-left corner of the fixed container is referred to by location (0,0). You should only be able to specify real locations for widgets or locations in positive space. The fixed container resizes itself, so every widget is completely visible.
You can explicitly set the size and font of text to avoid overlap, but this is not advised in most cases. Accessibility options are provided for users with low vision. If you change their fonts, some users may not be able to read the text on the screen.
Another problem with using Gtk.Fixed arises when your application is translated into other languages. A user interface may look great in English, but the displayed strings in other languages may cause display problems, because the width is not constant. Furthermore, languages that are read right to left, such as Hebrew and Arabic, cannot be properly mirrored with the Gtk.Fixed widget. It is best to use a variable-sized container, such as Gtk.Box or Gtk.Grid in this case.
Finally, it can be quite a pain adding and removing widgets from your graphical interface when using a Gtk.Fixed container. Changing the user interface requires you to reposition all of your widgets. If you have an application with a lot of widgets, this presents a long-term maintenance problem.
On the other hand, you have grids, boxes, and various other automatically formatting containers. If you need to add or remove a widget from the user interface, it is as easy as adding or removing a cell. This makes maintenance much more efficient, which is something you should consider in large applications.
Therefore, unless you know that none of the presented problems will plague your application, you should use variable-sized containers instead of Gtk.Fixed. This container was presented only so you know it is available if a suitable situation arises. Even in suitable situations, flexible containers are almost always a better solution and are the proper way of doing things.
Expanders
Gtk.Expander Container
Activating a Gtk.Expander widget cause it to be expanded or retracted depending on its current state.
Tip
Mnemonics are available in almost every widget that displays a label. Where available, you should always use this feature, because some users prefer to navigate through applications with the keyboard.
If you wish to include an underscore character in the expander label, you should prefix it with a second underscore. If you do not want to take advantage of the mnemonic feature, you can use Gtk.Expander.new() to initialize the Gtk.Expander with a standard string as the label, but providing mnemonics as an option to the user is always a good idea. In normal expander labels, underscore characters are not parsed but are treated as just another character.
The Gtk.Expander widget itself is derived from Gtk.Bin, which means that it can only contain one child. As with other containers that hold one child, you need to use expander.add() to add the child widget.
Notebook
The Gtk.Notebook widget organizes child widgets into a number of pages. The user can switch between these pages by clicking the tabs that appear along one edge of the widget.
Container with Multiple Pages
After you create a Gtk.Notebook, it is not very useful until you add tabs to it. To add a tab to the end or beginning of the list of tabs, you can use notebook.append_page() or notebook.prepend_page(), respectively. Each of these methods accepts a child widget, and a widget to display in the tab, as shown next.
Tip
The tab label does not have to be a Gtk.Label widget. For example, you could use a Gtk.Box widget that contains a label and a close button. This allows you to embed other useful widgets, such as buttons and images, into the tab label.
Each notebook page can only display one child widget. However, each of the children can be another container, so each page can display many widgets. In fact, it is possible to use Gtk.Notebook as the child widget of another Gtk.Notebook tab.
Caution
Placing notebooks within notebooks is possible but should be done with caution, because it can easily confuse the user. If you must do this, make sure that you place the child notebook’s tabs on a different side of the notebook than its parent’s tabs. By doing this, the user is able to figure out what tabs belong to which notebook.
All three of the functions used to add tabs to a Gtk.Notebook return the integer location of the tab you added or –1 if the action has failed.
Notebook Properties
Tab position can be set in notebook.set_tab_pos() by using the Gtk.PositionType enumeration. These include Gtk.PositionType.TOP, Gtk.PositionType.BOTTOM, Gtk.PositionType.LEFT, and Gtk.PositionType.RIGHT.
This property forces tabs to be hidden from the user. Arrows are provided so that the user is able to scroll through the list of tabs. This is necessary because tabs are only shown in one row or column.
If you resize the window so that all of the tabs cannot be shown, the tabs are made scrollable. Scrolling also occurs if you make the font size large enough that the tabs cannot all be drawn. You should always set this property to True if there is any chance that the tabs will take up more than the allotted space.
Tab Operations
GTK+ provides multiple functions that allow you to interact with tabs that already exist. Before learning about these methods, it is useful to know that most of these cause the change-current-page signal to be emitted. This signal is emitted when the current tab that is in focus is changed.
At times, you may also want switch to the next or previous tab, which can be done with call notebook.next_page() or notebook.prev_page(). If a call to either of these functions would cause the current tab to drop below zero or go above the current number of tabs, nothing occurs; the call is ignored.
When deciding what page to move to, it is often useful to know the current page and the total number of tabs. These values can be obtained with notebook.get_current_page(), respectively.
Event Boxes
Various widgets, including Gtk.Label , do not respond to GDK events, because they do not have an associated GDK window. To fix this, GTK+ provides a container widget called Gtk.EventBox. Event boxes catch events for the child widget by providing a GDK window for the object.
Adding Events to Gtk.Label
When using an event box, you need to decide whether the event box’s Gdk.Window should be positioned above the windows of its child or below them. If the event box window is above, all events inside the event box go to the event box. If the window is below, events in windows of child widgets first go to that widget and then to its parents.
Note
If you set the window’s position as below, events do go to child widgets first. However, this is only the case for widgets that have associated GDK windows. If the child is a Gtk.Label widget, it does not have the ability to detect events on its own. Therefore, it does not matter whether you set the window’s position as above or below in Listing 4-8.
Gdk.EventMask Values
Value | Description |
---|---|
Gdk.EventMask.EXPOSURE_MASK | Accepts events when a widget is exposed. |
Gdk.EventMask.POINTER_MOTION_MASK | Accepts events emitted when the proximity of the window is left. |
Gdk.EventMask.POINTER_MOTION_HINT_MASK | Limits the number of GDK_MOTION_NOTIFY events, so they are not emitted every time the mouse moves. |
Gdk.EventMask.BUTTON_MOTION_MASK | Accepts pointer motion events while any button is pressed. |
Gdk.EventMask.BUTTON1_MOTION_MASK | Accepts pointer motion events while button 1 is pressed. |
Gdk.EventMask.BUTTON2_MOTION_MASK | Accepts pointer motion events while button 2 is pressed. |
Gdk.EventMask.BUTTON3_MOTION_MASK | Accepts pointer motion events while button 3 is pressed. |
Gdk.EventMask.BUTTON_PRESS_MASK | Accepts mouse button press events. |
Gdk.EventMask.BUTTON_RELEASE_MASK | Accepts mouse button release events. |
Gdk.EventMask.KEY_PRESS_MASK | Accepts key press events from a keyboard. |
Gdk.EventMask.KEY_RELEASE_MASK | Accepts key release events from a keyboard. |
Gdk.EventMask.ENTER_NOTIFY_MASK | Accepts events emitted when the proximity of the window is entered. |
Gdk.EventMask.LEAVE_NOTIFY_MASK | Accepts events emitted when the proximity of the window is left. |
Gdk.EventMask.FOCUS_CHANGE_MASK | Accepts change of focus events. |
Gdk.EventMask.STRUCTURE_MASK | Accepts events emitted when changes to window configurations occur. |
Gdk.EventMask.PROPERTY_CHANGE_MASK | Accepts changes to object properties. |
Gdk.EventMask.VISIBILITY_NOTIFY_MASK | Accepts change of visibility events. |
Gdk.EventMask.PROXIMITY_IN_MASK | Accepts events emitted when the mouse cursor enters the proximity of the widget. |
Gdk.EventMask.PROXIMITY_OUT_MASK | Accepts events emitted when the mouse cursor leaves the proximity of the widget. |
Gdk.EventMask.SUBSTRUCTURE_MASK | Accepts events that change the configuration of child windows. |
Gdk.EventMask.SCROLL_MASK | Accepts all scroll events. |
Gdk.EventMask.ALL_EVENTS_MASK | Accepts all types of events. |
You must call eventbox.set_events() before you call eventbox.realize() on the widget. If a widget has already been realized by GTK+, you have to instead use eventbox.add_events() to add event masks.
Before calling eventbox.realize(), your Gtk.EventBox does not yet have an associated Gdk.Window or any other GDK widget resources. Normally, realization occurs when the parent is realized, but event boxes are an exception. When you call window.show() on a widget, it is automatically realized by GTK+. Event boxes are not realized when you call window.show_all(), because they are set as invisible. Calling eventbox.realize() on the event box is an easy way to work around this problem.
When you realize your event box, you need to make sure that it is already added as a child to a top-level widget, or it will not work. This is because, when you realize a widget, it automatically realizes its ancestors. If it has no ancestors, GTK+ is not happy, and realization fails.
After the event box is realized, it has an associated Gdk.Window. Gdk.Window is a class that refers to a rectangular region on the screen where a widget is drawn. It is not the same thing as a Gtk.Window, which refers to a top-level window with a title bar and so on. A Gtk.Window contains many Gdk.Window objects, one for each child widget. They are used for drawing widgets on the screen.
Test Your Understanding
This chapter has introduced you to a number of container widgets that are included in GTK+. The following two exercises allow you to practice what you have learned about a few of these new widgets.
Exercise 1: Using Multiple Containers
One important characteristic of containers is that each container can hold other containers. To really drive this point home, in this example, you use a large number of containers. The main window shows a Gtk.Notebook and two buttons along the bottom.
The notebook should have four pages. Each notebook page should hold a Gtk.Button that moves to the next page (the Gtk.Button on the last page should wrap around to the first page).
Create two buttons along the bottom of the window. The first should move to the previous page in the Gtk.Notebook, wrapping to the last page if necessary. The second button should close the window and exit the application when clicked.
Exercise 1 is a simple application to implement, but it illustrates a few important points. First, it shows the usefulness of Gtk.Box, and how vertical and horizontal boxes can be used together to create complex user interfaces.
It is true that this same application could be implemented with a Gtk.Grid as the direct child of the window, but it is significantly easier to align the buttons along the bottom with a horizontal box. You notice that the buttons were packed at the end of the box, which aligns them to the right side of the box, and this is easier to implement with boxes.
Also, you saw that containers can, and should, be used to hold other containers. For example, in Exercise 1, a Gtk.Window holds a vertical Gtk.Box, which holds a horizontal Gtk.Box and a Gtk.Notebook. This structure can become even more complex as your application grows in size.
Once you have completed Exercise 1, move on to Exercise 2. In the next problem, you use the paned container instead of a vertical box.
Exercise 2: Even More Containers
In this exercise, you expand upon the code you wrote in Exercise 1. Instead of using a vertical Gtk.Box to hold the notebook and horizontal box of buttons, create a vertical Gtk.Paned widget.
In addition to this change, you should hide the Gtk.Notebook tabs, so the user is not able to switch between pages without pressing buttons. In this case, you not be able to know when a page is being changed. Therefore, each button that is in a Gtk.Notebook page should be contained by its own expander. The expander labels allow you to differentiate between notebook pages.
Once you have completed Exercise 2, you will have had practice with Gtk.Box, Gtk.Paned, Gtk.Notebook, and Gtk.Expander—four important containers used throughout the rest of this book.
Before continuing on to the next chapter, you may want to test out a few of the containers covered in this chapter that you did not need for Exercises 1 and 2. This gives you practice using all of the containers, because later chapters do not review past information.
Summary
In this chapter, you learned about the two types of container widgets: decorators and layout containers. Types of decorators covered were expanders, and event boxes. Types of layout containers covered were boxes, panes, grids, fixed containers, and notebooks.
The event box container is seen in later chapters, because there are other widgets besides Gtk.Label that cannot handle GDK events. This is specified when you learn about these widgets. You will see most of the containers in later chapters as well.
While these containers are necessary for GTK+ application development, merely displaying Gtk.Label and Gtk.Button widgets in containers is not very useful (or interesting) in most applications. This type of application does little to accommodate anything beyond basic user interaction.
Therefore, in the next chapter, you are going to learn about many widgets that allow you to interact with the user. These widgets include types of buttons, toggles, text entries, and spin buttons.