Springe zum Hauptinhalt

Überlistet

Daten in ListStore speichern und mit ComboBox und TreeView anzeigen

Für die Speicherung und Anzeige von Daten in Listen- oder Tabellenform benötigt man in GTK+-Anwendungen verschiedene Elemente:

  1. Im Modell werden die Daten verwaltet, es gibt zwei Typen:

    • ListStore: flache Liste, die Spalten können neben Text-, Zahlenwerten auch GTK+-Elemente (z.B. Buttons, Checkboxen) enthalten

    • TreeStore: funktioniert prinzipiell wie ListStore, Zeilen können ihrerseits Kind-Einträge besitzen, Daten können im Gegensatz zu ListStore nicht in Glade angegeben werden (TreeStore-Artikel)

  2. Widgets:

    • TreeView: dieses Widget eignet sich zum Anzeigen, Sortieren, Bearbeiten von Daten, wird von beiden Modelltypen verwendet; es können parallel mehrere TreeView-Widgets angelegt werden, die auf dieselbe Datenbasis (Modell) zurückgreifen, aber zum Beispiel verschiedene Spalten anzeigen

    • ComboBox: Comboboxen dienen der Auswahl aus einer gegebenen Liste, deren Datenbasis ein List- oder TreeStore sein kann (siehe Artikel zu Spinbutton und Combobox)

    • CellRenderers: Unterwidgets, in denen die anzuzeigenden Daten, deren Layout und weitere Optionen wie Bearbeitbarkeit festgelegt werden

/images/09_treestore2.thumbnail.png

Glade

ListStore

Um die Vielseitigkeit von ListStore zu skizzieren, wird im Beispiel ein Gtk.ListStore (zu finden in der Elementauswahl links unter "Sonstiges > Listenverwahrung") erstellt und von drei Widgets verwendet.

Zunächst werden ein paar Spalten erstellt. ListStore-Daten lassen sich direkt in Glade eingeben. Dies ist allerdings nur für wenige Zeilen und Spalten praktikabel und übersichtlich. Selbst wenige Daten würde ich immer direkt im Python-Code einlesen.

Wie man sieht, werden Änderungen im ListStore (Sortierung, Inhalt) sofort in allen Widgets aktualisiert, die auf dieses Objekt zugreifen. Für verschiedene Sortierungen des selben List-/TreeStores muss man Gtk.TreeModelSort anwenden (Beispiel siehe TreeStore-Artikel).

/images/09_treestore1.thumbnail.png

Widgets

ComboBox

Als "Baumansichtsmodell" wird wie auch bei den folgenden Widgets der ListStore ausgewählt. Über "Edit > Hierarchie" ein CellRendererText hinzugefügt. Im ersten Feld ("Text") stellt man ein, aus welcher Spalte das Dropdown-Menü angezeigt werden soll. Um die Auswahl zu verarbeiten, wird das Signal changed belegt.

TreeView #1

