Layout Containers - PyGObject

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
View this page
Toggle table of contents sidebar

# Layout Containers

While many GUI toolkits require you to precisely place widgets in a window, using absolute positioning, GTK uses a different approach. Rather than specifying the position and size of each widget in the window, you can arrange your widgets in rows, columns, and/or tables. The size of your window can be determined automatically, based on the sizes of the widgets it contains. And the sizes of the widgets are, in turn, determined by the amount of text they contain, or the minimum and maximum sizes that you specify, and/or how you have requested that the available space should be shared between sets of widgets. You can perfect your layout by specifying padding distance and centering values for each of your widgets. GTK then uses all this information to resize and reposition everything sensibly and smoothly when the user manipulates the window.

GTK widgets can parent other widgets hierarchically, and the way those widgets are displayed depend on the `Gtk.LayoutManager` set in the `Gtk.Widget.props.layout_manager` property. For ease of use, GTK provides “pre made” containers that have default layout managers for different needs. Most of these containers implement the `Gtk.Orientable` interface, it allows to set the widget orientation using `Gtk.Orientable.props.orientation`, you can either set `Gtk.Orientation.HORIZONTAL` or `Gtk.Orientation.VERTICAL`.

Some containers / layout managers like `Gtk.Box` allow you to influence the allocation of a child using the `Gtk.Widget.props.halign`, `Gtk.Widget.props.valign`, `Gtk.Widget.props.hexpand` and `Gtk.Widget.props.vexpand` properties.

The main container widgets are:

- `Gtk.Box`

- `Gtk.CenterBox`

- `Gtk.HeaderBar`

- `Gtk.Grid`

- `Gtk.ListBox`

- `Gtk.FlowBox`

- `Gtk.Stack`

- `Gtk.Notebook`

We are looking into each one in the following sections.

## Boxes

Boxes are invisible containers into which we can pack our widgets. When packing widgets into a horizontal box, the objects are inserted horizontally from left to right or right to left depending on whether `Gtk.Box.prepend()` or `Gtk.Box.append()` is used. You can also use the `Gtk.Box.insert_child_after()` or `Gtk.Box.reorder_child_after()` methods to insert a widget on a specific position. In a vertical box, widgets are packed from top to bottom or vice versa. You may use any combination of boxes inside or beside other boxes to create the desired effect.

### Example

Let’s take a look at a slightly modified version of the Extended Example with two buttons.

     1import gi
     2
     3gi.require_version('Gtk', '4.0')
     4from gi.repository import Gtk
     5
     6
     7class MyWindow(Gtk.ApplicationWindow):
     8    def __init__(self, **kargs):
     9        super().__init__(**kargs, title='Hello World')
    10
    11        box = Gtk.Box(spacing=6)
    12        self.set_child(box)
    13
    14        button1 = Gtk.Button(label='Hello')
    15        button1.connect('clicked', self.on_button1_clicked)
    16        box.append(button1)
    17
    18        button2 = Gtk.Button(label='Goodbye')
    19        button2.props.hexpand = True
    20        button2.connect('clicked', self.on_button2_clicked)
    21        box.append(button2)
    22
    23    def on_button1_clicked(self, _widget):
    24        print('Hello')
    25
    26    def on_button2_clicked(self, _widget):
    27        print('Goodbye')
    28
    29
    30def on_activate(app):
    31    # Create window
    32    win = MyWindow(application=app)
    33    win.present()
    34
    35
    36app = Gtk.Application(application_id='com.example.App')
    37app.connect('activate', on_activate)
    38
    39app.run(None)

First, we create a horizontally orientated box container where 6 pixels are placed between children. This box becomes the child of the top-level window.

            box = Gtk.Box(spacing=6)
            self.set_child(box)

Subsequently, we add two different buttons to the box container.

            button1 = Gtk.Button(label='Hello')
            button1.connect('clicked', self.on_button1_clicked)
            box.append(button1)

            button2 = Gtk.Button(label='Goodbye')
            button2.props.hexpand = True
            button2.connect('clicked', self.on_button2_clicked)
            box.append(button2)

