6. Layout Containers — Python GTK+ 3 Tutorial 3.4 documentation (original) (raw)

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+ arranges widgets hierarchically, using containers. They are invisible to the end user and are inserted into a window, or placed within each other to layout components. There are two flavours of containers: single-child containers, which are all descendants of Gtk.Bin, and multiple-child containers, which are descendants of Gtk.Container. The most commonly used are vertical or horizontal boxes (Gtk.Box) and grids (Gtk.Grid).

6.1. 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 whetherGtk.Box.pack_start() or Gtk.Box.pack_end() is used. 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.

6.1.1. Example

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

_images/layout_box_example.png

1import gi 2 3gi.require_version("Gtk", "3.0") 4from gi.repository import Gtk 5 6 7class MyWindow(Gtk.Window): 8 def init(self): 9 super().init(title="Hello World") 10 11 self.box = Gtk.Box(spacing=6) 12 self.add(self.box) 13 14 self.button1 = Gtk.Button(label="Hello") 15 self.button1.connect("clicked", self.on_button1_clicked) 16 self.box.pack_start(self.button1, True, True, 0) 17 18 self.button2 = Gtk.Button(label="Goodbye") 19 self.button2.connect("clicked", self.on_button2_clicked) 20 self.box.pack_start(self.button2, True, True, 0) 21 22 def on_button1_clicked(self, widget): 23 print("Hello") 24 25 def on_button2_clicked(self, widget): 26 print("Goodbye") 27 28 29win = MyWindow() 30win.connect("destroy", Gtk.main_quit) 31win.show_all() 32Gtk.main()

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.

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

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

    self.button1 = Gtk.Button(label="Hello")
    self.button1.connect("clicked", self.on_button1_clicked)
    self.box.pack_start(self.button1, True, True, 0)

    self.button2 = Gtk.Button(label="Goodbye")
    self.button2.connect("clicked", self.on_button2_clicked)
    self.box.pack_start(self.button2, True, True, 0)

While with Gtk.Box.pack_start() widgets are positioned from left to right,Gtk.Box.pack_end() positions them from right to left.

6.2. 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 childwill 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, usingGtk.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 ofsibling that child is positioned next to.
  4. width and height indicate the number of columns and rows the child widget will span, respectively.

Finally, Gtk.Grid can be used like a Gtk.Box by just usingGtk.Grid.add(), which will place children next to each other in the direction determined by the “orientation” property (defaults toGtk.Orientation.HORIZONTAL).

6.2.1. Example

_images/layout_grid_example.png

1import gi 2 3gi.require_version("Gtk", "3.0") 4from gi.repository import Gtk 5 6 7class GridWindow(Gtk.Window): 8 def init(self): 9 10 super().init(title="Grid Example") 11 12 button1 = Gtk.Button(label="Button 1") 13 button2 = Gtk.Button(label="Button 2") 14 button3 = Gtk.Button(label="Button 3") 15 button4 = Gtk.Button(label="Button 4") 16 button5 = Gtk.Button(label="Button 5") 17 button6 = Gtk.Button(label="Button 6") 18 19 grid = Gtk.Grid() 20 grid.add(button1) 21 grid.attach(button2, 1, 0, 2, 1) 22 grid.attach_next_to(button3, button1, Gtk.PositionType.BOTTOM, 1, 2) 23 grid.attach_next_to(button4, button3, Gtk.PositionType.RIGHT, 2, 1) 24 grid.attach(button5, 1, 2, 1, 1) 25 grid.attach_next_to(button6, button5, Gtk.PositionType.RIGHT, 1, 1) 26 27 self.add(grid) 28 29 30win = GridWindow() 31win.connect("destroy", Gtk.main_quit) 32win.show_all() 33Gtk.main()

6.3. ListBox

A Gtk.ListBox is a vertical container that contains Gtk.ListBoxRowchildren. 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.

Using Gtk.ListBox is often an alternative to Gtk.TreeView, especially when the list content has a more complicated layout than what is allowed by aGtk.CellRenderer, or when the content is interactive (e.g. has a button in it).

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

6.3.1. Example

_images/listbox_example.png