Das erste TreeView-Widget wird innerhalb eines Gtk.ScrolledWindow-Containers angelegt. Wie bei ComboBox werden nun beliebige CellRenderer angelegt. Wird der Sortierungsanzeiger aktiviert, können die Spalten mit Klick auf den Spaltenkopf sortiert werden. In der Sortierspaltenkennung wird die Spalte angegeben, nach der sortiert werden soll, auf diese Weise kann man eine Spalte auch gemäß einer anderen Spalte sortieren (hier im Beispiel wird die mittlere Spalte nach der letzten sortiert, die Sortierung der beiden hinteren Spalten liefert also das gleiche Ergebnis.

TreeView #2

Das zweite TreeView-Widget wird innerhalb eines Sichtfeldes (Gtk.Viewport) erstellt. Dieser Container bietet keine Scrollbalken, das Widget vergrößert automatisch, so dass alle Zeilen sichtbar sind. Bei größeren Tabellen ist ein ScrolledWindow also praktikabler. Es werden die gleichen Daten angezeigt wie zuvor, allerdings ohne Sortierungsanzeiger, dafür wird die mittlere Spalte ("Description") editierbar gemacht und erhält eine Funktion für das Signal edited.

Button

Ein Klick auf den Button soll jeweils eine weitere Zeile zum ListStore hinzufügen, es wird also das clicked-Signal belegt.

Python

TreeStore

Die in TreeStore vorhandenen Zeilen lassen sich einfach über for row in store abrufen. Neue Zeilen lassen sich mit append hinzufügen, andere Optionen wären insert oder remove, um Zeilen an bestimmten Positionen einzufügen oder zu entfernen.

ComboBox

Normalerweise benötigt man für den Zugang zu einer Datenzeile einen TreeIter, das Objekt, das auf den Pfad im Modell zeigt (alternativ kann man diese auch über TreePath ansprechen).

iter, model = widget.get_active_iter(), widget.get_model()
row = model[iter]
print("Selection:", row[0])

Zellen bearbeiten

Das edited-Signal übergibt als Parameter die bearbeitete Zeile und den neuen Zelleninhalt. Dieser muss allerdings explizit als neuer Zelleninhalt übergeben werden, sonst zeigt die Zelle nach der Bearbeitung wieder den alten Inhalt an. Dafür kann man einfach die vom Widget übergebene Position (TreePath) statt des TreeIters verwenden.

def on_cellrenderer_descr_edited(self, widget, pos, edit):
    x.store[int(pos)][1] = edit

Listings

Python

09_liststore.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_cbox_changed(self, widget):
        iter, model = widget.get_active_iter(), widget.get_model()
        row = model[iter]
        print("Selection:", row[0])

    def on_cellrenderer_descr_edited(self, widget, pos, edit):
        x.store[int(pos)][1] = edit

    def on_add_row_button_clicked(self,widget):
        x.store.append(list(x.more_rows[len(x.store) - 3]))
        #set button inactive when all rows are appended
        if len(x.store) == 7:
            x.button.set_sensitive(False)


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        self.builder.add_from_file("09_liststore.glade")
        self.builder.connect_signals(Handler())

        window = self.builder.get_object("window")
        window.show_all()

        self.button = self.builder.get_object("add_row_button")
        self.store = self.builder.get_object("liststore")

        #print all values
        [print(row[:]) for row in self.store]

        self.more_rows = [("four", "", 5739),
                          ("five", "", 120),
                          ("six", "", 4),
                          ("seven", "lucky number", 7),
                          ]

    def main(self):
        Gtk.main()


x = Example()
x.main()

Glade

09_liststore.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkListStore" id="liststore">
    <columns>
      <!-- column-name name -->
      <column type="gchararray"/>
      <!-- column-name descr -->
      <column type="gchararray"/>
      <!-- column-name num -->
      <column type="gint"/>
    </columns>
    <data>
      <row>
        <col id="0" translatable="yes">one</col>
        <col id="1" translatable="yes">textextext</col>
        <col id="2">12345</col>
      </row>
      <row>
        <col id="0" translatable="yes">two</col>
        <col id="1" translatable="yes">bla blubb</col>
        <col id="2">479</col>
      </row>
      <row>
        <col id="0" translatable="yes">three</col>
        <col id="1" translatable="yes"></col>
        <col id="2">0</col>
      </row>
    </data>
  </object>
  <object class="GtkWindow" id="window">
    <property name="width_request">300</property>
    <property name="can_focus">False</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkComboBox" id="cbox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="model">liststore</property>
            <property name="entry_text_column">0</property>
            <signal name="changed" handler="on_cbox_changed" swapped="no"/>
            <child>
              <object class="GtkCellRendererText"/>
              <attributes>
                <attribute name="text">0</attribute>
              </attributes>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkBox">
            <property name="width_request">150</property>
            <property name="height_request">250</property>
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkScrolledWindow">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <child>
                  <object class="GtkTreeView">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="model">liststore</property>
                    <property name="headers_clickable">False</property>
                    <child internal-child="selection">
                      <object class="GtkTreeSelection"/>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn">
                        <property name="title" translatable="yes">Name</property>
                        <property name="sort_indicator">True</property>
                        <property name="sort_column_id">0</property>
                        <child>
                          <object class="GtkCellRendererText"/>
                          <attributes>
                            <attribute name="text">0</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn">
                        <property name="title" translatable="yes">Description</property>
                        <property name="sort_indicator">True</property>
                        <property name="sort_column_id">2</property>
                        <child>
                          <object class="GtkCellRendererText"/>
                          <attributes>
                            <attribute name="text">1</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn">
                        <property name="title" translatable="yes">Number</property>
                        <property name="sort_indicator">True</property>
                        <property name="sort_column_id">2</property>
                        <child>
                          <object class="GtkCellRendererText"/>
                          <attributes>
                            <attribute name="text">2</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkViewport">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <child>
                  <object class="GtkTreeView">
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="model">liststore</property>
                    <child internal-child="selection">
                      <object class="GtkTreeSelection"/>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn">
                        <property name="title" translatable="yes">Name</property>
                        <child>
                          <object class="GtkCellRendererText"/>
                          <attributes>
                            <attribute name="text">0</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn">
                        <property name="title" translatable="yes">Description</property>
                        <child>
                          <object class="GtkCellRendererText" id="cellrenderer_descr">
                            <property name="editable">True</property>
                            <signal name="edited" handler="on_cellrenderer_descr_edited" swapped="no"/>
                          </object>
                          <attributes>
                            <attribute name="text">1</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                    <child>
                      <object class="GtkTreeViewColumn">
                        <property name="title" translatable="yes">Number</property>
                        <child>
                          <object class="GtkCellRendererText"/>
                          <attributes>
                            <attribute name="text">2</attribute>
                          </attributes>
                        </child>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="add_row_button">
            <property name="label">gtk-add</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
            <signal name="clicked" handler="on_add_row_button_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <placeholder/>
    </child>
  </object>
</interface>

Qual der Wahl

Spinbutton und Combobox

Die Widgets erleichtern die Eingabe bestimmer Werte, indem eine Listenauswahl oder ein Wertebereich und gegebenenfalls Standardwerte dazu vorgegeben werden. Die Eingabe ist normalerweise rein intuitiv über Mausklicks machbar, Tastatureingaben sind optional.

Glade

/images/08_combospin.thumbnail.png

Spinbutton

Spinbuttons verfügen über zahlreiche, per Glade festlegbare Eigenschaften wie Mindest-/Höchst-/Standardwert. Zum Spinbutton gehört zwingend das Widget adjustment, das unter "Allgemein > Attribute des Einstellknopfs > Stellgröße" ausgewählt oder angelegt werden kann.

Im Beispiel repräsentieren die beiden Spinbuttons Monat und Jahr, wobei der Spinbutton für den Monat zyklisch angelegt wird, das heißt, nach dem Erreichen des Maximalwertes springt er auf den Mindestwert um. Dieses Verhalten löst das Signal wrapped aus und wird angelegt, um die Jahreszahl im zweiten Spinbutton passend umzuschalten.

Combobox

Es gibt in GTK+ zwei verschiedene Combobox-Widgets:

Gtk.ComboboxText

Die Auswahlliste des Dropdown-Menüs sind Strings und werden direkt im Widget erstellt.

Gtk.Combobox

Die Daten für die Auswahlliste stammen aus einem Listen- oder Baumspeicher (ListStore oder TreeStore). In diesen können Datensätze mit verschiedenen Informationen gespeichert werden (siehe auch Artikel "Überlistet").

Beide Widgets können zusätzlich ein optionales Eingabefeld besitzen. In diesem Fall muss "Allgemein > Hat Eintrag" aktiviert sein. Dies legt das interne Widget Gtk.Entry an. Wichtig ist, dass dieses unter "Gemeinsam > Widget Flags" fokussierbar gemacht wird.

Im Beispiel gibt es zwei ComboboxText-Widgets. Das erste besitzt kein Eingabefeld, es ist also ausschließlich eine Auswahl unter den gegebenen Listenpunkten möglich, die Auswahlliste ist direkt in Glade eingegeben. Die zweite Combobox besitzt ein Eingabefeld, zu demonstrativen Zwecken werden die Listenpunkte direkt im Programm erstellt. Bei beiden wird das Signal changed abgefangen.

Python

Spinbutton

Der Wert eines Spinbutton lässt sich einfach per get_value bzw. set_value ermitteln bzw. festlegen. So werden im Beispiel zu Beginn die aktuellen Monats- und Jahreszahlen eingetragen und in der Funktion on_spin_m_wrapped beim Umschalten von 12 auf 1 die Jahreszahl um 1 erhöht und umgekehrt.

Combobox

Listeneinträge einer Combobox können einfach mit der Funktion append angefügt werden, wie in diesem Beispiel etwa

[self.builder.get_object("comboboxtext2").append(None, entry) for entry in ("bla", "blubb", "ja", "nein")]

Der aktuell angewählte Eintrag wird mit der Funktion widget.set_active_text() ermittelt, diese gibt auch den Text des optionalen Texteintragfeldes aus.

Listings

Python

08_combospin.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import datetime

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_spin_m_wrapped(self, widget):
        if widget.get_value() == 1:
            x.spin_y.set_value(x.spin_y.get_value() + 1)
        else:
            x.spin_y.set_value(x.spin_y.get_value() - 1)

    def on_comboboxtext1_changed(self, widget):
        print("Auswahl ComboBox 1:", widget.get_active_text())

    def on_comboboxtext2_changed(self, widget):
        print("Auswahl ComboBox 2:", widget.get_active_text())


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        self.builder.add_from_file("08_combospin.glade")
        self.builder.connect_signals(Handler())

        #set current values for month/year
        self.builder.get_object("spin_m").set_value(datetime.datetime.now().month)
        self.spin_y = self.builder.get_object("spin_y")
        self.spin_y.set_value(datetime.datetime.now().year)

        #set combobox list values
        [self.builder.get_object("comboboxtext2").append(None,entry) for entry in ("bla", "blubb", "ja", "nein")]

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Glade

08_combospin.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkAdjustment" id="adj_j">
    <property name="lower">1234</property>
    <property name="upper">3000</property>
    <property name="step_increment">1</property>
    <property name="page_increment">10</property>
  </object>
  <object class="GtkAdjustment" id="adj_m">
    <property name="lower">1</property>
    <property name="upper">12</property>
    <property name="step_increment">1</property>
    <property name="page_increment">10</property>
  </object>
  <object class="GtkWindow" id="window">
    <property name="can_focus">False</property>
    <property name="default_width">400</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="homogeneous">True</property>
            <child>
              <object class="GtkSpinButton" id="spin_m">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="placeholder_text" translatable="yes">Monat</property>
                <property name="adjustment">adj_m</property>
                <property name="wrap">True</property>
                <signal name="wrapped" handler="on_spin_m_wrapped" swapped="no"/>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkSpinButton" id="spin_y">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="adjustment">adj_j</property>
                <property name="climb_rate">1</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">2</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkComboBoxText" id="comboboxtext1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <items>
              <item id="&lt;Geben Sie die Kennung ein&gt;" translatable="yes">eins</item>
              <item translatable="yes">zwei</item>
              <item translatable="yes">12345</item>
              <item translatable="yes">mehr Listenblabla</item>
            </items>
            <signal name="changed" handler="on_comboboxtext1_changed" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkComboBoxText" id="comboboxtext2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="has_entry">True</property>
            <signal name="changed" handler="on_comboboxtext2_changed" swapped="no"/>
            <child internal-child="entry">
              <object class="GtkEntry" id="entry">
                <property name="can_focus">True</property>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">3</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <placeholder/>
    </child>
  </object>
</interface>

Alles so schön bunt hier

Schöner klicken mit Cascading Style Sheets

CSS

GTK+-Objekte lassen sich mit Hilfe von CSS im Layout verändern. Meiner unmaßgeblichen Ansicht nach sollte man es damit allerdings nicht übertreiben und das grundlegende Erscheinungsbild dem eingestellten Theme überlassen. Links:

Glade

/images/07_css.thumbnail.png

Mit Glade werden nur die Fenster/Widgets angelegt, in diesem Beispiel vier Levelbars mit Werten Die CSS-Layout-Anweisungen erfolgen dann im Code.

Python

CSS

Layout-Anweisungen erfolgen nach dem Muster

widget [element] {
  font...
  color...
  background...
  ...
}

innerhalb einer String-Variblen, die von der Klasse Gtk.CssProvider() geladen werden.

Levelbar

Levelbars können, wie bereits im Artikel "Bars" angedeutet, in definierten Wertebereichen unterschiedliche Farben annehmen (um zum Beispiel einen kritischen Ladezustand zu visualisieren). Die vordefinierten Offset-Marker dafür sind:

  • low (<=.25)

  • high (<=.75)

  • full (bis 1)

Die Werte können mit den Funktionen get_offset_value abgefragt bzw. mit add_offset_value angelegt oder verändert werden.

Im Beispiel wird der unteren Levelbar ein zusätzlicher Offsetmarker zwischen high und full angelegt, deshalb wird beim Wert von 0.8 dort im Gegensatz zur dritten Levelbar nicht der Marker für full ereicht.

self.bar.add_offset_value("alert", .9)

Listings

Python

07_css.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        self.builder.add_from_file("07_css.glade")
        self.builder.connect_signals(Handler())

        css = b"""

levelbar trough block.filled.low {
    background-color: green;
}

levelbar trough block.filled.high {
    background-color: yellow;
}

levelbar trough block.filled.alert {
    background-color: orange;
}

levelbar trough block.filled.full {
    background-color: red;
}
"""
        #load css stylesheet
        style_provider = Gtk.CssProvider()
        style_provider.load_from_data(css)

        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(),
            style_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

        self.bar = self.builder.get_object("lev4")
        self.bar.add_offset_value("alert", .9)

        print("low:  ", self.bar.get_offset_value("low"))
        print("high: ", self.bar.get_offset_value("high"))
        print("alert:", self.bar.get_offset_value("alert"))
        print("full: ", self.bar.get_offset_value("full"))

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Glade

07_css.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="window">
    <property name="can_focus">False</property>
    <property name="default_width">400</property>
    <property name="default_height">150</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="homogeneous">True</property>
        <child>
          <object class="GtkLevelBar" id="lev1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="margin_top">5</property>
            <property name="margin_bottom">5</property>
            <property name="value">0.20000000000000001</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkLevelBar" id="lev2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="margin_top">5</property>
            <property name="margin_bottom">5</property>
            <property name="value">0.5</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkLevelBar" id="lev3">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="margin_top">5</property>
            <property name="margin_bottom">5</property>
            <property name="value">0.80000000000000004</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkLevelBar" id="lev4">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="margin_top">5</property>
            <property name="margin_bottom">5</property>
            <property name="value">0.80000000000000004</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">3</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
</interface>

Bars

Progressbars und Levelbars

Glade

/images/06_progresslevel.thumbnail.png

Progressbar

Fortschrittsbalken zeigen für gewöhnlich den Status eines länger dauernden Prozesses an. Es gibt dabei zwei Modi:

  1. verhältnismäßige Anzeige, der Fortschritt wird mit einem Wert zwischen 0 und 1 ausgedrückt

  2. Aktivitätsmodus, ein beweglicher Block läuft nach zugewiesener Schrittweite hin und her

Zusätzlich besitzt das Widget eine optionale Textanzeige. Wird der Inhalt nicht spezifiziert, wird der Fortschritt in Prozent angezeigt.

Levelbar

Levelbars werden normalerweise als Füllstandsanzeiger genutzt. Der Füllstand wird dabei wie beim Fortschrittsbalken angezeigt, weitere Widget-Eigenschaften sind:

  1. Zwei Anzeigenmodi:

    1. continuous: ein einzelner Block repräsentiert den gegebenen Wert

    2. discrete: Levelbar wird in eine festgelegte Anzahl von Blöcken geteilt, ein Block steht für einen Wertebereich

  2. Festlegen von Minimal-/Maximalwert möglich, Standardwert ist 0 bzw. 1; beim Anzeigenmodus discrete entspricht der Maximalwert der Anzahl der Blöcke

  3. Farbliche Änderungen des Balkens bei Überschreiten bestimmter Werte (siehe CSS-Artikel)

Python

Progressbar

Im Beispiel repräsentiert der erste Balken den Wert, der zweite befindet sich im Aktivitätsmodus. Möchte man bei letzterem trotzdem eine Prozentangabe im Textfeld darstellen, muss man diesen manuell einsetzen:

widget.pulse()
widget.set_text("{} %".format(perc_value))

Levelbar

Was set_fraction für Progressbar, ist set_value für Levelbar. Im continuous-Modus ist dies selbsterklärend, im discrete-Modus muss man bedenken, wie viele Blöcke definiert wurden:

widget.set_value(fraction * blocks)

Auf die Funktion add_offset_value und Farbänderung des Balkens beim Überschreiten definierter Werte wird im CSS-Artikel eingegangen.

Listings

Python

06_progresslevel.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import time

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_go_clicked(self, widget):
        for i in range(101):
            x.progbar1.set_fraction(i / 100)
            x.progbar2.pulse()
            x.progbar2.set_text("{} %".format(i))
            x.levbar1.set_value(i / 100)
            x.levbar2.set_value((i / 100) * 5)
            time.sleep(.05)
            #interrupt main loop to update GUI
            while Gtk.events_pending():
                Gtk.main_iteration()
        x.progbar2.set_fraction(1)


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        self.builder.add_from_file("06_progresslevel.glade")
        self.builder.connect_signals(Handler())

        self.progbar1 = self.builder.get_object("prog1")
        self.progbar2 = self.builder.get_object("prog2")
        self.levbar1 = self.builder.get_object("lev1")
        self.levbar2 = self.builder.get_object("lev2")

        self.levbar2.add_offset_value("high", 4)
        self.levbar2.add_offset_value("full", 5)

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Glade

06_progresslevel.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="window">
    <property name="can_focus">False</property>
    <property name="default_width">400</property>
    <property name="default_height">150</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="homogeneous">True</property>
        <child>
          <object class="GtkProgressBar" id="prog1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="text" translatable="yes">Text hier</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkProgressBar" id="prog2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="pulse_step">0.14999999999999999</property>
            <property name="show_text">True</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkLevelBar" id="lev1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkLevelBar" id="lev2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="max_value">5</property>
            <property name="mode">discrete</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">3</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="go">
            <property name="label" translatable="yes">Go!</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="always_show_image">True</property>
            <signal name="clicked" handler="on_go_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">7</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
</interface>

Drei-Gänge-Menü

Menüs, Toolbars und Statusbars

Glade

/images/05_menutoolbar.thumbnail.png

Toolbar

Toolbars können verschiedene Widgets wie Buttons, Togglebuttons, Radiobuttons oder (Unter-)Menüs enthalten. Die Erstellung und Bearbeitung erfolgt analog zum Menü über "Edit...".

Statusbar

In der Statusbar können kurze Meldungen/Nachrichten eingeblendet werden. Die Meldungen werden analog zu einer Liste behandelt, das Widget bietet die Funktionen push und pop.

Python

Um Nachrichten an die Statusbar zu senden, bedient man sich einfach der Funktion

widget.push(content_id, message)

Wenn man Meldungen ausschließlich "obendrauf" einblendet, kann man als content_id eine beliebige Zahl angeben, zum Beispiel 0.

Listings

Python

05_menutoolbar.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_info_button_clicked(self, widget):
        x.sb_message("Öffne Info-Dialog")
        x.builder.get_object("about_dialog").show_all()

    def on_close_button_clicked(self, widget):
        x.sb_message("Schließe Info-Dialog")
        x.builder.get_object("about_dialog").hide_on_delete()

    def on_nothing_here(self, widget):
        x.sb_message("{}: Widget hat keine Funktion.".format(widget))


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        self.builder.add_from_file("05_menutoolbar.glade")
        self.builder.connect_signals(Handler())

        window = self.builder.get_object("window")
        window.show_all()

    def sb_message(self,message):
        self.builder.get_object("statusbar").push(0, message)

    def main(self):
        Gtk.main()


x = Example()
x.main()

Glade

05_menutoolbar.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="window">
    <property name="can_focus">False</property>
    <property name="resizable">False</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="margin_bottom">9</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkMenuBar">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_Datei</property>
                <property name="use_underline">True</property>
                <child type="submenu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-new</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                        <signal name="activate" handler="on_nothing_here" swapped="no"/>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-open</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                        <signal name="activate" handler="on_nothing_here" swapped="no"/>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-save</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                        <signal name="activate" handler="on_nothing_here" swapped="no"/>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-save-as</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                        <signal name="activate" handler="on_nothing_here" swapped="no"/>
                      </object>
                    </child>
                    <child>
                      <object class="GtkSeparatorMenuItem">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-quit</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                        <signal name="activate" handler="on_window_destroy" swapped="no"/>
                        <accelerator key="q" signal="activate" modifiers="GDK_CONTROL_MASK"/>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_Hilfe</property>
                <property name="use_underline">True</property>
                <child type="submenu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkImageMenuItem" id="menu_info">
                        <property name="label">gtk-about</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                        <signal name="activate" handler="on_info_button_clicked" swapped="no"/>
                        <accelerator key="i" signal="activate" modifiers="GDK_CONTROL_MASK"/>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkToolbar">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="toolbar_style">both</property>
            <child>
              <object class="GtkToolButton">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">toolbutton</property>
                <property name="use_underline">True</property>
                <property name="stock_id">gtk-yes</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="homogeneous">True</property>
              </packing>
            </child>
            <child>
              <object class="GtkMenuToolButton">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">menu</property>
                <property name="use_underline">True</property>
                <property name="stock_id">gtk-print</property>
                <child type="menu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkMenuItem">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="label" translatable="yes">submenuitem 1</property>
                        <property name="use_underline">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkMenuItem">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="label" translatable="yes">submenuitem 2</property>
                        <property name="use_underline">True</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="homogeneous">True</property>
              </packing>
            </child>
            <child>
              <object class="GtkToggleToolButton">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">Toggle</property>
                <property name="use_underline">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="homogeneous">True</property>
              </packing>
            </child>
            <child>
              <object class="GtkSeparatorToolItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="homogeneous">True</property>
              </packing>
            </child>
            <child>
              <object class="GtkRadioToolButton" id="tb_radio1">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">Radio</property>
                <property name="use_underline">True</property>
                <property name="active">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="homogeneous">True</property>
              </packing>
            </child>
            <child>
              <object class="GtkRadioToolButton">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">Radio</property>
                <property name="use_underline">True</property>
                <property name="active">True</property>
                <property name="group">tb_radio1</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="homogeneous">True</property>
              </packing>
            </child>
            <child>
              <object class="GtkRadioToolButton">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">Radio</property>
                <property name="use_underline">True</property>
                <property name="active">True</property>
                <property name="group">tb_radio1</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="homogeneous">True</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkSeparator">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">3</property>
          </packing>
        </child>
        <child>
          <object class="GtkStatusbar" id="statusbar">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="margin_left">10</property>
            <property name="margin_right">10</property>
            <property name="margin_start">10</property>
            <property name="margin_end">10</property>
            <property name="margin_top">6</property>
            <property name="margin_bottom">6</property>
            <property name="orientation">vertical</property>
            <property name="spacing">2</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">4</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
  <object class="GtkAboutDialog" id="about_dialog">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Info</property>
    <property name="resizable">False</property>
    <property name="modal">True</property>
    <property name="window_position">center-on-parent</property>
    <property name="destroy_with_parent">True</property>
    <property name="type_hint">dialog</property>
    <property name="deletable">False</property>
    <property name="transient_for">window</property>
    <property name="program_name">Info Dialog</property>
    <property name="version">0.1</property>
    <property name="comments" translatable="yes">Platz für mehr Blabla</property>
    <property name="website">www.example.com</property>
    <property name="logo_icon_name">image-missing</property>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <property name="layout_style">end</property>
            <child>
              <object class="GtkButton" id="close_button">
                <property name="label">gtk-close</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
                <signal name="clicked" handler="on_close_button_clicked" swapped="no"/>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
</interface>

Clickbaiting

Switch, Checkbox, Radiobutton - mehr Elemente zum Anklicken

In diesem Artikel wird exemplarisch die Verwendung von Switches, Checkboxen und Radiobuttons vorgestellt. Folgend werden weitere Steuerungs- und Anzeigenelemente verwendet, es wird aber kein Anspruch auf Vollständigkeit erhoben, da die Verwendungsprozedur praktisch nach folgendem Schema funktioniert:

  1. Container (Box, Leiste etc.) für Element anlegen

  2. Element hinzufügen

  3. Element mit einer Bezeichnung versehen (bei Elementen ohne Interaktion wie Boxen oder Trennlinien kann darauf verzichtet werden)

  4. gewünschtem Signal eine Funktion zuweisen

  5. (optional) Signal-/Funktionsaufruf in der Vorschaufunktion testen

  6. Funktion im Programmcode schreiben

Alle verfügbaren GTK+-Klassen und ihre Funktionen findet man unter Python GI API Reference >> Gtk 3.0 >> Classes.

/images/04_clickableelements.thumbnail.png

Glade

Switch oder Schalter

Ein Switch ist ein einfacher Ein-/Aus-Schalter mit, Überraschung!, zwei Zuständen. Der Zustand lässt sich über das Signal state_set abrufen.

Checkbox

Checkboxen sind Togglebuttons in anderem Outfit, hier wird demnach das Signal toggled belegt.

Radiobutton

Radiobuttons dienen der Auswahl _eines_ Listenpunktes aus einer gegebenen Liste. Das Element selbst funktioniert ebenfalls wie ein Togglebutton (das Signal toggled zuweisen).

Zusätzlich werden die zusammengehörigen Listenpunkte einer Gruppe zugeordet. Dies bewerkstelligt man einfach, indem man alle Radiobuttons unter "Allgemein > Knopfattribute > Gruppe" an einem "führenden Radiobutton" ausrichtet.

Python

Da Checkbox und Radiobutton Togglebuttons sind, wird hier der Status über die Funktion widget.get_active() abgerufen.

Beim Switch wird dem Signal state_set ein Parameter übergeben, der True/False ausgibt:

def on_switch_state_set(self, widget, state):
    if state is True:
        print("switch is on")
    else:
        print("switch is off")

Listings

Python

04_clickableelements.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_switch_state_set(self, widget, state):
        if state is True:
            print("switch is on")
        else:
            print("switch is off")

    def on_cbutton_toggled(self, widget):
        if widget.get_active():
            print("checkbox checked")
        else:
            print("checkbox unchecked")

    def on_rbutton1_toggled(self, widget):
        if widget.get_active():
            print("radiobutton selection changed to 1")

    def on_rbutton2_toggled(self, widget):
        if widget.get_active():
            print("radiobutton selection changed to 2")

    def on_rbutton3_toggled(self, widget):
        if widget.get_active():
            print("radiobutton selection changed to 3")


class Example:

    def __init__(self):

        self.gladefile = "04_clickableelements.glade"
        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.gladefile)
        self.builder.connect_signals(Handler())

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Glade