## CenterBox

`Gtk.CenterBox` arranges three children in a row or a column depending on its orientation, keeping the middle child centered as well as possible.

To add children you use `Gtk.CenterBox.set_start_widget()`, `Gtk.CenterBox.set_center_widget()`, and `Gtk.CenterBox.set_end_widget()`.

### Example

     1import gi
     2
     3gi.require_version('Gtk', '4.0')
     4from gi.repository import Gtk
     5
     6
     7class CenterBoxWindow(Gtk.ApplicationWindow):
     8    def __init__(self, **kargs):
     9        super().__init__(**kargs, default_width=400, title='CenterBox Example')
    10
    11        box = Gtk.CenterBox()
    12        self.set_child(box)
    13
    14        button1 = Gtk.Button(label='Start')
    15        box.set_start_widget(button1)
    16
    17        label = Gtk.Label(label='Center')
    18        box.set_center_widget(label)
    19
    20        button2 = Gtk.Button(label='End')
    21        box.set_end_widget(button2)
    22
    23
    24def on_activate(app):
    25    # Create window
    26    win = CenterBoxWindow(application=app)
    27    win.present()
    28
    29
    30app = Gtk.Application(application_id='com.example.App')
    31app.connect('activate', on_activate)
    32
    33app.run(None)

## HeaderBar

A `Gtk.HeaderBar` is similar to a horizontal `Gtk.CenterBox`, it allows to place children at the start or the end. In addition, it allows a title to be displayed. The title will be centered with respect to the width of the box, even if the children at either side take up different amounts of space.

`Gtk.HeaderBar` is designed to be used as a window titlebar. This means that it can show typical window frame controls, such as minimize, maximize and close buttons, or the window icon. It is also draggable, meaning that you can move the parent window from it. You can use the `Gtk.Window.set_titlebar()` method to set it as so.

To add children you use `Gtk.HeaderBar.pack_start()`, `Gtk.HeaderBar.pack_end()`, and `Gtk.HeaderBar.set_title_widget()` for the center one. By default if no `title_widget` is set it will have a label with the window’s `Gtk.Window.props.title`.

### Example

     1import gi
     2
     3gi.require_version('Gtk', '4.0')
     4from gi.repository import Gtk
     5
     6
     7class HeaderBarWindow(Gtk.ApplicationWindow):
     8    def __init__(self, **kargs):
     9        super().__init__(**kargs, default_width=400, title='HeaderBar Example')
    10
    11        header_bar = Gtk.HeaderBar()
    12        self.set_titlebar(header_bar)
    13
    14        button = Gtk.Button(label='Button')
    15        header_bar.pack_start(button)
    16
    17        icon_button = Gtk.Button(icon_name='open-menu-symbolic')
    18        header_bar.pack_end(icon_button)
    19
    20
    21def on_activate(app):
    22    # Create window
    23    win = HeaderBarWindow(application=app)
    24    win.present()
    25
    26
    27app = Gtk.Application(application_id='com.example.App')
    28app.connect('activate', on_activate)
    29
    30app.run(None)

## Grid

`Gtk.Grid` is a container which arranges its child widgets in rows and columns, but you do not need to specify the dimensions in the constructor. Children are added using `Gtk.Grid.attach()`. They can span multiple rows or columns. The `Gtk.Grid.attach()` method takes five parameters:

1. The `child` parameter is the `Gtk.Widget` to add.

2. `left` is the column number to attach the left side of `child` to.

3. `top` indicates the row number to attach the top side of `child` to.

4. `width` and `height` indicate the number of columns that the `child` will span, and the number of rows that the `child` will span, respectively.

It is also possible to add a child next to an existing child, using `Gtk.Grid.attach_next_to()`, which also takes five parameters:

1. `child` is the `Gtk.Widget` to add, as above.

2. `sibling` is an existing child widget of `self` (a `Gtk.Grid` instance) or `None`. The `child` widget will be placed next to `sibling`, or if `sibling` is `None`, at the beginning or end of the grid.

