Perl / Tk
Einführung in die Erstellung
grafischer Oberflächen
mit Perl / Tk
von Christian Dühl

Woraus bestehen grafische Oberflächen?
Grafische Oberflächen bestehen aus verschiedenen Fenster-Elementen, sogenannten Widgets, die zur Information des Anwenders oder zur Interaktion mit dem Anwender dienen.
Diese Widgets reichen von der Anzeige eines einfachen Wortes bis zu Textfeldern, die schon viele Eigenschaften eines Editors aufweisen und zu mächtigen vektororientierten Zeichenflächen.

Verschiedene Widgets an einem Beispiel
Rechts sieht man das Optionenmenü von Powerpoint, folgende Widgets lassen sich erkennen:
 Texteingabefelder (Entrys)
 Überschriften (Label)
 Auswahlfelder (Checkboxen)
 Rahmen (Frames)
 Schalter (Buttons)
 Reiter (NoteBooks)

Grundsätzliches zur Programmierung grafischer Oberflächen
Bei grafischen Oberflächen hat das Programm keine lineare Handlungssteuerung, wie man es von der „normalen“ Programmierung her gewohnt ist. Das liegt daran, dass man dem Benutzer nicht die Reihenfolge seiner Eingaben vorgeben möchte. Der Benutzer muss etwa im Dialog auf der vorigen Folie nicht von oben nach unten seine Wünsche eingeben, sondern kann auf ein beliebiges Widget klicken bzw. Text in beliebige Eingabefelder eintragen.
Diese Aktionen des Benuztzers nennt man Ereignisse („Events“) und der Programmablauf ist nun ereignisgesteuert, das heißt, dass das Programm auf bestimmte Ereignisse reagiert. Um dies tun zu können, gibt es eine Schleife, die immer wieder durchlaufen wird, und in der auftretende Ereignisse (vom Benutzer oder anders ausgelöst) behandelt werden.
Diese Ereignisschleife wird von Perl/Tk bereitgestellt und kann mit dem Befehl MainLoop()aufgerufen werden.

"Während solche Funktionen ablaufen"
Während solche Funktionen ablaufen, reagiert die Oberfläche nicht auf Benutzereingaben. Normalerweise ist dies nicht schlimm, wenn nach dem Druck auf etwa den Ok-Button kurz etwas gemacht wird und der Dialog dann beispielsweise verschwindet.
Sollte man längere Berechnungen durchführen, so kann man dies der Oberfläche vorher mitteilen (dafür gibt es die Methoden Busy und Unbusy). Dann wird unter anderem ein Wartecursor angezeigt.
Sehr lange Wartezeiten sollte man dann mit einem Fortschrittsbalken oder ähnlichem „unterhaltsam“ gestalten.
Nun kommen wir zum ersten Perl/Tk-Programm.

Hallo Welt
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
my $mw = MainWindow->new();
$mw->Label(-text => 'Hallo Welt')
    ->pack();
MainLoop();

Besprechung zu Hallo Welt
Wenn man mal die „Vorrede“ (Shebang, Pragmas und use Tk;) und den Aufruf der MainLoop weglässt, bleiben nur zwei Anweisungen übrig.
    my $mw = MainWindow->new();
Damit wird ein neues Objekt vom Typ MainWindow erzeugt. Mindestens ein solches Objekt wird in jedem Perl/Tk-Programm gebraucht. Mit
    $mw->Label(-text => 'Hallo Welt')
        ->pack();
wird eine Überschrift (Label) erzeugt, die den Text „Hallo Welt“ anzeigt. Genauer gesagt wird ein Label-Objekt erzeugt. Hinterher wird noch die Methode pack des erzeugten Objektes aufgerufen. Dann wird das Objekt wieder vergessen. Man könnte es mit „my $label =“ vorweg „einfangen“, aber es wird hier nicht weiter benötigt.
Die Methode pack() packt ein Widget ins erzeugende Fenster (oder Rahmen),  hier ins Hauptfenster. Dabei kann man nähere Angaben machen, wie das Objekt eingefügt werden soll. Die Darstellung hängt sehr davon ab, in welcher Reihenfolge die Objekte gepackt werden.