04_clickableelements.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="window">
    <property name="can_focus">False</property>
    <property name="default_width">150</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="margin_left">4</property>
        <property name="margin_right">4</property>
        <property name="margin_top">4</property>
        <property name="margin_bottom">4</property>
        <property name="orientation">vertical</property>
        <property name="spacing">1</property>
        <child>
          <object class="GtkSwitch" id="switch">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="active">True</property>
            <signal name="state-set" handler="on_switch_state_set" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkCheckButton" id="cbutton">
            <property name="label" translatable="yes">Checkbox</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">False</property>
            <property name="active">True</property>
            <property name="draw_indicator">True</property>
            <signal name="toggled" handler="on_cbutton_toggled" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkSeparator">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">3</property>
          </packing>
        </child>
        <child>
          <object class="GtkRadioButton" id="rbutton1">
            <property name="label" translatable="yes">Alternative 1</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">False</property>
            <property name="active">True</property>
            <property name="draw_indicator">True</property>
            <signal name="toggled" handler="on_rbutton1_toggled" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">4</property>
          </packing>
        </child>
        <child>
          <object class="GtkRadioButton" id="rbutton2">
            <property name="label" translatable="yes">Alternative 2</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">False</property>
            <property name="active">True</property>
            <property name="draw_indicator">True</property>
            <property name="group">rbutton1</property>
            <signal name="toggled" handler="on_rbutton2_toggled" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">5</property>
          </packing>
        </child>
        <child>
          <object class="GtkRadioButton" id="rbutton3">
            <property name="label" translatable="yes">Alternative 3</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">False</property>
            <property name="active">True</property>
            <property name="draw_indicator">True</property>
            <property name="group">rbutton1</property>
            <signal name="toggled" handler="on_rbutton3_toggled" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">6</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