3. `side` is a `Gtk.PositionType` indicating the side of `sibling` that `child` is positioned next to.

4. `width` and `height` indicate the number of columns and rows the `child` widget will span, respectively.

### Example

     1import gi
     2
     3gi.require_version('Gtk', '4.0')
     4from gi.repository import Gtk
     5
     6
     7class GridWindow(Gtk.ApplicationWindow):
     8    def __init__(self, **kargs):
     9        super().__init__(**kargs, title='Grid Example')
    10
    11        button1 = Gtk.Button(label='Button 1')
    12        button2 = Gtk.Button(label='Button 2')
    13        button3 = Gtk.Button(label='Button 3')
    14        button4 = Gtk.Button(label='Button 4')
    15        button5 = Gtk.Button(label='Button 5')
    16        button6 = Gtk.Button(label='Button 6')
    17
    18        grid = Gtk.Grid()
    19        grid.attach(button1, 0, 0, 1, 1)
    20        grid.attach(button2, 1, 0, 2, 1)
    21        grid.attach_next_to(button3, button1, Gtk.PositionType.BOTTOM, 1, 2)
    22        grid.attach_next_to(button4, button3, Gtk.PositionType.RIGHT, 2, 1)
    23        grid.attach(button5, 1, 2, 1, 1)
    24        grid.attach_next_to(button6, button5, Gtk.PositionType.RIGHT, 1, 1)
    25
    26        self.set_child(grid)
    27
    28
    29def on_activate(app):
    30    # Create window
    31    win = GridWindow(application=app)
    32    win.present()
    33
    34
    35app = Gtk.Application(application_id='com.example.App')
    36app.connect('activate', on_activate)
    37
    38app.run(None)

## ListBox

A `Gtk.ListBox` is a vertical container that contains `Gtk.ListBoxRow` children. These rows can be dynamically sorted and filtered, and headers can be added dynamically depending on the row content. It also allows keyboard and mouse navigation and selection like a typical list.

Although a `Gtk.ListBox` must have only `Gtk.ListBoxRow` children, you can add any kind of widget to it via `Gtk.ListBox.append()` or `Gtk.ListBox.insert()` and a `Gtk.ListBoxRow` widget will automatically be inserted between the list and the widget.

`Gtk.ListBox` allows activating and selecting its children. Selection can be configured with `Gtk.ListBox.props.selection_mode`, selection modes are `Gtk.SelectionMode.NONE` (not selection at all), `Gtk.SelectionMode.SINGLE` (one or none elements can be selected), `Gtk.SelectionMode.BROWSE` (user can’t deselect a currently selected element except by selecting another element) and `Gtk.SelectionMode.MULTIPLE` (any number of elements may be selected).

It will emit the `row-activated` signal when a row is activated in any form, and `row-selected` when a row is selected.