Geometrie-Manager
pack: Der Standardgeometriemanager, man umgibt Gruppen von Widgets mit (meist unsichtbaren) Rahmen, um diese gemeinsam anzuordnen.
grid: Ein Geometriemanager für tabellenartige Fenster.
place: Ein Geometriemanager zur freien Platzierung von Widgets. Mit diesem Manager können Widgets auch überlappt dargestellt werden.

Das Schalter Widget
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
my $mw = MainWindow->new();
$mw->Button(-text    => 'Klick mich',
            -command => sub { $mw->destroy() },
           )
     ->pack();
MainLoop();

Besprechung zum Schalter Widget
Mit den Zeilen
    $mw->Button(-text    => 'Klick mich',
                -command => sub { $mw->destroy() },
               )
         ->pack();
wird ein Schalter-Objekt (Button) erzeugt und gepackt. Der Schalter bekommt die Aufschrift „Klick mich“ und außerdem wird über -command eine sogenannte Callback-Funktion hinterlegt, die aufgerufen wird, wenn der Schalter ausgelöst wird.
Im Beispiel ist die Callback-Funktion eine Closure, man könnte statt sub {...} auch eine Referenz zu einer benannten Funktion angeben, etwa so:
        -command => \&beenden
Wieder könnte man das Schalter-Objekt mit „my $schalter =“ vor der obigen Zeile für die spätere Verwendung abspeichern, aber das ist hier noch nicht nötig.

Widget-Optionen
-option => Anweisung  ist typischer Tk-Stil um Parameter festzulegen. Hier ein paar Widget-Optionen, die man häufiger anwendet:
Widget-Option mögliche Werte Bedeutung
-anchor "n", "ne", "e", "se", "s", "sw", "w", Der Text wird an dieser Position verankert. "nw" oder "center "
-background Farbe (etwa 'SeaGreen3') Legt die Hintergrundfarbe des Widgets fest.
-borderwidth Betrag Ändert die Breite des Rahmens.
-command Callback (Funktionsreferenz, anonyme Subroutine Aktiviert die Callback-Funktion, wenn das
oder anonyme Liste, deren erstes Element Widget angeklickt wird.
eine Funktionsreferenz und deren weitere Elemente
Parameter für diese Funktion sind):
    \&myfunc
    sub { ... }
    [ \&myfunc, $arg1, $arg2, \@arg3 ]
-height Betrag (in Pixeln oder Zeilen) Ändert die Höhe des Widgets.
-width Betrag (in Pixeln oder Zeichen) Ändert die Breite des Widgets.
-justify "left", "right" oder "center“ Ausrichtung von mehrzeiligen Text.
-relief "flat", "groove", "raised", "ridge", Ändert den Kantentyp.
"sunken" oder "solid "
-state "normal", "disabled" oder "active " Der Status des Widgets.
-text "Text " Der Textstring, der im Widget angezeigt wird.
-textvariable Variablenreferenz: \$text Wie -text , nur das sich der Text ändert,
wenn die Variable $text sich ändert.
-title "Titel" Gibt dem Widget einen Titel.

Das Eingabefeld Widget
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
my $mw = MainWindow->new();
$mw->Entry()->pack();
MainLoop();

Besprechung zum Eingabefeld Widget
Mit der Zeile
    $mw->Entry()->pack();
wird ein Eingabefeld-Objekt (Entry) erzeugt und gepackt. In diesem Eingabefeld können einzeilige Eingaben des Benutzers entgegengenommen werden.
Man kann es auch beim Start des Programms schon mit Text vorbelegen, den der Benutzer dann ggf. ändern kann.
Möchte man später abfragen, was der Benutzer in das Eingabefeld eingetragen hat, so kann man dazu die Methode get verwenden. Dazu muss man das Eingabefeld bei der Erzeugung aber in einer Variablen abspeichern:
    my $entry = $mw->Entry()->pack();