</interface>

Durchzug

Inhalt

Fenster und Dialoge öffnen und schließen

Glade

Mit Glade lassen sich verschiedene Fensterarten und Dialoge erstellen. Im Beispiel hat das Hauptfenster zwei Buttons, ein Button öffnet ein Info-Fenster, der andere schließt das Hauptfenster und öffnet ein anderes Fenster, das jeweils das gleiche tut.

/images/03_changewindow.thumbnail.png

Es werden insgesamt 7 Signale angelegt:

  • Fenster, jeweils
    • Info-Button (Headerbar links): clicked

    • Wechsel-/"Ok"-Button (Headerbar rechts): clicked

    • Schließen/Beenden: destroy

  • Info-Dialog
    • Schließen-Button: destroy

03_changewindow.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="win1">
    <property name="can_focus">False</property>
    <property name="resizable">False</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="title">Fenster 1</property>
        <property name="subtitle">Untertitel</property>
        <property name="show_close_button">True</property>
        <child>
          <object class="GtkButton" id="info_button1">
            <property name="label">gtk-about</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
            <signal name="clicked" handler="on_info_button_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="button1">
            <property name="label">gtk-ok</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
            <signal name="clicked" handler="on_button1_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="margin_bottom">9</property>
        <property name="orientation">vertical</property>
        <property name="spacing">10</property>
        <child>
          <object class="GtkLabel" id="label1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis placerat, dui at sollicitudin mollis, lectus risus porttitor felis, sed malesuada purus eros sit amet magna. Nunc consectetur rutrum gravida. Mauris sed enim vitae orci mattis pretium eu interdum arcu. Morbi sed enim non erat bibendum tincidunt. Aenean nunc nisl, sagittis sit amet tellus ac, condimentum ullamcorper mi. Cras ornare faucibus laoreet. Quisque quis sagittis est, et hendrerit libero.</property>
            <property name="wrap">True</property>
            <property name="max_width_chars">80</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
  <object class="GtkAboutDialog" id="about_dialog">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Info</property>
    <property name="resizable">False</property>
    <property name="modal">True</property>
    <property name="window_position">center-on-parent</property>
    <property name="destroy_with_parent">True</property>
    <property name="type_hint">dialog</property>
    <property name="deletable">False</property>
    <property name="transient_for">win1</property>
    <property name="program_name">Info Dialog</property>
    <property name="version">0.1</property>
    <property name="comments" translatable="yes">Platz für mehr Blabla</property>
    <property name="website">www.example.com</property>
    <property name="logo_icon_name">image-missing</property>
    <child>
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <property name="layout_style">end</property>
            <child>
              <object class="GtkButton" id="close_button">
                <property name="label">gtk-close</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
                <signal name="clicked" handler="on_close_button_clicked" swapped="no"/>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
  <object class="GtkWindow" id="win2">
    <property name="can_focus">False</property>
    <property name="resizable">False</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="title">Fenster 2 </property>
        <property name="subtitle">Anderer Untertitel</property>
        <property name="show_close_button">True</property>
        <child>
          <object class="GtkButton" id="info_button2">
            <property name="label">gtk-about</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
            <signal name="clicked" handler="on_info_button_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="button2">
            <property name="label">gtk-ok</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
            <signal name="clicked" handler="on_button2_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="margin_bottom">9</property>
        <property name="orientation">vertical</property>
        <property name="spacing">10</property>
        <child>
          <object class="GtkLabel" id="label2">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">In sagittis purus nec eleifend dignissim. Curabitur venenatis eleifend leo ac tincidunt. Etiam ut consequat neque. Aenean in libero placerat, iaculis est quis, blandit nulla. Nulla euismod cursus nisl efficitur imperdiet. Sed vel augue vitae dui congue eleifend id eu libero. Cras laoreet velit nibh, et pharetra ante pharetra id. Nullam mollis arcu a nibh pulvinar, sed volutpat quam facilisis. Vivamus quis leo quis orci aliquam fermentum. Donec varius accumsan nisi eu ullamcorper. Integer condimentum, eros sit amet convallis vehicula, elit leo mattis risus, quis suscipit turpis nibh sed nulla. Sed id justo ut magna commodo eleifend. Praesent nunc arcu, elementum eu dolor nec, rutrum molestie mauris.</property>
            <property name="wrap">True</property>
            <property name="max_width_chars">80</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Python

