Perl/Tk
Willemers Informatik-Ecke

Grafische Oberfläche: Tk

Dieser Ausflug in die Welt der grafischen Programme kann natürlich das Thema nicht völlig erschöpfen. Es macht aber einfach Spaß, wenn man sieht, wie schnell man mit Perl grafische Oberflächen erstellen kann.

Um mit Perl X-Clients zu schreiben, bedienen Sie sich des Moduls Tk. Ursprünglich wurde Tk für die Skriptsprache Tcl entwickelt. Aber auch mit Perl können Sie Tk benutzen. Das folgende Beispiel erstellt ein Fenster mit einem Label, also einer Anzeige für Text, in dem das Wort >>Huhu<< steht. Darunter erscheint ein Button mit der Aufschrift >>Schluss<<, der das Programm beendet.


Grafisches Minimalprogramm

Ablauf

Im ersten Schritt wird das Hauptfenster generiert. Dabei wird die Methode new der externen Klasse MainWindow verwendet, um ein Fenster zu erzeugen, das anschließend über die Objektvariable $meinFenster erreichbar ist. Falls sich das für Sie sehr nach objektorientiertem Vokabular anhört, liegen Sie richtig. Mit Perl können Sie durchaus objektorientiert programmieren. Dann werden nacheinander die Methoden Label und Button aufgerufen, die ein entsprechendes Element generieren:

#!/usr/bin/perl -w
use Tk;

my $meinFenster = MainWindow->new; $meinFenster->Label( -text=>"Huhu" )->pack; $meinFenster->Button(-text => "Schluss", -command => [$meinFenster => 'destroy'] )->pack; MainLoop;

Gerade beim Button ist schön zu sehen, dass bei der Erstellung bereits alle relevanten Eigenschaften eines Buttons erzeugt werden, also die Beschriftung (text) und die aufzurufende Funktion bei Betätigung des Buttons. Letzteres führt dazu, dass das Fenster zerstört und damit das Programm beendet wird. Zu guter Letzt läuft das Programm in die bei allen grafischen Oberflächen typische Ereignisschleife, hier MainLoop. Das bedeutet, dass das Programm nicht mehr von sich aus agiert, sondern auf Benutzeraktivitäten wartet und dann mit den hinterlegten Rückruffunktionen reagiert.

->

Sie finden im Listing zwei ungewöhnliche Pfeile, die im bisherigen Kapitel nicht behandelt wurden. Der Pfeil, der sich aus einem Bindestrich und einem Größerzeichen zusammensetzt, kann man sich als Zeiger auf Bestandteile grafischer Objekte vorstellen. So kann ein Fenster Funktionen aufrufen, die sich auf das Fenster beziehen, oder Sie können die zum Fenster gehörigen Variablen auslesen.

=>

Der zweite Pfeil, der aus einem Gleichheits- und einem Größerzeichen besteht, ist einfacher zu erläutern. Man kann ihn sich als Ersatz für ein Komma vorstellen. Sie können ihn auch durch ein Komma ersetzen. Der Pfeil hat den Vorteil, Parameterpaare deutlicher hervorzuheben. So bezieht sich das >>Huhu<< auf die Option -text.

pack

Die Funktion pack() muss auf alle Elemente des Fensters angewandt werden, damit die Elemente im Fenster angeordnet und damit sichtbar werden. Solange nur mit wenigen Elementen gearbeitet wird, reicht diese Erklärung. Auf das Thema und die verschiedenen Anordnungsmöglichkeiten wird später genauer eingegangen.

Widgets und Ressourcen

Die Kontrollelemente unter X gehören zu den Widgets. Widgets sind eigenständige Bestandteile der grafischen Oberfläche. Neben den Kontrollelementen gibt es Containerwidgets, die andere Widgets aufnehmen und diese ausrichten. Wie schon im Kapitel über das X Window System erläutert wurde, haben Widgets Eigenschaften, die über ihre Namen zu beeinflussen sind. Diese Eigenschaften nennt man Ressourcen.

Ressource Bedeutung Werte
-background -bg Hintergrundfarbe Farbangabe
-foreground -fg Vordergrundfarbe Farbangabe
-relief 3D-Effekt raised, sunken, flat, ridge (umrahmt)
-borderwidth -bd Randstärke Numerischer Wert
-anchor Ausrichtung 'n', 'w', 's', 'e' oder 'center'

