© W. David Ashley and Andrew Krause 2019
W. David Ashley and Andrew KrauseFoundations of PyGTK Developmenthttps://doi.org/10.1007/978-1-4842-4179-0_4

4. Containers

W. David Ashley1  and Andrew Krause2
(1)
AUSTIN, TX, USA
(2)
Leesburg, VA, USA
 

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 following topics are covered.
  • 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.

The Gtk.Bin only exposes one method, get_child. The only purpose of the Gtk.Bin class is to provide an instantiable widget from which all subclasses that only require one child widget can be derived. It is a central class for common base.
binwin = Gtk.Bin()

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

The Gtk.Container class currently provides four signals. These are "add", "check_resize", "remove", and "set_focus_child".
  • "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.

The graphical output of the application is shown in Listing 4-1. Notice that the names are shown in the same order as they were added to the array, even though each was packed at the start position. Notice that the names are shown in the same order as they were added to the array, even though each was packed at the start position.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
names = ["Andrew", "Joe", "Samantha", "Jonathan"]
class AppWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
    for name in names:
        button = Gtk.Button.new_with_label(name)
        vbox.pack_start(button, True, True, 0)
        button.connect("clicked", self.on_button_clicked)
        button.set_relief(Gtk.ReliefStyle.NORMAL)
    self.set_border_width(10)
    self.set_size_request(200, -1)
    self.add(vbox)
    self.show_all()
def on_button_clicked(self, widget):
    self.destroy()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Boxes")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 4-1

Vertical Boxes with Default Packing

Figure 4-1 shows the result of running Listing 4-1.
../images/142357_2_En_4_Chapter/142357_2_En_4_Fig1_HTML.jpg
Figure 4-1

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.

As with every widget, you need to initialize Gtk.Box before using the object. All the parameters that are passed are keyword parameters. The default orientation if no keyword "orientation" is passed the default is Gtk.Orientation.HORIZONTAL. Other keywords are available, such as "spacing". If the "homogeneous" keyword is set to True, all of the children are given the smallest amount of space that can fit every widget.
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)

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.

By adding widgets to the box with pack_start(), the child has three properties automatically set. Expanding is set to True, which automatically provides the cell with the extra space allocated to the box. This space is distributed evenly to all of the cells that request it. The fill property is also set to True, which means the widget expands into all of the extra space provided instead of filling it with padding. Lastly, the amount of padding placed between the cell and its neighbors is set to zero pixels.
vbox.pack_start(button, True, True, 0)

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.

It should also be noted that the pack_start() and pack_end() methods not only specify the packing parameters, they also add the widget to the specified widget instance. It is not necessary to call the add() method to add the widget if you call one of packing methods. In fact, it is a runtime error if you attempt to add the same widget with a packing method and the add() method.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
names = ["Andrew", "Joe", "Samantha", "Jonathan"]
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        for name in names:
            button = Gtk.Button.new_with_label(name)
            vbox.pack_end(button, False, False, 5)
            button.connect("clicked", self.on_button_clicked)
            button.set_relief(Gtk.ReliefStyle.NORMAL)
        self.set_border_width(10)
        self.set_size_request(200, -1)
        self.add(vbox)
        self.show_all()
    def on_button_clicked(self, widget):
        self.destroy()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Boxes")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 4-2

Vertical_Boxes Specifying Packing Parameters

Since we packed each of the widgets starting at the end, they are shown in reverse order in Figure 4-2). The packing began at the end of the box and packed each child before the previous one. You are free to intersperse calls to start and end packing functions. GTK+ keeps track of both reference positions. Since we packed each of the widgets starting at the end, they are shown in reverse order. The packing began at the end of the box and packed each child before the previous one. You are free to intersperse calls to start and end packing functions. GTK+ keeps track of both reference positions.
../images/142357_2_En_4_Chapter/142357_2_En_4_Fig2_HTML.jpg
Figure 4-2

Vertical_Boxes specifying packing parameters

By setting the expand property to True, the cell expands so that it takes up additional space allocated to the box that is not needed by the widgets. By setting the fill property to True, the widget expands to fill extra space available to the cell. Table 4-1 offers a brief description of all possible combinations of the expand and fill properties.
Table 4-1

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.