Die entscheidenden Funktionen in der Handhabung von Fenstern sind

# Fenster anzeigen
Gtk.Builder.get_object("name").show_all()

# Fenster ausblenden, kann mit show_all() reaktiviert werden
Gtk.Builder.get_object("name").hide_on_delete()

# Fenster schließen, Gtk wird dabei beendet
Gtk.main_quit()

Die Buttons zum Öffnen des Info-Dialogs und zum Beenden des Programms führen die jeweils identische Funktion aus; es werden demzufolge nur 5 Funktionen in der Handler-Klasse benötigt.

Das vollständige Beispiel ist dann:

03_changewindow.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_button1_clicked(self, widget):
        x.window.hide_on_delete()
        x.set_window("win2")

    def on_button2_clicked(self, widget):
        x.window.hide_on_delete()
        x.set_window("win1")

    def on_info_button_clicked(self, widget):
        x.about.show_all()

    def on_close_button_clicked(self, widget):
        x.about.hide_on_delete()


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        self.builder.add_from_file("03_changewindow.glade")
        self.builder.connect_signals(Handler())

        self.about = self.builder.get_object("about_dialog")

        self.set_window("win1")

    def set_window(self, win):
        self.window = self.builder.get_object(win)
        self.window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Siehe auch Fortsetzung-Artikel zu Dialogen.

