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.