6. Layoutbehållare

Medan många verktygslådor för grafiska användargränssnitt tvingar dig att precist placera komponenter i ett fönster med absolut positionering, så använder GTK+ ett annat tillvägagångssätt. Snarare än att ange positionen och storleken för varje komponent i fönstret så kan du arrangera dina komponenter i rader, kolumner och/eller tabeller. Storleken på ditt fönster kan avgöras automatiskt, baserat på storleken för de komponenter som det innehåller. Storlekarna för komponenterna avgörs i sin tur av mängden text som de innehåller, eller minimi- och maximistorlekarna som du anger, och/eller hur du har begärt att det tillgängliga utrymmet ska delas mellan uppsättningar av komponenter. Du kan göra din layout bättre genom att ange utfyllnadsavstånd och centreringsvärden för var och en av dina komponenter. GTK+ använder sedan all denna information för att ändra storlek och position för allt på ett förnuftigt och smidigt sätt när användaren manipulerar fönstret.

GTK+ arrangerar komponenter hierarkiskt, med behållare. De är osynliga för slutanvändaren och infogas i ett fönster, eller placeras i varandra för att skapa en layout för komponenter. Det finns två sorters behållare: behållare med ett barn, vilka alla är ättlingar till Gtk.Bin, och behållare med flera barn, vilka är ättlingar till Gtk.Container. De mest använda är vertikala eller horisontella rutor (Gtk.Box) och rutnät (Gtk.Grid).

6.1. Rutor

Rutor är osynliga behållare som vi kan packa våra komponenter i. Då komponenter packas i en horisontell ruta infogas objekten horisontellt från vänster till höger eller höger till vänster beroende på huruvida Gtk.Box.pack_start() eller Gtk.Box.pack_end() används. I en vertikal ruta packas komponenter uppifrån och ner eller omvänt. Du kan använda alla tänkbara kombinationer av rutor i eller bredvid andra rutor för att skapa önskad effekt.

6.1.1. Exempel

Låt oss ta en titt på en något ändrad version av det utökade exemplet med två knappar.

_images/layout_box_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class MyWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Hello World")

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

        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)

    def on_button1_clicked(self, widget):
        print("Hello")

    def on_button2_clicked(self, widget):
        print("Goodbye")


win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

Vi skapar först en horisontellt orienterad rutbehållare där 6 bildpunkter placeras mellan barn. Denna ruta blir barnet till toppnivåfönstret.

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

Därefter lägger vi till två olika knappar till rutbehållaren.

        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)

Medan komponenter positioneras från vänster till höger med Gtk.Box.pack_start(), så positioneras de från höger till vänster med Gtk.Box.pack_end().

6.2. Grid

Gtk.Grid är en behållare som arrangerar sina barnkomponenter i rader och kolumner, men du behöver inte ange dimensionerna i konstruktorn. Barn läggs till med Gtk.Grid.attach(). De kan spänna över flera rader eller kolumner. Metoden Gtk.Grid.attach() tar fem parametrar:

  1. Parametern child är den Gtk.Widget som ska läggas till.

  2. left är kolumnnumret att fästa vänster sida av child till.

  3. top indikerar radnumret att fästa översidan av child till.

  4. width och height indikerar antalet kolumner respektive rader som child kommer spänna över.

Det är också möjligt att lägga till ett barn intill ett befintligt barn, med Gtk.Grid.attach_next_to(), vilket också tar fem parametrar:

  1. child är den Gtk.Widget som ska läggas till, som ovan.

  2. sibling är en befintlig barnkomponent till self (en Gtk.Grid-instans) eller None. child-komponenten kommer placeras intill sibling, eller om sibling är None, i början eller slutet av rutnätet.

  3. side är en Gtk.PositionType som indikerar vilken sida av sibling som child positioneras intill.

  4. width och height indikerar antalet kolumner respektive rader som komponenten child kommer spänna över.

Slutligen kan Gtk.Grid användas som en Gtk.Box genom att helt enkelt använda Gtk.Grid.add(), vilken kommer placera barn intill varandra i den riktning som avgörs av egenskapen ”orientation” (har standardvärdet Gtk.Orientation.HORIZONTAL).

6.2.1. Exempel