Nachträgliches Ändern

Sie können Ressourcen nachträglich ändern, indem Sie die Funktion configure des jeweiligen Widgets mit der Ressource als Parameter aufrufen. Beispielsweise können Sie den Text eines Labels folgendermaßen ändern:

my $anzeige = $mw->Label( -text=>'Beschriftung' );
...
$anzeige->configure(-text=>'Anderer Text');

Weitere Informationen gibt es unter man Tk::options.

Kontrollelemente

Für alle interaktiven Programme sind die Kontrollelemente zentraler Bestandteil ihrer Fenster. In diesem Abschnitt erläutere ich die wichtigsten Elemente, ihre Ressourcen und den Umgang mit ihnen.

Label

Ein Label ist ein Beschriftungsfeld. Sie können es mit einem Text und einem Bild füllen. Davon abgesehen, hat es wenig Temperament. Es reagiert auf keine Ereignisse. In den allermeisten Fällen dient es nur zur Beschriftung.

Ressource Bedeutung Werte
-text Beschriftung Zeichenkette
-font Schrift Zeichensatzbezeichnung

Message

Eine Sonderform des Labels ist die Message. Man kann ihr längere Texte angeben, die sie je nach Platzangebot selbstständig umbricht.

Button

Der einfache Button ist ein Knopf, der auf das Anklicken mit der linken Maustaste reagiert. Ansonsten kann man ihn wie ein Label mit einem Text oder einem Bild versehen.

$meinFenster->Button(-text => "Schluss",
                     -command => sub{faerbe('red')});

Neben der Beschriftung ist die wichtigste Eigenschaft eines Buttons, dass man ihn anklicken kann. Mit der Option -command wird festgelegt, welche Aktion dann ausgelöst wird. Oben wird die Funktion faerbe() aufgerufen, wenn der Button angeklickt wird. Man nennt eine solche Funktion auch Callback. Solche Callbacks sind die Schnittstelle zwischen den Benutzeraktionen an der Oberfläche und dem eigentlichen Programm.

Checkbutton

Der Checkbutton ist eine Sonderform des Buttons. Durch Anklicken erhält er eine Marke. Wenn Sie ihn ein weiteres Mal anklicken, geht der Button in seinen Ursprungszustand zurück:

my $schoen=$mw->Checkbutton(-text =>ßchön",
                       -anchor=>'w')->pack(-fill,'x');
my $stark=$mw->Checkbutton(-text =>ßtark",
                       -anchor=>'w')->pack(-fill,'x');
my $klug=$mw->Checkbutton(-text =>"klug",
                       -anchor=>'w')->pack(-fill,'x');


Checkbutton-Widgets

Die Ressource -anchor bewirkt eine Ausrichtung nach links, wenn ihr Wert wie oben 'w' ist. Das wirkt aber nur dann, wenn bei der Funktion pack angegeben ist, dass das Widget den gesamten Raum in X-Richtung füllen soll. Auf die Ausrichtung und Anordnung von Widgets wird später näher eingegangen.

Radiobutton

Der Radiobutton ist eine andere Variante der Buttons. Man könnte ihn als eine Weiterentwicklung des Checkbuttons ansprechen. Mehrere Buttons werden zusammengefasst. Es darf nur einer angewählt sein. Er hat seinen Namen von den Stationstasten eines Radios, von denen ja auch immer nur eine gleichzeitig angewählt sein kann. Wird eine andere gedrückt, springt die bisher gedrückte Taste heraus.


Radiobutton-Widgets

my $mw = MainWindow->new;
my $radvar='red';

$mw->Radiobutton(-text => 'rot', -variable=>$radvar, -value=>'red', -anchor=>'w')->pack(-fill,'x'); $mw->Radiobutton(-text => 'gelb', -variable=>$radvar, -value=>'yellow', -anchor=>'w')->pack(-fill,'x'); MainLoop;

Koordinierung über eine Variable

Relevant für die Funktionalität sind die Optionen -variable und -value. In der Variablen $radvar legen die Radiobuttons nicht nur ihren Wert ab, sondern stellen anhand der Variablen auch fest, ob sie angewählt sind oder nicht. Dazu hat jeder Radiobutton einen eigenen Wert, die hinter in der Ressource -value abgelegt ist.

