GUI mit Perl/Tk

Einführung in TkPerl

Inhaltsverzeichnis

Perl + Tk = Perl/Tk

Tk ist ursprünglich ein Zusatz zu Tcl zur Erstellung von Grafical User Interfaces (GUI) in Tcl unter X11. Tk fand als eine sehr gelungene Bibliothek für eine graphische Benutzeroberfläche auch sehr weitreichendes Interesse jenseits von Tcl bei anderen Programmiersprachen wie Scheme, Python, Guile und insbesondere Perl.

John Ousterhout hat Tk so speziell für Tcl entwickelt, daß entsprechende Portierungen für andere Skript-Programmiersprachen nicht sehr einfach waren. Malcom Beattie, der den ersten Versuch für Perl startete, übernahm deswegen des Tcl-Interpreter mit.

Später versuchte Nick Ing-Simmons es mehr mit einem generellen Ansatz: Er bereinigte Tk von allen Abhängigkeiten zu Tcl und schuf eine sprach-unabhängige Schnittstelle. Dies nennt sich pTk (portable Tk, auch ptk). Die heutige Perl-Schnittstelle zu Tk basiert auf pTk.
Perl/Tk von Nick Ing-Simons gestattet den Aufruf von Tcl-Funktionen in der Syntax von OOP-Perl. Diese Implementation ist hier Gegenstand.

Eine neuere Implementierung stellt Tkx dar. Tkx ist eine Brücke zwischen Perl und Tcl. Tcl-Befehle werden über ein definiertes Schema in Perl-Code übersetzt. Tkx bringt Vorteile der neuen Tcl-Version mit sich (z.B. native GUI, Styles). Dafür muss sich der Programmierer mit der Dokumentation von Tcl auseinandersetzen.

Unter einem Package versteht man in Perl einen abgeschlossenen Namensraum für Funktionen und Variablen. Durch dieses Konstrukt können in einem Programm Namenskollisionen vermieden werden. Aufrufe innerhalb eines packages können durch Angabe des einfachen Names, Aufrufe aus anderen packages müssen mit ihrem full qualified Namen erfolgen: package::name.

Unter einem Modul versteht man in Perl eine Datei mit der Endung .pm. Sie enthält ein Teilprogramm (vergleichbar mit Units in Pascal oder Includes in C). In Module ausgelagert werden packages. Ein Modul wird im Hauptprogramm aufgerufen durch

use < modulname >

Durch diese Anweisung werden alle im Modul stehenden Package-, Funktions- und Variablenanweisungen übernommen, und die Befehle ausgeführt, welche außerhalb von Subroutinen stehen (Initialisierungen). Perl erwartet von der use-Funktion die Rückgabe des Wertes 1, deshalb enden Module mit der Zeile 1;

Module können auch hierarchisch geschachtelt werden in (Sub-)Directories. Die Anweisung use SUBDIR::MODUL sucht in allen Directories die sich im INC-Pfad (@INC-Array) befinden nach einem Subdirectory SUBDIR und dort nach der Datei MODUL.pm .

Perl/Tk ist ein Perl-Modul (Tk.pm), der mit use Tk aufgerufen wird. Durch use wird das Modul beim Kompilieren geladen und die Namen in den Namensraum eingefügt. (Die Anweisung require lädt ebenfalls Module, jedoch zur Laufzeit und importiert keine Namen).

Installation von Perl/Tk

Perl/Tk wird prinzipiell wie jedes andere Modul installiert. Informationen dazu finden sich in sehr ausführlicher Form im Wiki der deutschen Perl-Community: Wie installiere ich ein Modul?

Manchmal gestaltet es sich etwas schwierig, Perl/Tk auf Windows zu installieren. Die Seite Perl/Tk auf Windows installieren zeigt Lösungen auf.

Als schnellen Test, ob Perl/Tk bereits installiert ist, kann folgender Einzelner in der Konsole verwendet werden:

perl -e "use Tk 999;"

Wenn Perl/Tk installiert ist, dann erscheint eine Meldung, dass nur die "kleinere" Version (kleiner als Version 999) vorhanden ist:

C:\Windows\System32>perl -e "use Tk 999";
Tk version 999 required--this is only version 804.029 at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

Wenn Perl/Tk nicht installiert ist, dann sieht die Meldung wie folgt aus:

C:\Windows\System32>perl -e "use Tk 999";
Can't locate Tk.pm in @INC (@INC contains: C:/Perl/site/lib 
C:/Perl/lib .) at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

OOP

Unter Perl5 ist eine Klasse lediglich ein Package, deren Subroutinen Objekte manipulieren können und damit Methoden im Sinne der OOP sind.

Perl/Tk stellt mit dem Modul Tk.pm Widgets in Form von Objektklassen zur Verfügung. Der Pfeil-Operator für die Dereferenzierung -> bedeutet den Aufruf einer Methode (Subroutine) eines Objekts.

Ein neues Widget wird nach folgendem Schema erzeugt:

$kind = $vater->Widget-Typ([ -option => "wert", ... ] )