In the previous pack_end() call, each cell is told to place five pixels of spacing between itself and any neighbor cells. Also, according to Table 4-1 neither the cell nor its child widget expand to take up additional space provided to the box.
vbox.pack_end(button, True, True, 0)

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+.

While you should try to finalize the order of elements in a Gtk.Boxwidget before displaying it to the user, it is possible to reorder child widgets in a box with reorder_child().
vbox.reorder_child(child_widget, position)

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.

There are two types of paned widgets: horizontal resizing and vertical resizing. As with boxes, Gtk.Paned provides all the functions for both horizontal and vertical panes. Listing 4-3 shows a simple example where two Gtk.Button widgets are placed as the children of a horizontal pane.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        hpaned = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        button1 = Gtk.Button.new_with_label("Resize")
        button2 = Gtk.Button.new_with_label("Me!")
        button1.connect("clicked", self.on_button_clicked)
        button2.connect("clicked", self.on_button_clicked)
        hpaned.add1(button1)
        hpaned.add2(button2)
        self.add(hpaned)
        self.set_size_request(225, 150)
        self.show_all()
    def on_button_clicked(self, button):
        self.destroy()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Panes")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 4-3

Horizontal Paned with Buttons

As you can see in Figure 4-3 the Gtk.Paned widget places a vertical bar between its two children. By dragging the bar, one widget shrinks while the other expands. In fact, it is possible to move the bar so that one child is completely hidden from the user’s view. You learn how to prevent this with the pack1() and pack2() methods.
../images/142357_2_En_4_Chapter/142357_2_En_4_Fig3_HTML.jpg
Figure 4-3

Horizontal paned with buttons

In Figure 4-3 we created a Gtk.Paned object with the following.
hpaned = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
If you want to use a vertical paned widget instead, you need only to call the following.
vpaned = Gtk.Paned.new(Gtk.Orientation.VERTICAL)

All of the Gtk.Paned functions then work with either type of paned widget.

Since Gtk.Paned can only handle two children, GTK+ provides a function for packing each child. In the following example, pack1() and pack2() methods were used to add both children to Gtk.Paned. These functions use the default values for the resize and shrink properties of the Gtk.Paned widget.
hpaned.add1(button1);
hpaned.add2(button2);
The preceding add1() and add2() method calls are from Listing 4-3 and are equivalent to the following.
hpaned.pack1(label1, False, True);
hpaned.pack2(label2, True, True);

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.

The last parameter specifies whether the child can be made smaller than its size requisition. In most cases, you want to set this to True so that a widget can be completely hidden by the user by dragging the resize bar. If you want to prevent the user from doing this, set the third parameter to False. Table 4-2 illustrates how the resize and shrink properties interrelate.
Table 4-2

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.

You can easily set the exact position of the resize bar with set_position(). The position is calculated in pixels with respect to the top or left side of the container. If you set the position of the bar to zero, it is moved all the way to the top or left if the widget allows shrinking.
paned.set_position(position)
Most applications want to remember the position of the resize bar, so it can be restored to the same location when the user next loads the application. The current position of the resize bar can be retrieved with get_position().
pos = paned.get_position()

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.

Figure 4-4 shows a simple grid that contains three widgets. Notice that the single label spans two columns. This illustrates the fact that grids allow one widget to span multiple columns and/or rows as long as the region is rectangular.
../images/142357_2_En_4_Chapter/142357_2_En_4_Fig4_HTML.jpg
Figure 4-4

Grid displaying name

Listing 4-4 inserts two Gtk.Label widgets and a Gtk.Entry widget into the two-by-two area (you learn how to use the Gtk.Entry widget in Chapter 5, but this gives you a taste of what is to come).
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(150, 100)
        grid = Gtk.Grid.new()
        label1 = Gtk.Label.new("Enter the following information ...")
        label2 = Gtk.Label.new("Name: ")
        entry = Gtk.Entry.new()
        grid.attach(label1, 0, 0, 2, 1)
        grid.attach(label2, 0, 1, 1, 1)
        grid.attach(entry, 1, 1, 1, 1)
        grid.set_row_spacing(5)
        grid.set_column_spacing(5)
        self.add(grid)
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Tables")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 4-4