Listbox

Eine Listbox kann mehrere Zeilen aufnehmen. Zunächst wird sie generiert, wie andere Kontrollelemente auch. Eine wichtige Ressource ist die Höhe, die angibt, wie viele Zeilen sichtbar sind. Die Listbox kennt die Kommandos insert zum Einfügen von Zeilen, delete zum Löschen und get, um den Inhalt einer Zeile auszulesen.

#!/usr/bin/perl -w
use strict;
use Tk;

my $mw = MainWindow->new;
my $list = $mw->Listbox(-height=> 5);
$list->pack;
$list->insert(0,'gelb','blau','grün','rot','schwarz','weiß');
MainLoop;

Ressource Werte
-height Anzahl der sichtbaren Zeilen

Scrollbars

Scrollbars (Rollbalken) werden im Zusammenhang mit anderen Widgets gebraucht, wenn der Raum im Fenster zu klein ist, um das Widget komplett darzustellen. Dann kann man einen Scrollbar verwenden, um den Ausschnitt auszuwählen, den man sehen möchte. Daher braucht der Scrollbar die Information, welches Widget er kontrollieren soll. Auf der anderen Seite braucht das kontrollierte Widget auch Informationen darüber, dass es von einem Scrollbar kontrolliert wird. Man löst das Problem, indem man zunächst das zu kontrollierende Widget erzeugt, dann den Scrollbar und schließlich dem Widget durch Ressourcenänderung mitteilt, dass es einen Scrollbar besitzt. Im folgenden Beispiel werden diese Schritte an einer Listbox demonstriert:

#!/usr/bin/perl -w
use strict;
use Tk;

my $mw = MainWindow->new;
# Erzeuge die Listbox
my $list = $mw->Listbox(-height=> 5)->pack(-side, 'left');
# Dann den passenden Scrollbar
my $scroll = $mw->Scrollbar(-command, [yview=>$list]);
$scroll->pack(-side,'right', -fill,'y');
# Nun existiert ein Scrollbar, und die Listbox sollte das wissen
$list->configure(-yscrollcommand => ['set', $scroll]);
# Fuelle Werte in die Listbox...
$list->insert(0,'gelb','blau','grün','rot','schwarz','weiß');
MainLoop;


Listbox mit Scrollbar

Scrolled

Da Scrollbars fast immer eingesetzt werden, um andere Widgets zu steuern, gibt es die Sonderform Scrolled. Damit werden ein kontrolliertes Widget und ein Scrollbar in einem Schritt erzeugt:

#!/usr/bin/perl -w
# use strict;
use Tk;

my $mw = MainWindow->new; my $scrlist = $mw->Scrolled(Listbox,-height,5,-scrollbars=>'e'); $scrlist->insert(0,'gelb','blau','grün','rot','schwarz','weiß'); $scrlist->pack; MainLoop;

Scrollbarposition

Hinter der Option -scrollbars steht in der Zeichenkette, an welcher Seite des Widgets der Scrollbar angebracht sein soll. Die Seite wird durch die englischen Kürzel für die Himmelsrichtungen (n, e, s, w) angegeben. Im Beispiel oben befindet sich der Scrollbar also an der rechten Seite. Durch Angabe der Zeichenkette 'se' wird sowohl rechts als auch unten ein Scrollbar angefügt.

Scale

Das Widget Scale ist ein Schieber, mit dem Sie Zahlenwerte einstellen können. Mit dem Kommando get lässt sich der Zahlenwert auslesen. Das folgende Beispiel erzeugt einen solchen Schieber und übernimmt den Wert in ein Label, wenn der Button gedrückt wird.


Scale-Widget

#!/usr/bin/perl -w
use strict;
use Tk;

my $mw = MainWindow->new;
my $anzeige = $mw->Label( -text=>"" )->pack;
my $scale = $mw->Scale(-from=>1, -to=>20, 
                       -orient=>'horizontal')->pack;
$mw->Button(-text => "Zeige", -command => sub{zeige()},)->pack;
MainLoop;

sub zeige {
  my $val = $scale->get;
  $anzeige->configure(-text=>$val);
}

Das Widget Scale verwendet folgende Ressourcen:

