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:

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.

../_images/layout_box.png
 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#

../_images/layout_center.png
 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#

../_images/layout_headerbar.png
 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#

../_images/layout_grid.png
 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#

../_images/layout_listbox.png
  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#

../_images/layout_flowbox.png
  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#

../_images/layout_stack.png
 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#

../_images/layout_notebook.png
 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)