Push the button

Buttons und Labels

Glade

Ausgehend vom letzten Beispiel werden nun ein paar Elemente hinzugefügt - ein Label, ein Button und ein Togglebutton. Jedes Anzeigen- oder Steuerungselement benötigt je ein Container. In diesem Beispiel werden vertikale Boxen angelegt, diese lassen sich jederzeit erweitern, es ist auch möglich, Container beliebig ineinander zu verschachteln.

Den Elementen Button und Togglebutton wird auf clicked bzw. toggled ein Signal zugewiesen. Label dient nur der Anzeige von Text, hier wird kein Signal benötigt.

In der Vorschauansicht kann man testen, ob die korrekte Reaktion ausgelöst wird.

/images/02_gladepreview.thumbnail.png

Python

Ein Klick auf den Button soll in der Labelanzeige einen anderen Text anzeigen, hier wird zufällig ein Element aus einer Liste ausgewählt.

Alle Gtk.Builder-Objekte können über die Funktion get_object angesprochen werden:

Gtk.Builder.get_object("name").funktion(options)

# Beispiel GtkLabel
Gtk.Builder.get_object("label_name").set_text("neuer Text")

Der Togglebutton soll die Labelanzeige leeren und den Button inaktivieren und bei erneutem Klick wieder freigeben.