_images/layout_grid_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class GridWindow(Gtk.Window):
    def __init__(self):

        super().__init__(title="Grid Example")

        button1 = Gtk.Button(label="Button 1")
        button2 = Gtk.Button(label="Button 2")
        button3 = Gtk.Button(label="Button 3")
        button4 = Gtk.Button(label="Button 4")
        button5 = Gtk.Button(label="Button 5")
        button6 = Gtk.Button(label="Button 6")

        grid = Gtk.Grid()
        grid.add(button1)
        grid.attach(button2, 1, 0, 2, 1)
        grid.attach_next_to(button3, button1, Gtk.PositionType.BOTTOM, 1, 2)
        grid.attach_next_to(button4, button3, Gtk.PositionType.RIGHT, 2, 1)
        grid.attach(button5, 1, 2, 1, 1)
        grid.attach_next_to(button6, button5, Gtk.PositionType.RIGHT, 1, 1)

        self.add(grid)


win = GridWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.3. ListBox

En Gtk.ListBox är en vertikal behållare som innehåller Gtk.ListBoxRow-barn. Dessa rader kan sorteras och filtreras dynamiskt, och rubriker kan läggas till dynamiskt beroende på radinnehållet. Den tillåter också tangentbords- och musnavigering samt markering som en typisk lista.

Att använda Gtk.ListBox är ofta ett alternativ till Gtk.TreeView, särskilt när listinnehållet har en mer komplicerad layout än vad som tillåts av en Gtk.CellRenderer, eller när innehållet är interaktivt (t.ex. har en knapp i sig).

Även om en Gtk.ListBox endast får ha barn som är Gtk.ListBoxRow, så kan du lägga till valfri komponent till den med Gtk.Container.add(), så kommer automatiskt en Gtk.ListBoxRow-komponent infogas mellan listan och komponenten.

6.3.1. Exempel

_images/listbox_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class ListBoxRowWithData(Gtk.ListBoxRow):
    def __init__(self, data):
        super().__init__()
        self.data = data
        self.add(Gtk.Label(label=data))


class ListBoxWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="ListBox Demo")
        self.set_border_width(10)

        box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        self.add(box_outer)

        listbox = Gtk.ListBox()
        listbox.set_selection_mode(Gtk.SelectionMode.NONE)
        box_outer.pack_start(listbox, True, True, 0)

        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        hbox.pack_start(vbox, True, True, 0)

        label1 = Gtk.Label(label="Automatic Date & Time", xalign=0)
        label2 = Gtk.Label(label="Requires internet access", xalign=0)
        vbox.pack_start(label1, True, True, 0)
        vbox.pack_start(label2, True, True, 0)

        switch = Gtk.Switch()
        switch.props.valign = Gtk.Align.CENTER
        hbox.pack_start(switch, False, True, 0)

        listbox.add(row)

        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        label = Gtk.Label(label="Enable Automatic Update", xalign=0)
        check = Gtk.CheckButton()
        hbox.pack_start(label, True, True, 0)
        hbox.pack_start(check, False, True, 0)

        listbox.add(row)

        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        label = Gtk.Label(label="Date Format", xalign=0)
        combo = Gtk.ComboBoxText()
        combo.insert(0, "0", "24-hour")
        combo.insert(1, "1", "AM/PM")
        hbox.pack_start(label, True, True, 0)
        hbox.pack_start(combo, False, True, 0)

        listbox.add(row)

        listbox_2 = Gtk.ListBox()
        items = "This is a sorted ListBox Fail".split()

        for item in items:
            listbox_2.add(ListBoxRowWithData(item))

        def sort_func(row_1, row_2, data, notify_destroy):
            return row_1.data.lower() > row_2.data.lower()

        def filter_func(row, data, notify_destroy):
            return False if row.data == "Fail" else True

        listbox_2.set_sort_func(sort_func, None, False)
        listbox_2.set_filter_func(filter_func, None, False)

        def on_row_activated(listbox_widget, row):
            print(row.data)

        listbox_2.connect("row-activated", on_row_activated)

        box_outer.pack_start(listbox_2, True, True, 0)
        listbox_2.show_all()


win = ListBoxWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.4. Stack och StackSwitcher

En Gtk.Stack är en behållare som bara visar ett av sina barn åt gången. Till skillnad från Gtk.Notebook så tillhandahåller Gtk.Stack inte något sätt för användare att ändra det synliga barnet. Istället kan komponenten Gtk.StackSwitcher användas med Gtk.Stack för att tillhandahålla denna funktionalitet.