Dann kann man den Inhalt mit
    my $input = $entry->get();
ermitteln.
Alternativ kann man beim Erzeugen des Eingabefeldes mit
    -textvariable => \$text
eine (vorher definierte) Variable an das Eingabeelement binden. Ändert man den Inhalt der Variablen, so ändert sich der angezeigte Text und umgekehrt.

Rahmenelement und Skalen Widget
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
my $mw = MainWindow->new();
my $f1 = $mw->Frame(-relief      => 'sunken',
                    -width       => '50',
                    -height      => '50',
                    -borderwidth => '1',
                   )
             ->pack();
$f1->Scale(-from   => 0,
           -to     => 100,
           -orient => "vertical",
           -label  => "Schieb mich",
          )
    ->pack();
MainLoop();

Besprechung zum Rahmenelement
Mit den Zeilen
    my $f1 = $mw->Frame(-relief      => 'sunken',
                        -width       => '50',
                        -height      => '50',
                        -borderwidth => '1',
                       )
                 ->pack();
wird ein Rahmenelement (Frame) $f1 mit den Maßen 50x50 Pixeln und der Randbreite 1  Pixel erzeugt und in das Hauptfenster $wm gepackt.
Es hat die Reliefart „sunken“. Außerdem gibt es noch die Reliefarten raised, flat, ridge, solid und groove (vergleiche perldoc Tk::options). Diese Reliefarten kann man auch bei anderen Widgets setzen, die Defaultwerte der Reliefart sind von Widget zu Widget unterschiedlich.

Besprechung zum Skalen Widget
Mit den Zeilen
    $f1->Scale(-from   => 0,
               -to     => 100,
               -orient => "vertical",
               -label  => "Schieb mich",
              )
        ->pack();
wird ein Skalenelement erzeugt und in den Rahmen $f1 gepackt. Das Skalenelement hat den Wertebereich von 0 bis 100, ist vertikal ausgerichtet und trägt das Label „Schieb mich“.
Nun kommen wir zu einem etwas komplexeren Programm, an dem einige weitere Widgets und das generelle Umgehen mit und das Auslesen von Widgets erläutert wird.

Auswahlmöglichkeiten: Radiobutton, Checkbox und Listbox

Folie 18

Folie 19

Folie 20

Folie 21

Folie 22

Besprechung zu Radiobutton, Checkbox und Listbox
Und so sieht das Programm dann aus. Der Benutzer kann mit Checkbuttons (jeder einzelne wählbar oder nicht wählbar), Radiobuttons (nur eine Auswahl pro Gruppe) und der Listbox (hier: mehrere Einträge gleichzeitig auswählbar) seine „Bestellung“ zusammenklicken und dann per OK-Button absenden.

"Sahne"
Sahne      : ja
Extrawaffel: ja
Streusel   : Zartbitterabrieb
Sauce      : Schokoladensauce
Vanille
Schokolade
Malaga
Wallnuss
weiße Schokolade
Aber natürlich könnte hier auch etwas anderes passieren, etwa die Bestellung per E-Mail an den nächsten Eisladen zu verschicken oder dergleichen.

Besprechung zum Geometriemanager pack
Hier tauchten nun zum ersten Mal Optionen der Methode pack auf. Etwa beim Schalter:
    pack(-side   => 'bottom',
         -expand => 0,
         -fill   => 'none',
         -ipadx  => 20,
         -pady   => 2,
        );