Die Syntax für die Erzeugung eines neuen Fensters gestaltet sich wie folgt:

$object = MainWindow->new

in diesem Fenster können weitere Widgets erzeugt werden durch:

$newobject = $object->widget(options)

Die möglichen widgets sind in Kapitel aufgeführt. Die Optionen unterscheiden sich naturgemäss für die verschiedenen widgets und finden sich in der Tabelle im Anhang. Ebenso unterscheiden sich die Methoden, die mit der Syntax:

$object->methode(options)

Beispiel:

$object->configure(-background => 'beige');

Erste Beispiele

Als erstes Beispiel wird in einem Fenster ein Text ausgegeben und mit einem "Quit"-Button das Fenster wieder geschlosssen.

#!perl

use strict;
use warnings;
use utf8;
use Tk;

# Haupt-Fenster erzeugen. mw = main window
my $mw = MainWindow->new();

# Titel setzen (sichtbar in der Fensterleiste)
$mw->title("Hello");

# Label erzeugen
my $label = $mw->Label(
	-text => 'Guten Morgen, sonnige Welt!',
); 

# Button erzeugen
my $quit_btn = $mw->Button(
	-text		=> 'Quit', 
	-command	=> sub{
		exit(0); # normal beenden
	},
);

# Label auf der GUI platzieren
$label->pack();

# Button auf der GUI platzieren
$quit_btn->pack();

# Ereignisschleife der GUI starten
$mw->MainLoop();

exit(0);

Mit use Tk werden alle zu Tk gehörenden Module importiert einschließlich aller Widgets wie beispielsweise MainWindow. Ferner wird auch der eigene Namensraum mit einigen Namen wie beispielsweise MainLoop geflutet. Per Konvention wird use strict verwendet, um die Disziplinen zu erzwingen, die das Leben in einer modularisierten Welt erleichtern.

$mw ist ein Objekt von dem Typ MainWindow, das mit my nur lokal sichtbar angelegt wird (dies ist insbesondere auch wegen use strict notwendig). Label und Button sind Methoden von $mw, die entsprechende Widgets kreieren.

Die Konstruktions-Parameter von Widgets werden als assoziative Arrays übergeben. Mit der Methode pack wird das jeweilige Objekt an den entsprechenden Geometrie-Manager übergeben.

MainLoop ist die zentrale Schleife, die alle Ereignisse abarbeitet. Die GUI wird dabei angezeigt und Interaktionen eines Nutzers mit der GUI werden verarbeitet.

Hallo-Welt-Programm in Perl/Tk

Im zweiten Beispiel werden die Standardoptionen am Beispiel eines Buttons, sowie das Ausblenden von Widgets gezeigt. Dieses Beispiel enthält alle möglichen Optionen des Widgets Button und (auskommentiert) die Anweisung, die verhindert, dass das entsprechende Window grösser "gezogen" werden kann.

#!perl

use strict;
use warnings;
use utf8;
use Tk;

my $count = 0;
my $mw = MainWindow->new();

$mw->title("Button-Test");
$mw->geometry('+20+20');

# verhindert das 'Größerziehen':
# $mw->resizable('0','0');

my $degscht = "Auch dies ist \n ein Button \n nur grösser!
                \n und mit anderem Font";

my $quit_btn = $mw->Button (
	-textvariable		=> \$degscht,
	
	# alternativ statt -textvariable:
	# -text    => "    Press     "
	
	# -bitmap=>"question",
	-borderwidth		=> '5',

	# statt -borderwidth geht auch:
	# -bd => '3',
	
	# ohne Wirkung:
	# -highlightthickness => '30',
	
	-highlightcolor		=> 'SkyBlue',
	-activebackground	=> 'green',
	-relief 			=> 'raised',
	-width				=> 20,
	-height				=> 20,
	-command			=> sub{
		$mw->destroy();
		return 1;
	},
	-font				=> "9x15",
	-foreground			=> "red",
	-activeforeground	=> 'white',
	-background			=> "yellow",
	-cursor				=> 'arrow',
	-state				=> "normal"
);

my $quit_btn2 = $mw->Button(
	-text    => "    Quit     ", 
	-command => [sub{&machput}]
);

$quit_btn->pack();
$quit_btn2->pack();

$mw->MainLoop();
exit(0);

sub machput {
	$quit_btn2->destroy();
	return 1;
} # /machput

Button mit geänderter Farbe und Größe in Perl/Tk

Graphischer Aufbau einer Anwendung

Der Aufbau einer grafischen Anwendung wird in diesem Abschnitt an Hand eines kleinen Beispielprogramms dargestellt. Das Programm zeigt es Inhalt eines Verzeichnisses in einer Listbox an.

GUI in Perl/Tk

Die Struktur des Fensters stellt sich in etwa so dar:

Aufbau eines Perl/Tk-Programms

Die graphische Benutzeroberfläche einer Anwendung besteht aus einer Vielzahl von Komponenten (den sogenannten Widgets), die hierarchisch organisiert und zusammengebaut werden. Die Herausforderung liegt in der Findung (und Nutzung) geeigneter Abstraktionen zum Zusammenfügen einer Widget-Hierarchie.