### Example

      1import gi
      2
      3gi.require_version('Gtk', '4.0')
      4from gi.repository import Gtk
      5
      6
      7class ListBoxRowWithData(Gtk.ListBoxRow):
      8    def __init__(self, data):
      9        super().__init__()
     10        self.data = data
     11        self.set_child(Gtk.Label(label=data))
     12
     13
     14class ListBoxWindow(Gtk.ApplicationWindow):
     15    def __init__(self, **kargs):
     16        super().__init__(**kargs, default_width=400, title='ListBox Demo')
     17
     18        # Main box of out window
     19        box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
     20        box_outer.props.margin_start = 24
     21        box_outer.props.margin_end = 24
     22        box_outer.props.margin_top = 24
     23        box_outer.props.margin_bottom = 24
     24        self.set_child(box_outer)
     25
     26        # Let's create our first ListBox
     27        listbox = Gtk.ListBox()
     28        listbox.props.selection_mode = Gtk.SelectionMode.NONE
     29        listbox.props.show_separators = True
     30        box_outer.append(listbox)
     31
     32        # Let's create our first ListBoxRow
     33        row = Gtk.ListBoxRow()
     34        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=24)
     35        row.set_child(hbox)  # We set the Box as the ListBoxRow child
     36        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
     37        hbox.append(vbox)
     38        label1 = Gtk.Label(label='Automatic Date & Time', xalign=0)
     39        label2 = Gtk.Label(label='Requires internet access', xalign=0)
     40        vbox.append(label1)
     41        vbox.append(label2)
     42
     43        switch = Gtk.Switch()
     44        switch.props.hexpand = (
     45            True  # Lets make the Switch expand to the window width
     46        )
     47        switch.props.halign = Gtk.Align.END  # Horizontally aligned to the end
     48        switch.props.valign = (
     49            Gtk.Align.CENTER
     50        )  # Vertically aligned to the center
     51        hbox.append(switch)
     52
     53        listbox.append(row)  # Add the row to the list
     54
     55        # Our second row. We will omit the ListBoxRow and directly append a Box
     56        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=24)
     57        label = Gtk.Label(label='Enable Automatic Update', xalign=0)
     58        check = Gtk.CheckButton()
     59        check.props.hexpand = True
     60        check.props.halign = Gtk.Align.END
     61        hbox.append(label)
     62        hbox.append(check)
     63        listbox.append(hbox)  # Add the second row to the list
     64
     65        # Let's create a second ListBox
     66        listbox_2 = Gtk.ListBox()
     67        box_outer.append(listbox_2)
     68        items = 'This is a sorted ListBox Fail'.split()
     69
     70        # Populate the list
     71        for item in items:
     72            listbox_2.append(ListBoxRowWithData(item))
     73
     74        # Set sorting and filter functions
     75        listbox_2.set_sort_func(self.sort_func)
     76        listbox_2.set_filter_func(self.filter_func)
     77
     78        # Connect to "row-activated" signal
     79        listbox_2.connect('row-activated', self.on_row_activated)
     80
     81    def sort_func(self, row_1, row_2):
     82        return row_1.data.lower() > row_2.data.lower()
     83
     84    def filter_func(self, row):
     85        return False if row.data == 'Fail' else True
     86
     87    def on_row_activated(self, _listbox, row):
     88        print(row.data)
     89
     90
     91def on_activate(app):
     92    # Create window
     93    win = ListBoxWindow(application=app)
     94    win.present()
     95
     96
     97app = Gtk.Application(application_id='com.example.App')
     98app.connect('activate', on_activate)
     99
    100app.run(None)

## FlowBox

A `Gtk.FlowBox` is a container that positions child widgets in sequence according to its orientation.

For instance, with the horizontal orientation, the widgets will be arranged from left to right, starting a new row under the previous row when necessary. Reducing the width in this case will require more rows, so a larger height will be requested.

Likewise, with the vertical orientation, the widgets will be arranged from top to bottom, starting a new column to the right when necessary. Reducing the height will require more columns, so a larger width will be requested.

`Gtk.FlowBox` behaves similar to `Gtk.ListBox`, we could say that is its “grid” counterpart. Just like `Gtk.ListBox`, the children of a `Gtk.FlowBox` can be dynamically sorted and filtered, and also activated and selected.

Although a `Gtk.FlowBox` must have only `Gtk.FlowBoxChild` children, you can add any kind of widget to it via `Gtk.FlowBox.append()` or `Gtk.FlowBox.insert()`, and a `Gtk.FlowBoxChild` widget will automatically be inserted between the box and the widget.