Ressource Werte
-label Beschriftung
-from Min. Wert (numerisch)
-to Max. Wert (numerisch)
-length Zahlenwert
-orientation horizontal oder vertical

Entry

Das Widget Entry dient zur Eingabe einer Zeichenkette. Das folgende Beispiel erzeugt ein Fenster mit einem Eingabefeld, einem Label und einem Button. Wenn Sie etwas in das Eingabefeld eintippen und dann den Button anklicken, wird der Inhalt im Label angezeigt.


Entry-Widget

#!/usr/bin/perl -w
use strict;
use Tk;

my $mw = MainWindow->new; my $anzeige = $mw->Label( -text=>"" )->pack; my $entry = $mw->Entry()->pack; $mw->Button(-text => "Zeige", -command => sub{zeige()},)->pack; MainLoop;

sub zeige { my $val = $entry->get; $anzeige->configure(-text=>$val); }

Ressource Werte
-label Beschriftung
-textvariable Variable

Neben diesen Grundfunktionen gibt es natürlich noch eine Menge Möglichkeiten, mit Eingabefeldern umzugehen. Dabei ist allein der Umgang mit Selektionen, also Markierungen im Text, sehr umfangreicht. Genauere Informationen bekommen Sie über man Tk::Entry.

Menüs

Zu jedem etwas größeren Programm gehört auch ein Menübaum. Dieser besteht aus mehreren Elementen, die im Programm nacheinander generiert werden. Zunächst wird die Menüleiste (engl. menubar) erzeugt. Sie befindet sich immer am Kopf eines Fensters. Diese Position nennt sich unter Tk toplevel. In der Menüleiste werden kaskadierende Buttons eingesetzt, die beim Anklicken das eigentliche Menü aufklappen. Dahinter endlich verbergen sich die eigentlichen Menüpunkte, die bei Tk Command heißen und wie gewöhnliche Buttons funktionieren. Auch ihnen wird bei Erzeugung eine Callbackfunktion zugeordnet. Allerdings ist das nicht zwingend. Im Beispiel ist als zweite Kaskade eine Farbenselektion eingebaut, hinter der sich Radiobuttons verbergen. Wegen des Unterhaltungswertes sind die Menüpunkte auch gleich in der Farbe dargestellt, die man mit ihnen anwählen kann.


Menüs

#!/usr/bin/perl -w
use strict;
use Tk;

my $mw = MainWindow->new;
my $toplevel = $mw->toplevel;

# Die Menueleiste wird in den Kopf des Fensters gehaengt
my $menubar = $toplevel->Menu(-type => 'menubar');
$toplevel->configure(-menu => $menubar);

# Nun bauen wir ein Datei-Menue
my $datei = $menubar->cascade(-label => '~Datei',
                              -tearoff => 0);

$datei->command(-label => 'Zeige', -command => sub{zeige()});
$datei->command(-label => 'Piep', -command => [$mw=>'bell']);
$datei->command(-label => 'Quit', -command => [$mw=>'destroy']);

my $farbe = $menubar->cascade(-label => '~Farbe',
                              -tearoff => 0);

$farbe->radiobutton(-label => 'rot',
                    -command => sub{faerbe('red')},
                    -background => 'red');
$farbe->radiobutton(-label => 'gelb',
                    -command => sub{faerbe('yellow')},
                    -background => 'yellow');


my $anzeige = $mw->Label( -text=>"" )->pack;
my $entry = $mw->Entry()->pack;
$mw->Button(-text => "Zeige",
            -command => sub{zeige()},
           )->pack;
MainLoop;

sub zeige {
  my $val = $entry->get;
  $anzeige->configure(-text=>$val);
}

sub faerbe {
  $entry->configure(-background => $_[0]);
}

Inhaltlich ist das Programm nichts anderes als das Beispielprogramm für das Eingabefeld Entry. Allerdings gibt es nun die Möglichkeit, aus dem Menü heraus die Zeigefunktion zu aktivieren.

Widgetanordnung

Im vorigen Abschnitt wurde von der Funktion pack() bereits ausgiebig Gebrauch gemacht. Die einfachste Variante ist der Aufruf ohne Parameter. Dann werden die Widgets nacheinander in das Fenster gestopft und ihre Anordnung ist eher zufällig. Immerhin erfüllen die Widgets auch so ihre Funktion.