Strukturbaum einer Perl/Tk-Anwendung

Übersicht über die Widgets

Eine Liste mit Widgets, Screenshots und funktionstüchtigem Beispiel-Quellcode findet sich hier: Übersicht der Perl/Tk-Widgets.

Widget Beschreibung
Button Schaltflächen
Canvas Zeichenfläche. Erlaubt das erstellen beliebiger Grafiken, Interaktion mit dem Benutzer aber auch die Anzeige von Tk-Widgets.
Checkbutton Eine Box, die angehakt werden kann. Ähnlich der HTML-Checkbox:
Entry Eingabefelder / editierbare einzeiliges Textfelder
Frame Container für andere Frames. Kann z.B. zur Organisation von Widgets in einer komplexen GUI verwendet werden.
Label Zur Anzeige von Text oder als Container für ein Bild.
Listbox Auswählbare Strings mit Scrollbar
MainWindow Das erste Fenster einer jeden Perl/Tk-GUI-Anwendung. Eine spezielle Form des Toplevel-Fensters.
Menu Menüleisten
Menubutton Ein einzelner Menüpunkt
Radiobutton Ähnlich zur HTML-Radiobox:
Scale Schieberegler mit Zahlen
Scrollbar Scrollbalken können fast mit jedem Widget kombiniert werden.
Text Eingabefeld für Text. Mehrzeilig mit Formatierungsmöglichkeit. Ähnlich zu einem Texteditor.
Toplevel Eigenständige Fenster, ähnlich zum MainWindow (wovon es immer nur eines geben kann).

Das Widget Frame

Das Widget Frame nimmt eine Sonderrolle ein, da es lediglich einen Rahmen zur Verfügung stellt, in den weitere Widgets platziert werden können. Dadurch wird die Gestaltung von Fenstern flexibler und einfacher zu handhaben. Daneben haben Frames aber auch noch eine Dekorationsfunktion. So wie bei anderen Widgets auch kann durch die Angabe der relief-Option ein unterschiedliches 3-dimensionale Aussehen erzielt werden.

#!perl

use strict;
use warnings;
use utf8;
use Tk;

my $mw = MainWindow->new();

my @relief_styles = ("flat", "raised", "sunken", "ridge", "groove");

foreach my $reliefstyle ( @relief_styles ) {
	# Frame um das Label erzeugen
	my $frame = $mw->Frame(
		-relief => $reliefstyle, 
		-borderwidth => 5,
	);

	# Mit 2mm Abstand seitlich und nach
	# oben packen
	$frame->pack(
		-side => "left",
		-padx => "2m", 
		-pady => "2m",
	);

	# Label erzeugen
	$frame->Label(
		-text => $reliefstyle
	)->pack();
}

$mw->MainLoop();
exit(0);

Relief-Varianten flat, raised, sunken, ridge und groove am Beispiel mehrerer Label

Die Geometrie-Manager

Der Geometrie-Manager ist für die Anordnung der Widgets innerhalb eines Windows oder Frames zuständig. Verschiedene Teile einer Anwendung können unterschiedliche Geometrie-Manager verwenden. Perl/Tk kennt vier verschiedene Geometrie-Manager:

Manager Beschreibung
pack packt ein Widget ans andere, mit Vorgabe der "Himmelsrichtung". Die Positionierung von Widgets erfolgt jeweils innerhalb des übergeordneten Widgets im noch verbliebenen "Hohlraum" (cavity model).
grid teilt Window in ein regelmäßiges Gitter. Die Positionierung erfolgt innerhalb eines Gitters, das innerhalb des übergeordneten Widgets angelegt worden ist.
place platziert Widgets gezielt im Window. Hierbei wird die Position und Größe eines Widgets präzise in Relation zum übergeordneten Widget spezifiziert.
form Verallgemeinerung, die die Fähigkeiten von pack und place miteinander vereinigt.

Die Methode pack

Name Funktion Optionen
pack Macht das Widget entsprechend den Optionen, auf das die Methode pack angewendet wird. -after, -anchor, -before, -expand, -fill, -in, -ipadx, -ipady, -padx, -pady, -side

Die Methode pack befördert die davor definierten Widgets auf den Bildschirm. Das übergeordnete Widget stellt einen rechteckigen Raum zur Verfügung, der möglicherweise bereits teilweise belegt ist. Die Platzierung erfolgt dann anhand der angegebenen Optionen und mit Rücksicht auf die bereits im Hohlraum platzierten Widgets. Die gewünschte Seite kann dabei angegeben werden: Beispielsweise mit -side => 'top' (Positionierung an der Oberseite des Hohlraumes). Als Regel gilt: das Widget wird so weit in die gewünschte Richtung geschoben, wie es schon vorhandene Widgets zulassen. Wenn die Größe des übergeordneten Widgets nicht fest vorgegeben ist, dann ergibt sie sich implizit aus der Größe der eingefügten Widgets.