1import gi 2 3gi.require_version("Gtk", "3.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.add(Gtk.Label(label=data)) 12 13 14class ListBoxWindow(Gtk.Window): 15 def init(self): 16 super().init(title="ListBox Demo") 17 self.set_border_width(10) 18 19 box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) 20 self.add(box_outer) 21 22 listbox = Gtk.ListBox() 23 listbox.set_selection_mode(Gtk.SelectionMode.NONE) 24 box_outer.pack_start(listbox, True, True, 0) 25 26 row = Gtk.ListBoxRow() 27 hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50) 28 row.add(hbox) 29 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 30 hbox.pack_start(vbox, True, True, 0) 31 32 label1 = Gtk.Label(label="Automatic Date & Time", xalign=0) 33 label2 = Gtk.Label(label="Requires internet access", xalign=0) 34 vbox.pack_start(label1, True, True, 0) 35 vbox.pack_start(label2, True, True, 0) 36 37 switch = Gtk.Switch() 38 switch.props.valign = Gtk.Align.CENTER 39 hbox.pack_start(switch, False, True, 0) 40 41 listbox.add(row) 42 43 row = Gtk.ListBoxRow() 44 hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50) 45 row.add(hbox) 46 label = Gtk.Label(label="Enable Automatic Update", xalign=0) 47 check = Gtk.CheckButton() 48 hbox.pack_start(label, True, True, 0) 49 hbox.pack_start(check, False, True, 0) 50 51 listbox.add(row) 52 53 row = Gtk.ListBoxRow() 54 hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50) 55 row.add(hbox) 56 label = Gtk.Label(label="Date Format", xalign=0) 57 combo = Gtk.ComboBoxText() 58 combo.insert(0, "0", "24-hour") 59 combo.insert(1, "1", "AM/PM") 60 hbox.pack_start(label, True, True, 0) 61 hbox.pack_start(combo, False, True, 0) 62 63 listbox.add(row) 64 65 listbox_2 = Gtk.ListBox() 66 items = "This is a sorted ListBox Fail".split() 67 68 for item in items: 69 listbox_2.add(ListBoxRowWithData(item)) 70 71 def sort_func(row_1, row_2, data, notify_destroy): 72 return row_1.data.lower() > row_2.data.lower() 73 74 def filter_func(row, data, notify_destroy): 75 return False if row.data == "Fail" else True 76 77 listbox_2.set_sort_func(sort_func, None, False) 78 listbox_2.set_filter_func(filter_func, None, False) 79 80 def on_row_activated(listbox_widget, row): 81 print(row.data) 82 83 listbox_2.connect("row-activated", on_row_activated) 84 85 box_outer.pack_start(listbox_2, True, True, 0) 86 listbox_2.show_all() 87 88 89win = ListBoxWindow() 90win.connect("destroy", Gtk.main_quit) 91win.show_all() 92Gtk.main()

6.4. 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, theGtk.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.set_transition_type(). These animations respect the “gtk-enable-animations” setting.

Transition speed can be adjusted with Gtk.Stack.set_transition_duration()

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

All the content for the buttons comes from the child properties of theGtk.Stack.

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

6.4.1. Example

_images/stack_example.png

1import gi 2 3gi.require_version("Gtk", "3.0") 4from gi.repository import Gtk 5 6 7class StackWindow(Gtk.Window): 8 def init(self): 9 super().init(title="Stack Demo") 10 self.set_border_width(10) 11 12 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) 13 self.add(vbox) 14 15 stack = Gtk.Stack() 16 stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) 17 stack.set_transition_duration(1000) 18 19 checkbutton = Gtk.CheckButton(label="Click me!") 20 stack.add_titled(checkbutton, "check", "Check Button") 21 22 label = Gtk.Label() 23 label.set_markup("A fancy label") 24 stack.add_titled(label, "label", "A label") 25 26 stack_switcher = Gtk.StackSwitcher() 27 stack_switcher.set_stack(stack) 28 vbox.pack_start(stack_switcher, True, True, 0) 29 vbox.pack_start(stack, True, True, 0) 30 31 32win = StackWindow() 33win.connect("destroy", Gtk.main_quit) 34win.show_all() 35Gtk.main()

6.6. FlowBox

Note

This example requires at least GTK+ 3.12.

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.

The children of a Gtk.FlowBox can be dynamically sorted and filtered.

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

6.6.1. Example

_images/flowbox_example.png

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

6.7. Notebook

The Gtk.Notebook widget is a Gtk.Container whose children are pages that can be switched between using tab labels along one edge.

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 (seeGtk.Notebook.popup_enable(), Gtk.Notebook.popup_disable()).

6.7.1. Example

_images/notebook_plain_example.png

1import gi 2 3gi.require_version("Gtk", "3.0") 4from gi.repository import Gtk 5 6 7class MyWindow(Gtk.Window): 8 def init(self): 9 super().init(title="Simple Notebook Example") 10 self.set_border_width(3) 11 12 self.notebook = Gtk.Notebook() 13 self.add(self.notebook) 14 15 self.page1 = Gtk.Box() 16 self.page1.set_border_width(10) 17 self.page1.add(Gtk.Label(label="Default Page!")) 18 self.notebook.append_page(self.page1, Gtk.Label(label="Plain Title")) 19 20 self.page2 = Gtk.Box() 21 self.page2.set_border_width(10) 22 self.page2.add(Gtk.Label(label="A page with an image for a Title.")) 23 self.notebook.append_page( 24 self.page2, Gtk.Image.new_from_icon_name("help-about", Gtk.IconSize.MENU) 25 ) 26 27 28win = MyWindow() 29win.connect("destroy", Gtk.main_quit) 30win.show_all() 31Gtk.main()