-side

Durch die Option -side kann die Richtung angegeben werden, von der aus das Fenster aufgefüllt wird. Die üblichen Argumente der Option sind 'top' und 'left'. Dann werden die Widgets nacheinander von oben nach unten respektive von links nach rechts aufgefüllt. Natürlich gibt es auch die Argumente 'bottom' und 'right'. Der Vorgabewert ist übrigens 'top'. Das Vorgehen kann man sich vorstellen, als würde man mit der Kreissäge ein Stück des Fensters abschneiden. Ein Aufruf von pack() mit der Option -side 'top' würde also für das Widget die obere Kante des Fensters abtrennen. Der untere Rest kann nun noch frei verteilt werden. Diesen Rest können Sie wiederum an einer der Kanten absägen. Ein Problem ergibt sich, wenn Sie ein Fenster aus Label, Listbox, Scrollbar und zwei Buttons erstellen wollen, wie Sie es in der Abbildung sehen.


Mehrere Widgets, schön gepackt

Frame

Das Absägen des Titels ist noch trivial. In dem Moment, wo Sie aber links die Listbox abtrennen, bekommen Sie den Button nicht mehr unter die Listbox. Schneiden Sie dagegen die Listbox oben ab, fehlt Ihnen der Scrollbar an der rechten Seite. Man müsste Listbox und Scrollbar gemeinsam abtrennen können. Genau zu diesem Zweck gibt es Rahmen, die Tk Frame nennt. Sie werden wie ein normales Widget erzeugt, bekommen aber ihre Bedeutung beim Packen. Listbox und Scrollbar geben ihrer Packfunktion die zusätzliche Option -in und als Argument die Framevariable:

#!/usr/bin/perl -w
use strict;
use Tk;

my $mw = MainWindow->new;
my $titel = $mw->Label(-text=>"Oben",
                                -relief=>'groove');
my $framelist = $mw->Frame;
my $liste = $mw->Listbox(-height => 4);
my $scroll = $mw->Scrollbar(-command, [yview=>$liste]);
$liste->configure(-yscrollcommand => ['set', $scroll]);
my $lbut = $mw->Button(-text => "Links");
my $rbut = $mw->Button(-text => "Rechts");
$liste->insert(0,'eins','zwei','drei','vier','fünf');
# Anordnung der beteiligten Akteure
$titel->pack(-side,'top');
$framelist->pack(-side,'top');
$liste->pack(-in=>$framelist, -side,'left');
$scroll->pack(-in=>$framelist, -side,'right');
$lbut->pack(-side,'left');
$rbut->pack(-side,'right');
MainLoop;

Nun sind die Elemente an den gewünschten Positionen. Beim näheren Betrachten der folgenden Abbildung stellen Sie aber fest, dass der Scrollbar sehr klein geraten ist. Und auch die Überschrift ist ein wenig zu kurz gekommen.


Noch nicht perfekt

-fill

Ein Widget wird von sich aus nur so viel Platz in Anspruch nehmen, wie es benötigt. Diese Bescheidenheit führt aber zu optischen Unschönheiten, wenn beispielsweise drei unterschiedlich breite Buttons untereinander angeordnet sind. Hier kann man das Widget auffordern, den Raum in x-Richtung aufzufüllen, indem man als Argument für -fill 'x' angibt. Naheliegenderweise gibt es dann auch 'y' für das senkrechte Ausfüllen. Das Argument 'none' ist der Vorgabewert und füllt gar nichts aus.

Änderungen am Beispiel

Im Beispiel muss dem Scrollbar mitgeteilt werden, dass er den vertikalen Raum füllen soll und den Titel müssen Sie anweisen, die gesamte Breite zu belegen. Die geänderten Zeilen lauten:

$titel->pack(-side,'top',-fill=>'x');
$scroll->pack(-in=>$framelist, -side,'right',-fill=>'y');

Informationen

Für das Erstellen von grafischen Oberflächen mit Perl und Tk gibt es eine eigene Manpage. Der Aufruf lautet: man perl/Tk. Eine weitere Informationsquelle ist das Demoprogramm widget. Dies zeigt mehrere Beispiele für Widgets und die zugehörigen Quelltexte für Perl/Tk.