pack-Option mögliche Werte Bedeutung
-side "left", "right", "top" oder "bottom" Platziert das Widgetrechteck an die angegebene Seite
des Fensters oder Frames.
-fill "none", "x", "y" oder "both" Das Widgetrechteck breitet sich in die angegebene
Richtung aus.
-expand 1 oder 0 Das Widget füllt den Platz im Widgetrechteck aus
(oder nicht).
-ipadx Betrag in Pixeln Definiert einen horizontalen Abstand um das Widget
(dabei wird es um 2xBetrag in horizontaler Richtung
vergrößert).
-ipady Betrag in Pixeln Definiert einen vertikalen Abstand um das Widget
(dabei wird es um 2xBetrag in vertikaler Richtung
vergrößert).
-padx Betrag Setzt links und rechts je Betrag Pixel Polster ein.
-pady Betrag Setzt oben und unten je Betrag Pixel Polster ein.
-anchor "n", "ne", "e", "se", "s", "sw", "w", "nw“ Verankert das Widget in seinem Rechteck an der
oder "center“ angegebenen Stelle.

Rollbalken Widget
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
my $mw  = MainWindow->new();
my $box = $mw->Scrolled('Listbox',
                        -scrollbars => 'oe',
                        -height     => 5,
                       )
                 ->pack(-side       => 'left',
                        -fill       => 'both',
                        -expand     => 1,
                       );
$box->insert('end', $_)
    for qw(Eins Zwei Drei Vier Fünf Sechs Sieben Acht Neun Zehn);
MainLoop();

Besprechung zum Rollbalken Widget
Mit den Zeilen
    my $box = $mw->Scrolled('Listbox',
                            -scrollbars => 'oe',
                            -height     => 5,
                           )
wird eine Listbox erzeugt, die über einen optionalen „östlichen“ Rollbalken (Scrollbar) verfügt und fünf Zeilen hoch ist. Mit
    $box->insert('end', $_)
        for qw(Eins Zwei Drei Vier Fünf Sechs
               Sieben Acht Neun Zehn);
werden nacheinander ans Ende der Listbox zehn Werte eingefügt. Da die Listbox nur fünf Zeilen hoch ist, wird der Rollbalken benötigt (und deshalb angezeigt).

Zeichenflächenelement
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
my $mw = MainWindow->new();
my $c1 = $mw->Canvas(-width  => '350',
                     -height => '350',
                    )
              ->pack();
$c1->createLine( 25, 175, 325, 175,
                -arrow => 'last', );
$c1->createText( 335, 175,
                -fill  => 'blue',
                -text  => 'X', );
$c1->createLine(175, 325, 175,  25,
                -arrow => 'last', );
$c1->createText(175,  15,
                -fill  => 'darkgreen',
                -text => 'Y',
               );
MainLoop();

Besprechung zum Zeichenflächenelement
Mit den Zeilen
    my $c1 = $mw->Canvas(-width  => '350',
                         -height => '350',
                        )
wird eine Canvas erzeugt, welche 350x350 Pixel groß ist. Dann werden mit createLine und createText x-, y-Achse und Achsenbeschriftungen erzeugt:
    $c1->createLine( 25, 175, 325, 175,
                    -arrow => 'last', );
    $c1->createText( 335, 175,
                    -fill  => 'blue',
                    -text  => 'X', );

Text-Element
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
my $mw = MainWindow->new();
$mw->Button(-text    => 'Exit',
            -command => [$mw => 'destroy'],
           )
     ->pack(-side    => 'bottom');
$mw->Scrolled('Text',
              -scrollbars => 'osoe',
              -wrap       => 'none',
             )
       ->pack(-expand     => 1,
              -fill       => 'both',
             );
MainLoop();

Besprechung zum Text-Element
Mit den Zeilen
    $mw->Scrolled('Text',
                  -scrollbars => 'osoe',
                  -wrap       => 'none',
                 )
wird ein Textelement erzeugt, welches rechts und unten über optionale Scrollbalken verfügt. Der Text wird nicht umgebrochen.
Der Exit-Button wird zuerst eingesetzt, damit er auch noch zu sehen ist, wenn das Fenster stark verkleinert wird. Damit er trotzdem unter dem Text-Widget landet, wird er mit
    -side => 'bottom'
gepackt.