Övergångar mellan sidor kan animeras som bildspel eller toningar. Detta kan styras med Gtk.Stack.set_transition_type(). Dessa animeringar följer inställningen ”gtk-enable-animations”.

Övergångshastighet kan justeras med Gtk.Stack.set_transition_duration()

Komponenten Gtk.StackSwitcher agerar som en styrenhet för en Gtk.Stack; den visar en knapprad för att växla mellan de olika sidorna för den associerade stackkomponenten.

Allt innehåll för knapparna kommer från barnegenskaperna för Gtk.Stack.

Det är möjligt att associera flera Gtk.StackSwitcher-komponenter med samma Gtk.Stack-komponent.

6.4.1. Exempel

_images/stack_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class StackWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Stack Demo")
        self.set_border_width(10)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        self.add(vbox)

        stack = Gtk.Stack()
        stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
        stack.set_transition_duration(1000)

        checkbutton = Gtk.CheckButton(label="Click me!")
        stack.add_titled(checkbutton, "check", "Check Button")

        label = Gtk.Label()
        label.set_markup("<big>A fancy label</big>")
        stack.add_titled(label, "label", "A label")

        stack_switcher = Gtk.StackSwitcher()
        stack_switcher.set_stack(stack)
        vbox.pack_start(stack_switcher, True, True, 0)
        vbox.pack_start(stack, True, True, 0)


win = StackWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.5. HeaderBar

En Gtk.HeaderBar liknar en horisontell Gtk.Box, den tillåter att barn placeras i början eller slutet. Dessutom tillåter den att en titel visas. Titeln kommer centreras med avseende på rutans bredd, även om barnen på endera sida tar upp olika mycket utrymme.

Eftersom GTK+ nu stöder dekoration på klientsidan (CSD) så kan en Gtk.HeaderBar användas istället för namnlisten (vilken renderas av fönsterhanteraren).

En Gtk.HeaderBar placeras vanligen högst upp i ett fönster och bör innehålla vanligen använda kontroller som påverkar innehållet nedan. De tillhandahåller också åtkomst till fönsterstyrkomponenter, vilket inkluderar knappen för att stänga fönstret och fönstermenyn.

6.5.1. Exempel

_images/headerbar_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio


class HeaderBarWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="HeaderBar Demo")
        self.set_border_width(10)
        self.set_default_size(400, 200)

        hb = Gtk.HeaderBar()
        hb.set_show_close_button(True)
        hb.props.title = "HeaderBar example"
        self.set_titlebar(hb)

        button = Gtk.Button()
        icon = Gio.ThemedIcon(name="mail-send-receive-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
        button.add(image)
        hb.pack_end(button)

        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        Gtk.StyleContext.add_class(box.get_style_context(), "linked")

        button = Gtk.Button()
        button.add(
            Gtk.Arrow(arrow_type=Gtk.ArrowType.LEFT, shadow_type=Gtk.ShadowType.NONE)
        )
        box.add(button)

        button = Gtk.Button.new_from_icon_name("pan-end-symbolic", Gtk.IconSize.MENU)
        box.add(button)

        hb.pack_start(box)

        self.add(Gtk.TextView())


win = HeaderBarWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.6. FlowBox

Observera

Detta exempel kräver åtminstone GTK+ 3.12.

En Gtk.FlowBox är en behållare som positionerar barnkomponenter i ordning enligt sin orientering.

Exempelvis kommer komponenterna vid horisontell orientering att arrangeras från vänster till höger, och starta en ny rad under föregående rad då detta är nödvändigt. Att minska bredden kommer i detta fall kräva fler rader, så en större höjd kommer begäras.

På samma sätt kommer komponenterna vid vertikal orientering att arrangeras uppifrån och ner, och starta en ny kolumn till höger då detta är nödvändigt. Att minska höjden kommer kräva fler kolumner, så en större bredd kommer begäras.

Barnen till en Gtk.FlowBox kan sorteras och filtreras dynamiskt.

Även om en Gtk.FlowBox endast får ha barn som är Gtk.FlowBoxChild, så kan du lägga till valfri komponent till den med Gtk.Container.add(), så kommer automatiskt en Gtk.FlowBoxChild-komponent infogas mellan rutan och komponenten.

6.6.1. Exempel

_images/flowbox_example.png
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk


class FlowBoxWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="FlowBox Demo")
        self.set_border_width(10)
        self.set_default_size(300, 250)

        header = Gtk.HeaderBar(title="Flow Box")
        header.set_subtitle("Sample FlowBox app")
        header.props.show_close_button = True

        self.set_titlebar(header)

        scrolled = Gtk.ScrolledWindow()
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)

        flowbox = Gtk.FlowBox()
        flowbox.set_valign(Gtk.Align.START)
        flowbox.set_max_children_per_line(30)
        flowbox.set_selection_mode(Gtk.SelectionMode.NONE)

        self.create_flowbox(flowbox)

        scrolled.add(flowbox)

        self.add(scrolled)
        self.show_all()

    def on_draw(self, widget, cr, data):
        context = widget.get_style_context()

        width = widget.get_allocated_width()
        height = widget.get_allocated_height()
        Gtk.render_background(context, cr, 0, 0, width, height)

        r, g, b, a = data["color"]
        cr.set_source_rgba(r, g, b, a)
        cr.rectangle(0, 0, width, height)
        cr.fill()

    def color_swatch_new(self, str_color):
        rgba = Gdk.RGBA()
        rgba.parse(str_color)

        button = Gtk.Button()

        area = Gtk.DrawingArea()
        area.set_size_request(24, 24)
        area.connect("draw", self.on_draw, {"color": rgba})

        button.add(area)

        return button

    def create_flowbox(self, flowbox):
        colors = [
            "AliceBlue",
            "AntiqueWhite",
            "AntiqueWhite1",
            "AntiqueWhite2",
            "AntiqueWhite3",
            "AntiqueWhite4",
            "aqua",
            "aquamarine",
            "aquamarine1",
            "aquamarine2",
            "aquamarine3",
            "aquamarine4",
            "azure",
            "azure1",
            "azure2",
            "azure3",
            "azure4",
            "beige",
            "bisque",
            "bisque1",
            "bisque2",
            "bisque3",
            "bisque4",
            "black",
            "BlanchedAlmond",
            "blue",
            "blue1",
            "blue2",
            "blue3",
            "blue4",
            "BlueViolet",
            "brown",
            "brown1",
            "brown2",
            "brown3",
            "brown4",
            "burlywood",
            "burlywood1",
            "burlywood2",
            "burlywood3",
            "burlywood4",
            "CadetBlue",
            "CadetBlue1",
            "CadetBlue2",
            "CadetBlue3",
            "CadetBlue4",
            "chartreuse",
            "chartreuse1",
            "chartreuse2",
            "chartreuse3",
            "chartreuse4",
            "chocolate",
            "chocolate1",
            "chocolate2",
            "chocolate3",
            "chocolate4",
            "coral",
            "coral1",
            "coral2",
            "coral3",
            "coral4",
        ]

        for color in colors:
            button = self.color_swatch_new(color)
            flowbox.add(button)


win = FlowBoxWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.7. Notebook

Komponenten Gtk.Notebook är en Gtk.Container vars barn är sidor som kan växlas mellan med fliketiketter längs en kant.

Det finns många konfigurationsalternativ för GtkNotebook. Bland annat kan du välja på vilken kant flikarna visas (se Gtk.Notebook.set_tab_pos()), huruvida anteckningsboken ska göras större eller om rullningspilar ska läggas till då det finns för många flikar för att rymmas (se Gtk.Notebook.set_scrollable()), och huruvida det ska finnas en poppuppmeny som låter användarna växla sidor (se Gtk.Notebook.popup_enable(), Gtk.Notebook.popup_disable()).

6.7.1. Exempel

_images/notebook_plain_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class MyWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Simple Notebook Example")
        self.set_border_width(3)

        self.notebook = Gtk.Notebook()
        self.add(self.notebook)

        self.page1 = Gtk.Box()
        self.page1.set_border_width(10)
        self.page1.add(Gtk.Label(label="Default Page!"))
        self.notebook.append_page(self.page1, Gtk.Label(label="Plain Title"))

        self.page2 = Gtk.Box()
        self.page2.set_border_width(10)
        self.page2.add(Gtk.Label(label="A page with an image for a Title."))
        self.notebook.append_page(
            self.page2, Gtk.Image.new_from_icon_name("help-about", Gtk.IconSize.MENU)
        )


win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()