Durch den Geometriemanager pack kann zusammen mit dem Widget Frame (siehe Das Widget Frame) so auf einfache Art eine (nahezu) beliebige Windowgestaltung erreicht werden.

Man sieht wie sowohl die Reihenfolge der pack-Anweisungen, als auch die Werte der side-Option sich auf die Aufteilung des Fensters auswirken. Die relief-Option wurde lediglich benutzt um die Label-Positionen besser erkennen zu können.

Beispiel für den pack-Geometriemanager

Die Methode grid

Name Funktion Optionen
grid Macht das Widget auf das die Methode grid angewendet wird sichtbar, und platziert es entsprechend den Optionen. -, x, ˆ, -column, -row, -columnspan, -rowspan, -sticky, -in, -ipadx, -ipady, -padx, -pady
#!perl

use strict;
use warnings;
use utf8;
use Tk;

my $mw = MainWindow->new();

$mw->title('grid-test');

my $label1 = $mw->Label(
	-text	=> 'Label 0 - 1,1  ',
	-relief	=> 'sunken',
);
my $label2 = $mw->Label(
	-text	=> 'Label 1 - 2,2 ',
	-relief	=> 'sunken',
);
my $label3 = $mw->Label(
	-text	=> 'Label 2 - 3,3  ',
	-relief	=> 'sunken',
);
my $label4 = $mw->Label(
	-text	=> 'Label 3 --------- 4,1-2 ',
	-relief	=> 'sunken',
);
my $label5 = $mw->Label(
	-text	=> 'Label 4 --------- 5,2-3',
	-relief	=> 'sunken',
);

$label1->grid(-row=>'0', -column=>'0');
$label2->grid(-row=>'1', -column=>'1');
$label3->grid(-row=>'2', -column=>'2');
$label4->grid(-row=>'3', -columnspan=>'2',);
$label5->grid(-row=>'4', -column=>'1', -columnspan=>'2',);

$mw->MainLoop();
exit(0);

Beispiel für den grid-Geormetriemanager

#!perl

use strict;
use warnings;
use utf8;
use Tk;

my $mw = MainWindow->new();
$mw->title('grid-test-2');

my $label1 = $mw->Label(-text=>' - Label 0 - ', -relief=>'sunken');
my $label2 = $mw->Label(-text=>' - Label 1 - ', -relief=>'sunken');
my $label3 = $mw->Label(-text=>' - Label 2 - ', -relief=>'sunken');
my $label4 = $mw->Label(-text=>' - Label 3 - ', -relief=>'sunken');
my $label5 = $mw->Label(-text=>' - Label 4 - ', -relief=>'sunken');

$label1->grid($label2, $label3);
$label4->grid('x',$label5);

$mw->MainLoop();
exit(0);

Labels mit grid anordnen

Die Methode place

Name Funktion Optionen
place Macht das Widget auf das die Methode place angewendet wird sichtbar, und platziert es entsprechend gewählten x- und y-Koordinaten. -anchor, -bordermode, -height, -in, -relheight, -relwidth, -relx, -rey, -width, -x, -y

Mit place können die Widgets beliebig innerhalb des Windows gesetzt werden, also auch übereinander. Die Koordinaten können absolut (bezogen auf den Bildschirm) oder relativ (bezogen auf das Elternwidget) angegeben werden.

#!perl

use strict;
use warnings;
use utf8;
use Tk;

my $mw = MainWindow->new();
$mw->title('place-Test');

my $label1 = $mw->Label(-text=>' - Label 0 - ', -relief=>'sunken');
my $label2 = $mw->Label(-text=>' - Label 1 - ', -relief=>'sunken');
my $label3 = $mw->Label(-text=>' - Label 2 - ', -relief=>'sunken');
my $label4 = $mw->Label(-text=>' - Label 3 - ', -relief=>'sunken');
my $label5 = $mw->Label(-text=>' - Label 4 - ', -relief=>'sunken');

$label1->place(-x=>10, -y => 25);
$label2->place(-x=>20, -y => 50);
$label3->place(-x=>30, -y => 75);
$label4->place(-x=>40, -y => 85);
$label5->place(-x=>20, -y => 150);

$mw->MainLoop();
exit(0);

Platzierung mit dem place-Geormetriemanager

Die wichtigsten Optionen