### Example

      1import gi
      2
      3gi.require_version('Gtk', '4.0')
      4from gi.repository import Gtk, Gdk
      5
      6
      7class FlowBoxWindow(Gtk.ApplicationWindow):
      8    def __init__(self, **kargs):
      9        super().__init__(**kargs, title='FlowBox Demo')
     10
     11        self.set_default_size(300, 250)
     12
     13        header = Gtk.HeaderBar()
     14        self.set_titlebar(header)
     15
     16        scrolled = Gtk.ScrolledWindow()
     17        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
     18        self.set_child(scrolled)
     19
     20        flowbox = Gtk.FlowBox()
     21        flowbox.props.valign = Gtk.Align.START
     22        flowbox.props.max_children_per_line = 30
     23        flowbox.props.selection_mode = Gtk.SelectionMode.NONE
     24        scrolled.set_child(flowbox)
     25
     26        self.create_flowbox(flowbox)
     27
     28    def draw_button_color(self, area, cr, width, height, rgba):
     29        context = area.get_style_context()
     30
     31        Gtk.render_background(context, cr, 0, 0, width, height)
     32
     33        cr.set_source_rgba(rgba.red, rgba.green, rgba.blue, rgba.alpha)
     34        cr.rectangle(0, 0, width, height)
     35        cr.fill()
     36
     37    def color_swatch_new(self, str_color):
     38        rgba = Gdk.RGBA()
     39        rgba.parse(str_color)
     40
     41        button = Gtk.Button()
     42
     43        area = Gtk.DrawingArea()
     44        area.set_size_request(24, 24)
     45        area.set_draw_func(self.draw_button_color, rgba)
     46
     47        button.set_child(area)
     48
     49        return button
     50
     51    def create_flowbox(self, flowbox):
     52        colors = [
     53            'AliceBlue',
     54            'AntiqueWhite',
     55            'AntiqueWhite1',
     56            'AntiqueWhite2',
     57            'AntiqueWhite3',
     58            'AntiqueWhite4',
     59            'aqua',
     60            'aquamarine',
     61            'aquamarine1',
     62            'aquamarine2',
     63            'aquamarine3',
     64            'aquamarine4',
     65            'azure',
     66            'azure1',
     67            'azure2',
     68            'azure3',
     69            'azure4',
     70            'beige',
     71            'bisque',
     72            'bisque1',
     73            'bisque2',
     74            'bisque3',
     75            'bisque4',
     76            'black',
     77            'BlanchedAlmond',
     78            'blue',
     79            'blue1',
     80            'blue2',
     81            'blue3',
     82            'blue4',
     83            'BlueViolet',
     84            'brown',
     85            'brown1',
     86            'brown2',
     87            'brown3',
     88            'brown4',
     89            'burlywood',
     90            'burlywood1',
     91            'burlywood2',
     92            'burlywood3',
     93            'burlywood4',
     94            'CadetBlue',
     95            'CadetBlue1',
     96            'CadetBlue2',
     97            'CadetBlue3',
     98            'CadetBlue4',
     99            'chartreuse',
    100            'chartreuse1',
    101            'chartreuse2',
    102            'chartreuse3',
    103            'chartreuse4',
    104            'chocolate',
    105            'chocolate1',
    106            'chocolate2',
    107            'chocolate3',
    108            'chocolate4',
    109            'coral',
    110            'coral1',
    111            'coral2',
    112            'coral3',
    113            'coral4',
    114        ]
    115
    116        for color in colors:
    117            button = self.color_swatch_new(color)
    118            button.props.tooltip_text = color
    119            flowbox.append(button)
    120
    121
    122def on_activate(app):
    123    # Create window
    124    win = FlowBoxWindow(application=app)
    125    win.present()
    126
    127
    128app = Gtk.Application(application_id='com.example.App')
    129app.connect('activate', on_activate)
    130
    131app.run(None)

## Stack and StackSwitcher

A `Gtk.Stack` is a container which only shows one of its children at a time. In contrast to `Gtk.Notebook`, `Gtk.Stack` does not provide a means for users to change the visible child. Instead, the `Gtk.StackSwitcher` widget can be used with `Gtk.Stack` to provide this functionality.

Transitions between pages can be animated as slides or fades. This can be controlled with `Gtk.Stack.props.transition_type`. These animations respect the `gtk-enable-animations` setting.

Transition speed can be adjusted with `Gtk.Stack.props.transition_duration`.

The `Gtk.StackSwitcher` widget acts as a controller for a `Gtk.Stack`; it shows a row of buttons to switch between the various pages of the associated stack widget.