Grids Displaying Name

Grid Spacing

If you want to set the spacing for every column in a grid, you can use set_column_spacing(). This function was used in set_row_spacing() to add padding between rows. These functions override any previous settings of the grid.set_row_spacing() to add padding between rows. These functions override any previous settings of the grid.
grid.set_column_spacing(5)
The grid.attach() method require five parameters, as follows.
Grid.attach(child_widget, left_pos, top_pos, width, height)

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.

Listing 4-5 shows the Gtk.Fixed widget that contains two buttons, one found at each of the locations (0,0) and (20,30), with respect to the top-left corner of the widget.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        fixed = Gtk.Fixed.new()
        button1 = Gtk.Button.new_with_label("Pixel by pixel ...")
        button2 = Gtk.Button.new_with_label("you choose my fate.")
        button1.connect("clicked", self.on_button_clicked)
        button2.connect("clicked", self.on_button_clicked)
        fixed.put(button1, 0, 0)
        fixed.put(button2, 22, 35)
        self.add(fixed)
        self.show_all()
    def on_button_clicked(self, widget):
        self.destroy()
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Fixed")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 4-5

Specifying Exact Locations

The Gtk.Fixed widget initialized with Gtk.Fixed.new() allows you to place widgets with a specific size in a specific location. Placing widgets is performed with put() at specified horizontal and vertical positions.
fixed.put(child, x, y)

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.

If you need to move a widget after it has been placed within a The Gtk.Fixed container, you can use move(). You need to be careful not to overlap a widget that has already been placed. The Gtk.Fixed widget does not provide notification in the case of overlap. Instead, it tries to render the window with unpredictable results.
fixed.move(child, x, y)
This brings us to the inherent problems with using the Gtk.Fixed widget. The first problem is that your users are free to use whatever theme they want. This means that the size of text on the user’s machine may differ from the size of text on your machine unless you explicitly set the font. The sizes of widgets vary among different user themes as well. This can cause misalignment and overlap. This is illustrated in Figure 4-5, which shows two screenshots, one with a small font size and one with a larger font size.
../images/142357_2_En_4_Chapter/142357_2_En_4_Fig5_HTML.jpg
Figure 4-5

Problems caused by different font sizes in a Gtk.Fixed container

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

The Gtk.Expander container can handle only one child. The child can be shown or hidden by clicking the triangle to the left of the expander’s label. A before-and-after screenshot of this action can be viewed in Figure 4-6.
../images/142357_2_En_4_Chapter/142357_2_En_4_Fig6_HTML.jpg
Figure 4-6

A Gtk.Expander container

Listing 4-6 introduces you to the most important Gtk.Expander methods.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(200, 100)
        expander = Gtk.Expander.new_with_mnemonic("Click _Me For More!")
        label = Gtk.Label.new ("Hide me or show me, that is your choice.")
        expander.add(label)
        expander.set_expanded(True)
        self.add(expander)
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Hello World!")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 4-6

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.

The child widget of a Gtk.Expander container can be shown or hidden by calling expander.set_expanded().expander.set_expanded().
expander.set_expanded(boolean)
By default, GTK+ does not add any spacing between the expander label and the child widget. To add pixels of spacing, you can use expander.set_spacing() to add padding.
expander.set_spacing(spacing)

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.

You are able to specify the location of the tabs, although they appear along the top by default. You can also hide the tabs altogether. Figure 4-7 shows a Gtk.Notebook widget with two tabs that was created with the code in Listing 4-7.
../images/142357_2_En_4_Chapter/142357_2_En_4_Fig7_HTML.jpg
Figure 4-7

A notebook container with multiple pages