Option Parameter Funktion Beispiel
-activebackground <color> Hintergrund, aktiv siehe -background
-activeforeground <color> Vordergrund, aktiv siehe -background
-anchor n, ne, e, se, s, sw, w, nw, center Anker, Anbindungsrichtung -anchor => 'sw'
-background <color> Hintergrundfarbe nicht aktiv -background => 'gray50'
-bitmap <bitmap> Flächenmuster -bitmap => @bitmap.xbm
-borderwidth <width> c, m, i, p Randbreite -borderwidth => '2m'
-command sub{... } oder \&<funcname> oder [ < commandlist > ] Kommandoaufruf -command => sub{ machwas(); return 1; }
-cursor arrow, circle ... Cursorform -cursor => 'arrow'
-disabledforeground
-image
-text
-expand yes, no Ausdehnung ja/nein -expand => 'no'
-fill none, x, y, both Füllausdehnungsrichtung -fill => 'both'
-font <Zeichensätze> Wahl des Zeichensatzes -font => ''courierb10''
-foreground <color> Vordergrundfarbe (Schriftfarbe) -foreground => 'black'
-height <width> Höhe eines Widgets -height => '10m'
-highlightbackground <color> wenn Mouse auf Widget siehe -background
-highlightcolor <color> -highlightcolor => 'beige'
-highlightthickness-takefocus
-image <photo> legt Bild auf Button oder Label -image => $myphoto
-in $<widget> Geometrisches Parent
-justify
-label <text> Ausgabe von Text -label => 'text'
-padx <width> c, m, i, p Abstand seitlich -padx => '2m'
-pady <width> c, m, i, p Abstand oben und unten -pady => '3m'
-pady-wraplength
-relief flat, groove, raised, ridge, sunken Art 3D-Darstellung der Widgets -relief => 'raised'
-side top, left, bottom, right Positionierung -side => 'top'
-state normal, disabled, active Aktivierbar, gesperrt, aktiv -state => 'normal'
-status normal, disabled Editieren oder Readonly -status => 'normal'
-text <text> Beschriftung -text => 'text'
-textvariable <variablenreferenz> Referenz auf Variable -textvariable => \$myvar
-underline <offset> Hotkey-Zeichen als Offset -underline => 1
-width <width> Breite -width => '30m'
-wrap none, char, word Zeilenumbruch

Platzhalter Erklärung
<color> ist eine Farbe entweder mit Namen aus /usr/lib/X11/rgb.txt oder in der RGB-Darstellung #rrggbb (rr, gg, bb in Hex)
<width> Maßeinheiten: c = cm, m = mm, i = inch, p = pixel (Default)
<bitmap> entweder $filename oder der Name eines Tk-eigenen Bitmaps: error, gray25, gray50, hourglas, info, questhead, question oder warning.
<offset> Zahl die Offset in einem String anzeigt
<Zeichensätze> eine der unter X11 möglichen Bezeichnungen eines Zeichensatzes (bzw. der entsprechenden Window-Implementierung anderer OS).
<text> Textstring in einfachen oder doppelten (Perl-Variablen werden aufgelöst) Anführungszeichen.
<photo> Photowidget als Wert der -image-Option der beiden Widgets Button und Label.

Methoden

Neben einigen spezifischen Methoden, die es nur für einige Widgets gibt, gibt es auch Methoden, die in allen Widgets implementiert sind.

Der größte Teil der Methoden haben Informationscharakter, d.h. sie manipulieren nichts, sondern liefern nur Werte zurück. Eine vollständige Aufstellung ist in der Literatur 1. und 2. zu finden.