Reiterelement
#!/usr/bin/perl
use strict;
use warnings;
use Tk;
use Tk::NoteBook;
my $mw = new MainWindow;
my $nb = $mw->NoteBook()->pack();
my $page1 = $nb->add('PageID-1',
                     -label => 'Reiter Eins', );
my $page2 = $nb->add('PageID-2',
                     -label => 'Reiter Zwei', );
$page1->Label(-text => 'In Seite 1')->pack();
$page2->Label(-text => 'In Seite 2')->pack();
MainLoop();

Besprechung zum Reiterelement
Mit den Zeile
    my $nb = $mw->NoteBook()->pack();
wird ein Reiterelement (NoteBook) erzeugt und gepackt. In diesem werden nun mit
    my $page1 = $nb->add('PageID-1',
                         -label => 'Reiter Eins', );
    my $page2 = $nb->add('PageID-2',
                         -label => 'Reiter Zwei', );
zwei Seiten angelegt mit den Reitertiteln „Reiter Eins“ und „Reiter zwei“. In die Seiten wird nun noch je ein Label gesetzt:
    $page1->Label(-text => 'In Seite 1')->pack();
    $page2->Label(-text => 'In Seite 2')->pack();

Menüsystem
Man kann seinem Perl/Tk-Programm ein Menü hinzuzufügen, das man wie man es unter Windows gewöhnt ist, mit Alt+Buchstabe aufrufen und mit Maus und Tastatur darin navigieren kann. Einzelnen Menüpunkten kann man dabei Tastenkürzel zuordnen, die auch im Menü angezeigt werden.
Das Beispielprogramm menu.pl ist ziemlich lang, deshalb gehe ich zunächst auf den wichtigen Punkt, das Menü ein.
Das gesamte Programm kopiere ich unkommentiert auf eine ausgeblendete Folie für alle, die sich diesen Vortrag selbst ansehen.

Erstellen eines Menüsystems:
1.) Menü (Menubar) erstellen:
    my $menu = $mw->Menu(-type => 'menubar');
2.) Dem Fensterwidget dieses Menü zuweisen:
    $mw->configure(-menu => $menu);
3.) Eigentliche Menüs in der Menubar eintragen:
    $menu->cascade(-label     => 'Datei',
                   -underline => 0,              );
    $menu->cascade(-label     => 'Bearbeiten',
                   -underline => 0,              );
    $menu->separator();
    $menu->cascade(-label     => 'Hilfe',
                   -underline => 0,              );

4.) Die einzelnen Untermenüs definieren:
    my $menu_datei = $menu
        ->Menu(-menuitems => [
                              [
                               'command'    => 'Datei neu',
                               -command     => \&datei_neu,
                               -accelerator => 'Ctrl-N',
                               -underline   => 6,
                              ],
                              ..........
                              '-',
                              [
                               'command'    => 'Beenden',
                               -command     => \&tk_ende,
                               -accelerator => 'Ctrl-B',
                               -underline   => 0,
                              ],
                            ],
              );
5.) Untermenü in Menubar eintragen:
    $menu->entryconfigure('Datei', -menu => $menu_datei);
6.) Bindungen zum Aufruf des Menüs erzeugen:
    $mw->bind('<Alt-d>', sub {$menu->postcascade('Datei');});
    $mw->bind('<Alt-b>', sub {$menu->postcascade('Bearbeiten');});
    $mw->bind('<Alt-h>', sub {$menu->postcascade('Hilfe');});

Besprechung zum Menü-System
So sieht das Programm aus:

Weitere Quellen
Empfehlenswertes Buch: „Mastering Perl/Tk“ von Steve Lidie und Nancy Walsh.
Perldoc: Zu allen Teilproblemen hilft einem meist ein entsprechender perldoc aufruf weiter.
Perl/Tk im Netz:
http://www.perltk.org/
Onlinetutorial:
http://wiki.perl-community.de/bin/view/Wissensbasis/PerlTkTutorial