Der Zustand des Togglebuttons kann mit der Funktion get_active() abgerufen werden (gibt True/False zurück).

Abhängig vom verwendeten Widget erfordert die Signal-Funktion mindestens einen Parameter.

def on_t_button_toggled(self, widget):
    if widget.get_active():
        # do something
    else:
        # do something different

Listings

Python

02_labelbutton.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import random

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()

    def on_button_clicked(self,widget):
        new_text = random.choice(x.label_texts)
        x.builder.get_object("label").set_text(new_text)

    def on_t_button_toggled(self,widget):
        if widget.get_active():
            x.builder.get_object("label").set_text("")
            x.builder.get_object("button").set_sensitive(False)
        else:
            x.builder.get_object("button").set_sensitive(True)


class Example:

    def __init__(self):

        self.gladefile = "02_labelbutton.glade"

        self.label_texts = ["The things you used to own, now they own you.",
                            "I am Jack's complete lack of surprise. I am Jack's Broken Heart.",
                            "On a long enough time line, the survival rate for everyone drops to zero.",
                            "Sticking feathers up your butt does not make you a chicken!",
                            "I am Jack's smirking revenge."]

        self.builder = Gtk.Builder()
        self.builder.add_from_file(self.gladefile)
        self.builder.connect_signals(Handler())

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Glade