`Gtk.Stack` uses the auxiliary `Gtk.StackPage` object. `Gtk.Stack` will return a `Gtk.StackPage` every time you add a new children, either with `Gtk.Stack.add_child()`, `Gtk.Stack.add_named()` or `Gtk.Stack.add_titled()`. `Gtk.StackPage` holds important properties for the stack’s children, like the name, the title to display, or if the page needs attention, then this data can be used for example by `Gtk.StackSwitcher`.

It is possible to associate multiple `Gtk.StackSwitcher` widgets with the same `Gtk.Stack` widget.

### Example

     1import gi
     2
     3gi.require_version('Gtk', '4.0')
     4from gi.repository import Gtk, GObject
     5
     6
     7class StackWindow(Gtk.ApplicationWindow):
     8    def __init__(self, **kargs):
     9        super().__init__(**kargs, title='Stack Demo')
    10
    11        self.set_default_size(300, 250)
    12
    13        header = Gtk.HeaderBar()
    14        self.set_titlebar(header)
    15
    16        stack = Gtk.Stack()
    17        stack.props.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT
    18        stack.props.transition_duration = 1000
    19        self.set_child(stack)
    20
    21        checkbutton = Gtk.CheckButton(label='Click me!')
    22        checkbutton.props.hexpand = True
    23        checkbutton.props.halign = Gtk.Align.CENTER
    24        page1 = stack.add_titled(checkbutton, 'check', 'Check Button')
    25        checkbutton.bind_property(
    26            'active', page1, 'needs-attention', GObject.BindingFlags.DEFAULT
    27        )
    28
    29        label = Gtk.Label()
    30        label.set_markup('<big>A fancy label</big>')
    31        stack.add_titled(label, 'label', 'A label')
    32
    33        stack_switcher = Gtk.StackSwitcher()
    34        stack_switcher.set_stack(stack)
    35        header.set_title_widget(stack_switcher)
    36
    37        # Let's start in the second page
    38        stack.set_visible_child_name('label')
    39
    40
    41def on_activate(app):
    42    # Create window
    43    win = StackWindow(application=app)
    44    win.present()
    45
    46
    47app = Gtk.Application(application_id='com.example.App')
    48app.connect('activate', on_activate)
    49
    50app.run(None)

## Notebook

The `Gtk.Notebook` is a container whose children are pages switched between using tabs.

There are many configuration options for GtkNotebook. Among other things, you can choose on which edge the tabs appear (see `Gtk.Notebook.set_tab_pos()`), whether, if there are too many tabs to fit the notebook should be made bigger or scrolling arrows added (see `Gtk.Notebook.set_scrollable()`), and whether there will be a popup menu allowing the users to switch pages (see `Gtk.Notebook.popup_enable()`, `Gtk.Notebook.popup_disable()`).

You can add children with `Gtk.Notebook.append_page()`, it takes two widgets, the first is the widget to show as a page, and the second is the widget to show as the tab content.

### Example

     1import gi
     2
     3gi.require_version('Gtk', '4.0')
     4from gi.repository import Gtk
     5
     6
     7class NotebookWindow(Gtk.Window):
     8    def __init__(self, **kargs):
     9        super().__init__(**kargs, title='Simple Notebook Example')
    10
    11        notebook = Gtk.Notebook()
    12        self.set_child(notebook)
    13
    14        page1 = Gtk.Box()
    15        page1.append(Gtk.Label(label='Default Page!'))
    16        notebook.append_page(page1, Gtk.Label(label='Plain Title'))
    17
    18        page2 = Gtk.Box()
    19        page2.append(Gtk.Label(label='A page with an image for a Title.'))
    20        notebook.append_page(page2, Gtk.Image(icon_name='help-about'))
    21
    22
    23def on_activate(app):
    24    # Create window
    25    win = NotebookWindow(application=app)
    26    win.present()
    27
    28
    29app = Gtk.Application(application_id='com.example.App')
    30app.connect('activate', on_activate)
    31
    32app.run(None)