Methode Widget Beschreibung Beispiel
pack all Ordnet Widget im Windows an $widget->pack(options);
grid all Ordnet Widget im Windows an $widget->grid(options);
place all Ordnet Widget im Windows an $widget->place(options);
packForget all Widget löschen ohne zu zerstören $widget->packForget();
gridForget all Widget löschen ohne zu zerstören $widget->gridForget();
placeForget all Widget löschen ohne zu zerstören $widget->placeForget();
configure all Änderung der aktuellen Optionen $widget->configure(-background=>"grey");
destroy all Zerstören eines Widgets $widget->destroy();
bind all Verknüpfung Erreignissfolge und Widget entry->bind(«Return>",\&checkin($textvar));
insert Text fügt Zeile ein $widget->insert('end', 'Die letzte Zeile');
Listbox
delete Text löscht Zeile(n) $widget->delete(5, 10);
Listbox
select Text Auswahl von Zeile(n) $widget->select(5, 'end');
Listbox
after all Zeitverzögerung $messag->after(2*1000, sub { exit });
repeat all wiederholtes Aufrufen einer Subroutine $widget = repeat(600, \&wieder);

Ereignisse

Alle Aktionen des Benutzers (Bewegen der Maus, Benutzung der Maustasten oder der Tastatur) führen zu einem Strom einzelner Ereignisse. Aus der Mausposition oder der Fokus-Zugehörigkeit zum Zeitpunkt der Entstehung eines Ereignisses und vorheriger Interessensbekundungen (z.B. durch die Methode bind) ergibt sich eine Menge von Parteien, denen das Ereignis mitzuteilen ist.

Eine "Mitteilung" erfolgt in Perl/Tk durch den Aufruf einer (typischerweise anonymen) Prozedur und (bei Bedarf) einer Reihe von Parametern. Interessant sind die Spezifikationen von Ereignissen, die Möglichkeiten der Interessensbekundungen und die Reihenfolge der Mitteilungen.

Nachfolgendes Beispiel zeigt, wie der Klick auf ein Frame eine Ausgabe auf der Konsole erzeugt:

Mausklick-bind auf Frame

#!perl

use strict;
use warnings;
use Tk;

my $top = MainWindow->new;
my $frame = $top->Frame(-width => 50, -height => 50,
   -bg => 'yellow');
$frame->pack;
$frame->bind('<ButtonPress>',
   sub { print "Ouch, that hurts!\n" });
$top->MainLoop;
exit(0);

Spezifikation von Ereignissen

Ereignis-Spezifikationen sehen folgendes allgemeines Schema vor:

<modifier-modifier-type-detail>

Zu den Modifiern zählen Control, Shift, Button1 (1. Button der Maus), Alt, Meta und auch Double (für Doppelklicks). Auf die Angabe von Modifiern kann ganz verzichtet werden (dann spielen sie keine Rolle) oder es können ein oder zwei angegeben werden. In letzterem Falle müssen dann beide Modifier gleichzeitig aktiv sein.

Zu den Typen gehören ButtonPress, ButtonRelease, KeyPress, KeyRelease, Enter und Release (neben vielen weiteren).

Beim Detail kann (z.B. bei KeyPress) der Name einer weiteren Taste angegeben werden (oder schlicht der Buchstabe) oder (z.B. bei ButtonPress) die Nummer der Maustaste.

Die Angaben können weitgehend gekürzt werden, solange noch eindeutig ist, was gewünscht wird. Statt <Control-KeyPress-U> ist auch <Control-U> zulässig.

Ereignis-Spezifikationen operieren wie Muster, die eingehende Ereignisse filtern. So trifft beispielsweise <KeyPress> für alle gedrückten Tasten zu. Die Namen der Tasten hängen natürlich von dem lokalen Window-System ab und sind daher nicht in jedem Falle uneingeschränkt portabel (wie auch Tastaturen sehr unterschiedlich aussehen können).

Interessensbekundungen

Eine Interessensbekundung für Ereignisse besteht aus einem Kontext und einer Ereignisspezifikation. Mögliche Kontexte sind:

Der Kontext wird als erster Parameter bei bind angegeben. Wenn er fehlt, wird nur ein Bezug zu dem vorgegebenen Widget-Objekt hergestellt. Es kann ein Klassenname (z.B. Tk::Button) angegeben werden, das Widget eines Toplevel-Fensters oder schlicht all.

Wenn für ein Ereignis mehrere Interessensbekundigungen vorliegen, dann werden sie in einer definierten Reihenfolge abgearbeitet. Per Voreinstellung gilt folgende Reihenfolge:

Die Reihenfolge kann (mit bindtags) für jedes Widget verändert (und sogar noch erweitert) werden.

Ereignis-Bearbeiter

Beispiel für Event-Verarbeitung in Perl/TkKeycode der gedrückten Taste anzeigen

#!perl

use strict;
use warnings;
use utf8;
use Tk;

my $top   = MainWindow->new;
my $label = $top->Label(
    -text => 'Taste drücken...',
    -fg     => 'red'
);
$top->bind(
	'<KeyPress>',
	[
		sub {
			my ( $self, $keycode ) = @_;
			$label->configure(-text => sprintf("keycode: %s\n", $keycode));
		},
		Ev('k')
	]
);
$label->pack;
$label->focus;
$top->MainLoop;
exit(0);

Bei der Methode bind können Ereignis-Bearbeiter in einer Vielzahl von Varianten angegeben werden:

Ereignisbearbeiter Beschreibung
&subname Zeiger auf vorhandene Prozedur
sub{ ... } Anonyme Prozedur
'methodname' Methode des zugehörigen Widgets
[&subname, ...] Mit Parameterliste
[sub{ ... }, ...]
['mnethodname', ...]

Parameterlisten sind überflüssig, solange nicht Interesse an speziellen Ereignisparametern besteht, die über von Ev erzeugte Callback-Objekte zugänglich sind.

Weitere Beispiele

Das folgende Beispiel enthält ein kleines Tool zur Darstellung der in X11 möglichen Farben, mit Testfeldern deren Vorder- und Hintergrund durch Anklicken mit der linken bzw. rechten Maustaste in der Liste gewählt werden können.

#!perl

use strict;
use warnings;
use utf8;
use Tk;

my $fgcolor = 'black';
my $bgcolor = 'white';
my $font    = '-misc-fixed-medium-r-normal--13-100-100-100-c-80-iso8859-8';

my $mw = MainWindow->new();
$mw->title("Xcolors");
   
my $frame = $mw->Frame();
my $text  = $frame->Text(
    -wrap => 'none',
);

my $yscrollbar = $frame->Scrollbar(
    -command => [yview => $text],
);

my $Buttonframe = $mw->Frame();
my $OK_Button   = $Buttonframe->Button(
    -text        => 'Quit',
    -background => 'gray50',
    -foreground => 'white',
    -command    => sub{
        $mw->destroy;
        return 1;
    },
);

### Anzeige: Anzahl der Farben
my $labelvar = '';
my $label = $Buttonframe->Label(
    -textvariable => \$labelvar,
);

$text->configure(
    -yscrollcommand => [set => $yscrollbar],
);
$text->configure (
    -font => $font,
);

### Alles packen
$yscrollbar->pack(
    -side => 'right',
    -fill => 'y',
);

$label->pack(
    -fill => 'x',
    -side => 'right',
);

$frame->pack(
    -expand    => 1,
    -fill    => 'both',
);
$text->pack(
    -expand => 1,
    -fill    => 'both',
    -side    => 'left'
);
$Buttonframe->pack(
    -expand    => 1,
    -fill    => 'both',
    -side    => 'bottom',
);
$OK_Button->pack(-side => 'left');


### Farben auslesen und Textzeilen einfaerben

# wer die Datei nicht hat (z.B. auf Windows), der
# kann eine Testdatei anlegen, die folgendes beinhaltet (ohne #):
#! $XFree86$
#255 250 250        snow
#255 240 245        LavenderBlush
#106  90 205        SlateBlue

#open(my $fh, '< /usr/lib/X11/rgb.txt') or die 'rgb.txt not found';
open(my $fh, '<rgb.txt') or die 'rgb.txt not found';

my ($tagname,$grauwert, $foreground);
my $i=1;
while( my $row = <$fh> ) {
    # Kommentare entfernen
    $row =~ s/!.*//;
    
    # Leerzeilen ignorieren
    next if $row =~ /^\s*$/;

    my ($red, $green, $blue) = split(' ', $row);
    my $name = substr($row, 12);
    $name =~ s/^\s*//;
    chomp( $name );
    
    my $col = sprintf("#%02x%02x%02x", $red, $green, $blue);

    ### Beschriftungsfarbe weiß fuer dunkle Hintergrundfarben
    $grauwert = $red + 3*$green + $blue;
    $foreground = 'white';if ($grauwert > 625){$foreground='black'} 
    $name = substr ($name."                            ",0,20);
    
    # Text in Text-Widget einfügen (mit \n)
    $text->insert("end", "$name  $col  \n");

    ### Tag definieren und Farbe dort setzen
    $tagname = "zeile$i";
    $text->tag("add", $tagname, "$i.0", sprintf("%d.0", $i+1));
    $text->tag("configure", $tagname,
        -background => $col, 
        -foreground => $foreground
    );
    $text->tag(
        'bind' => $tagname,
        '<Button-1>' => sub{ &doit($name); }
    );
    $text->tag(
        'bind' => $tagname,
        '<Button-3>' => sub{ &shiftdoit($name); }
    );

    ### Jede neue Farbe sofort anzeigen
    $text->update();

    ### Anzeige auffrischen
    $i++;
    $labelvar="There are $i colors in /usr/lib/X11/rgb.txt      ";
}
close($fh);

# ------------------------------ Color Frames -----
my $color_frame = $mw->Frame;
$color_frame->pack(-side => 'bottom');
my $probe1 = $color_frame->Text;
$probe1->configure(
    -height => '5',
    -width  => '15',
    -state  => 'disabled',
    -font => $font,
);
$probe1->pack( -side => 'left' );

# -----
my $probe2 = $color_frame->Text;
$probe2->configure (
    -height => '5',
    -width  => '15',
    -state  => 'disabled',
    -font => $font,
);
$probe2->pack( -side => 'left' );

# -----
my $probe3 = $color_frame->Text;
$probe3->configure (
    -height => '5',
    -width  => '15',
    -state  => 'disabled',
    -font => $font,
);
$probe3->pack( -side => 'left' );

# -----
my $probe4 = $color_frame->Text;
$probe4->configure (
    -height => '5',
    -width  => '15',
    -state  => 'disabled',
    -font => $font,
);
$probe4->pack( -side => 'left' );

# -----
my $probe5 = $color_frame->Text;
$probe5->configure (
    -height => '5',
    -width  => '15',
    -state  => 'disabled',
    -font => $font,
);
$probe5->pack( -side => 'left' );

$probe1->bind ('<Button-1>', sub{set_wind('1')});
$probe2->bind ('<Button-1>', sub{set_wind('2')});
$probe3->bind ('<Button-1>', sub{set_wind('3')});
$probe4->bind ('<Button-1>', sub{set_wind('4')});
$probe5->bind ('<Button-1>', sub{set_wind('5')});

# ------------------------------ Radio Button Frame -----
my $radio_frame = $mw->Frame(-relief => 'sunken');
$radio_frame->pack(-side => 'bottom');

my $actw = 1;
my $helpvar = 'Left-button = background'
    .'              Right-button = foreground'; 

my $helplabel = $radio_frame->Label(
    -textvariable    => \$helpvar,
    -relief            => 'sunken',
);
$helplabel->configure(
    -background => 'gray50',
    -foreground => 'white'
);
$helplabel->pack(
    -side => 'top',
    -expand => 1,
    -fill=>'x'
);

foreach my $p ('probe1', 'probe2', 'probe3', 'probe4', 'probe5') {
    my $r = $radio_frame->Radiobutton(
        -text     => "  $p             ",
        -variable => \$actw,
        -relief   => 'flat',
        -value    => $actw++,
    );
    
    $r->pack(
        -side => 'left',
        -pady => 2,
        -anchor => 'w'
    );
    if( $p eq 'probe1' ){
        $r->configure(-state => 'active');
    };
}
$actw = 1;
$text->configure(-state => "disabled");
MainLoop;
# --------------------------------------------------------------
sub doit {
    $bgcolor = shift;
    $bgcolor =~ s|\s*$||;
    
    if ($actw == 1) {
        $probe1->configure(-state => 'normal');
        $probe1->delete ('1.0','end');
        $probe1->insert ('end',"f:$fgcolor\n\n");
        $probe1->insert ('end',"b:$bgcolor");
        $probe1->configure(
            -background    => $bgcolor,
            -state        => 'disabled',
        );
    }
    
    if ($actw == 2) {
        $probe2->configure(-state => 'normal');
        $probe2->delete ('1.0','end');
        $probe2->insert ('end',"f:$fgcolor\n\n");
        $probe2->insert ('end',"b:$bgcolor");
        $probe2->configure(
            -background    => $bgcolor,
            -state        => 'disabled',
        );
    }
    
    if ($actw == 3) {
        $probe3->configure(-state => 'normal');
        $probe3->delete ('1.0','end');
        $probe3->insert ('end',"f:$fgcolor\n\n");
        $probe3->insert ('end',"b:$bgcolor");
        $probe3->configure(
            -background    => $bgcolor,
            -state        => 'disabled',
        );
    }
    
    if ($actw == 4) {
        $probe4->configure(-state => 'normal');
        $probe4->delete ('1.0','end');
        $probe4->insert ('end',"f:$fgcolor\n\n");
        $probe4->insert ('end',"b:$bgcolor");
        $probe4->configure(
            -background    => $bgcolor,
            -state        => 'disabled',
        );
    }
    if ($actw == 5) {
        $probe5->configure(-state => 'normal');
        $probe5->delete ('1.0','end');
        $probe5->insert ('end',"f:$fgcolor\n\n");
        $probe5->insert ('end',"b:$bgcolor");
        $probe5->configure(
            -background    => $bgcolor,
            -state        => 'disabled',
        );
    }
}
# --------------------------------------------------------------
sub shiftdoit {
    $fgcolor = shift;
    $fgcolor =~ s|\s*$||;
    
    if ($actw == 1) {
        $probe1->configure(-state => 'normal');
        $probe1->delete ('1.0','end');
        $probe1->insert ('end',"f:$fgcolor\n\n");
        $probe1->insert ('end',"b:$bgcolor");
        $probe1->configure(
            -foreground => $fgcolor,
            -state        => 'disabled',
        );
    }
    
    if ($actw == 2) {
        $probe2->configure(-state => 'normal');
        $probe2->delete ('1.0','end');
        $probe2->insert ('end',"f:$fgcolor\n\n");
        $probe2->insert ('end',"b:$bgcolor");
        $probe2->configure(
            -foreground => $fgcolor,
            -state        => 'disabled',
        );
    }
    
    if ($actw == 3) {
        $probe3->configure(-state => 'normal');
        $probe3->delete ('1.0','end');
        $probe3->insert ('end',"f:$fgcolor\n\n");
        $probe3->insert ('end',"b:$bgcolor");
        $probe3->configure(
            -foreground => $fgcolor,
            -state        => 'disabled',
        );
    }
    
    if ($actw == 4) {
        $probe4->configure(-state => 'normal');
        $probe4->delete ('1.0','end');
        $probe4->insert ('end',"f:$fgcolor\n\n");
        $probe4->insert ('end',"b:$bgcolor");
        $probe4->configure(
            -foreground => $fgcolor,
            -state        => 'disabled',
        );
    }
    
    if ($actw == 5) {
        $probe5->configure(-state => 'normal');
        $probe5->delete ('1.0','end');
        $probe5->insert ('end',"f:$fgcolor\n\n");
        $probe5->insert ('end',"b:$bgcolor");
        $probe5->configure(
            -foreground => $fgcolor,
            -state        => 'disabled',
        );
    }
}
# --------------------------------------------------------------
sub set_wind {
    $actw = $_[0];
}

X11-Farben zur Ansicht in einer Perl/Tk-GUI

Literatur und Quellen

Diese Einführung basiert auf der Einführung in Perl/Tk von Dr. Rudolf Strub und mit freundlicher Genehmigung von Dr. Andreas F. Borchert auf den Folien des Kurses Objekt-orientierte Datenbankanwendungen.

  1. Learning Perl/ TK. Graphical User Interfaces with Perl (Nancy Walsh; O'Reilly)
  2. Learning Perl/Tk ins Deutsche übersetzt von M.K. Dalheimer: Einführung in Perl/Tk
  3. Tk Documentation "Perl/Tk - Writing Tk applications in Perl 5"
  4. Perl and the Tk Extension
  5. Writing GUI Applications in Perl/Tk (eine sehr gute Einführung)
  6. Perl/Tk
  7. GUI Interfaces with Perl/Tk
  8. (veraltete) Einführung in TkPerl von Dr. Rudolf Strub. Die Basis für diese Seite.
  9. Folien zum Kurs Objekt-orientierte Datenbankanwendungen von Dr. Andreas F. Borchert (PDF)
Top