02_labelbutton.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkImage" id="image1">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="stock">gtk-dialog-error</property>
  </object>
  <object class="GtkApplicationWindow" id="window">
    <property name="width_request">500</property>
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Titel</property>
    <property name="resizable">False</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="width_request">200</property>
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="homogeneous">True</property>
        <child>
          <object class="GtkLabel" id="label">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Label-Text kann in Glade unter "Beschriftung" eingegeben werden</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="button">
            <property name="label" translatable="yes">Label-Text ändern</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_button_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkToggleButton" id="t_button">
            <property name="label" translatable="yes">Oberen Button sperren</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="image">image1</property>
            <property name="image_position">top</property>
            <property name="always_show_image">True</property>
            <signal name="toggled" handler="on_t_button_toggled" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
</interface>

Fenster mit Aussicht

Minimalbeispiel

Glade

Nach dem Start präsentiert sich Glade dreigeteilt, links ist die Fenster-/Widget-Auswahl, in der Mitte die Projektansicht und rechts eine Baumansicht des Projekts, im unteren Bereich können Eigenschaften und Signale editiert werden.

Nun erstellt man ein Fenster und gibt ihm eine Kennung. Mit dieser Kennung wird das Objekt im Programmcode angesprochen.

/images/01_glade.thumbnail.png

Bemerkung

Die Glade-Oberfläche wurde mit der Version 3.22 modernisiert. Details dazu gibt es im Artikel Überarbeitete Oberfläche in Glade 3.22.

Um die Ausführung von Funktionen durch ein Widget zu initiieren, müssen sie mit Signalen gekoppelt werden. Signale können je nach Objektart verschieden ausgelöst werden, durch Anklicken, Markieren, Editieren, Schalten etc.

Um in diesem Beispiel das Programmfenster mit dem Schließen-Button zu schließen, wird das Signal destroy benötigt. Beim Funktionsnamen hilft die Vorschlagsfunktion nach dem Schema on_kennung_signal. Ich empfehle, diesen Vorschlägen im Allgemeinen zu folgen, sie erleichtern die Tipparbeit und hält die Codestruktur konsistent.

/images/01_destroysignal.thumbnail.png

Glade selbst erzeugt keinen Programmcode, sondern eine XML-Datei des Typs GtkBuilder.

Python

First things first. Die GtkBuilder-Funktionen stehen im Gtk-Modul aus den Python GObject Introspection-Bindings zur Verfügung:

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

Nach dem Aufruf von Gtk.Builder() wird die Glade-Datei geladen.

builder.add_from_file(gladefile)

Um die Übersicht zu bewahren, können dies auch mehrere Dateien sein, es sollte allerdings auf eine eindeutige Kennung geachtet werden. Bei doppelten gleichen Kennungen kann nur die zuletzt geladene mit get_object(kennung) angesprochen werden.

Anschließend werden die Signale verbunden. Meine Empfehlung ist hier, die dazugehörigen Funktionen der Übersicht wegen in eine eigene Klasse auszulagern.

self.builder.connect_signals(Handler())

Dieses Beispiel-Skript öffnet ein leeres Fenster, das per Schließen-Button beendet werden kann.

Ohne Glade

Das oben konstruierte Beispiel entspricht dem Basisbeispiel im Python GTK+ 3 Tutorial:

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

win = Gtk.Window()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

Man sollte sich von der Kürze dieses Beispiels nicht täuschen lassen. Die eigentlichen Elemente, Boxen, Widgets, Buttons, Leisten etc. fehlen hier komplett.

Listings

Python

01_minimal.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

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


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()


class Example:

    def __init__(self):

        self.builder = Gtk.Builder()
        self.builder.add_from_file("01_minimal.glade")
        self.builder.connect_signals(Handler())

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

Glade

01_minimal.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkApplicationWindow" id="window">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Titel</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <placeholder/>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
</interface>

Tutorial-Reihe zu Glade

Motivation

Bei der Erstellung der grafischen Oberfläche sowohl für gpt als auch NoN habe ich auf Glade zurückgegriffen, einem grafischen Werkzeug, mit dem man relativ einfach GTK+-Oberflächen erstellen kann.

Mit Glade erstellte Projektdateien sind GtkBuilder-XML-Dateien, die Verbindung zum eigentlichen Programm erfolgt über Signale, dabei werden zahlreiche Programmiersprachen unterstützt. Hier werde ich Python verwenden.

Da es in den letzten Jahren Versionssprünge sowohl bei Python als auch GTK+ gegeben hat (jeweils von 2.x auf 3.x), gibt es viele Dokumentationen und Tutorials, die nicht 1:1 anwendbar sind, d.h. die Funktionen sind meist gleich, nur die Syntax unterscheidet sich minimal (siehe Links).

An dieser Stelle versuche ich aktuell zu bleiben, derzeit mit Python 3.7.1 und Glade 3.22.1.

Dateien

Nicht exklusiv

GTK+-Elemente können natürlich auch ohne Glade direkt im Quellcode des Programms erstellt werden. Es ist möglich, beide Optionen parallel zu verwenden oder auch im Entwicklungs-Verlauf das eine gegen das andere zu ersetzen.

Da Glade in verschiedenen Programmiersprachen eingesetzt werden kann, ist es ebenso denkbar, Programme in verschiedenen Sprachen mit derselben Oberfläche zu erstellen (migrieren).