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.20.0 -->
<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>
      <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="GtkHeaderBar">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="title">Fenster 1</property>
            <property name="subtitle">Untertitel</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>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <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>
    <child type="titlebar">
      <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">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 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>
  <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>
      <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="GtkHeaderBar">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="title">Fenster 2 </property>
            <property name="subtitle">Anderer Untertitel</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>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <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>
    <child type="titlebar">
      <placeholder/>
    </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 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 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

Weiterlesen…

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

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.

/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, Widget, Buttons, Leisten etc. fehlen hier komplett.

Weiterlesen…

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.5.2 und Glade 3.20.0.

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).

Todo

  • WebKit2
  • Interaktion mit anderen Anwendungen mit und ohne Threading

Es begann ganz harmlos

Intro

GoPros sind aufmerksamkeitsheischende Zeitfresser.

Man versucht sie an schöne Orte zu führen, man produziert dabei Unmengen an Daten, die man dann in mehr oder weniger aufopferungsvoller Arbeit im Videoeditor möglichst ansehnlich zurecht schneidet.

Wenn man ganz schlau sein möchte, schreibt man ein paar Skripte, die einem etwas Arbeit abnehmen.

Und dann schreibt man eine kleine textbasierte UI, um das etwas abzurunden.

Und dann erwischt man sich dabei, eine graphische Oberfläche zusammen zu zimmern.

Und dann schreibt man das in ein Blog...