When creating a notebook container, you must specify a tab label widget and a child widget for each tab. Tabs can be added to the front or back, inserted, reordered, and removed.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(250, 100)
        notebook = Gtk.Notebook.new()
        label1 = Gtk.Label.new("Page 1")
        label2 = Gtk.Label.new("Page 2")
        child1 = Gtk.Label.new("Go to page 2 to find the answer.")
        child2 = Gtk.Label.new("Go to page 1 to find the answer.")
        notebook.append_page(child1, label1)
        notebook.append_page(child2, label2)
        notebook.set_tab_pos(Gtk.PositionType.BOTTOM)
        self.add(notebook)
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Notebook")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 4-7

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.

If you want to insert a tab in a specific location, you can use notebook.insert_page(). This function allows you to specify the integer location of the tab. The index of all tabs located after the inserted tab increase by one.
notebook.insert_page (child, tab_label, position)

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

In Listing 4-7, the tab-position property was set for the Gtk.Notebook, which was done with the following call.
notebook.set_tab_pos(position)

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.

Notebooks are useful if you want to give the user multiple options, but you want to show them in multiple stages. If you place a few in each tab and hide the tabs with notebook.set_show_tabs(), you can progress the user back and forth through the options. An example of this concept would be many of the wizards you see throughout your operating system, similar to the functionality provided by the Gtk.Assistant widget.
notebook.set_show_tabs(show_tabs)
At some point, the Gtk.Notebook runs out of room to store tabs in the allocated space. To remedy this problem, you can set notebook tabs as scrollable with notebook.set_scrollable().
notebook.set_scrollable(scrollable)

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.

If you can add tabs, there has to be a method to remove tabs as well. By using notebook.remove_page(), you can remove a tab based on its index reference. If you did not increase the reference count before adding the widget to the Gtk.Notebook, this function releases the last reference and destroys the child.
notebook.remove_page(page_number)
You can manually reorder the tabs by calling notebook.reorder_child(). You must specify the child widget of the page you want to move and the location to where it should be moved. If you specify a number that is greater than the number of tabs or a negative number, the tab is moved to the end of the list.
notebook.reorder_child(child, position)
There are three methods provided for changing the current page. If you know the specific index of the page you want to view, you can use notebook.set_current_page() to move to that page.
notebook.set_current_page(page_number)

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.

Listing 4-8 captures the button-press-event signal by using an event box. The text in the label is changed based on its current state when the label is double-clicked. Nothing visible happens when a single click occurs, although the signal is still emitted in that case (Gtk.Label) by using an event box. The text in the label is changed based on its current state when the label is double-clicked. Nothing visible happens when a single click occurs, although the signal is still emitted in that case.
#!/usr/bin/python3
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_border_width(10)
        self.set_size_request(200, 50)
        eventbox = Gtk.EventBox.new()
        label = Gtk.Label.new("Double-Click Me!")
        eventbox.set_above_child(False)
        eventbox.connect("button_press_event", self.on_button_pressed, label)
        eventbox.add(label)
        self.add(eventbox)
        eventbox.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
        eventbox.realize()
    def on_button_pressed(self, eventbox, event, label):
        if event.type == Gdk.EventType._2BUTTON_PRESS:
            text = label.get_text()
            if text[0] == 'D':
                label.set_text("I Was Double-Clicked!")
            else:
                label.set_text("Double-Click Me Again!")
        return False
class Application(Gtk.Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, application_id="org.example.myapp",
                         **kwargs)
        self.window = None
    def do_activate(self):
        if not self.window:
            self.window = AppWindow(application=self, title="Hello World!")
        self.window.show_all()
        self.window.present()
if __name__ == "__main__":
    app = Application()
    app.run(sys.argv)
Listing 4-8

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.

The location of the event box window can be moved above or below its children with eventbox.set_above_child(). By default, this property is set to False for all event boxes. This means that all events are handled by the widget for which the signal was first emitted. The event is then passed to its parent after the widget is finished.
eventbox.set_above_child(above_child)
Next, you need to add an event mask to the event box so that it knows what type of events the widget receives. Values for the Gdk.EventMask enumeration that specify event masks are shown in Table 4-3. A bitwise list of Gdk.EventMask values can be passed to eventbox.set_events() if you need to set more than one.
Table 4-3

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.249.198