GUI mit Perl/Tk

Geometriemanager

Geometrie-Manager zur Anordnung von Perl/Tk-Widgets

Intro: was ist ein Perl/Tk-Geometriemanager?

Geometrie-Manager koordinieren die Raum- und Platzverteilung von Widgets innerhalb der ihnen übergeordneten Widgets. Man spricht dabei von Master und Slave Widgets. Der Slave ist das Widget, dass durch den Geometrie-Manager angeordnet wird, der Master das Widget, in dem der Slave verteilt wird. Ein Widget wird erst dann sichtbar, wenn es durch einen Geometrie-Manager betreut wird. Im untenstehenden Button-Beispiel wird die Sichtbarkeit des Buttons durch den pack-Befehl realisiert.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $btn1 = $mw->Button(-text => 'ich verstecke mich');

my $btn2 = $mw->Button(-text => 'ich bin sichtbar');
$btn2->pack();

$mw->MainLoop();

Button wird durch pack-Geometriemanager sichtbar


Der Placer

Mit dem Placer ist es möglich den Widgets feste Positionen und Größen zu geben. Diese Angaben können sowohl absolut als auch relativ sein. Bei der absoluten Plazierung werden Pixelkoordinaten im Master für die Größe und Lage der Widgets angegeben. Relative Werte beziehen sich auf die Dimensionen des Masters. Die Slaves können dabei auch außerhalb des sichtbaren Bereichs eines Masters liegen. Mit dem Placer ist es daher etwas schwieriger, Oberflächen zu erstellen, deren Widgets auch nach einer Veränderung der Windowgröße noch gut zueinander koordiniert sind.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit(-title => 'Place a button');
$mw->geometry('320x240');

my $btn2 = $mw->Button(-text => 'ich bin sichtbar bei <55,77>');
$btn2->place(-x => 55, -y => 77);

$mw->MainLoop();

Tk::place - Beispiel

Der Packer

Der Packer ist einer der eher gebräuchlicheren Geometrie-Manager. Er warbeitet wir folgt: Die Slave-Widgets werden, ausgehend von den Innenkanten des Masters, der Reihe nach in den Master gepackt. Der Geometrie-Manager reserviert dabei für jeden Slave eine rechteckige Parzelle. Man muss also zwischen dem Platz, der dem Slave zusteht, also die für ihn reservierte Parzelle, und dem Platz, den das eigentliche Widget einnimmt unterscheiden. Es wird also zuerst die Parzelle im jeweiligen Master angelegt und dann das Widget nach weiteren Optionen in die Parzelle gepackt.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $btn1 = $mw->Button(-text => 'Hallo');
my $btn2 = $mw->Button(-text => 'Welt');
$btn1->pack;
$btn2->pack;

$mw->MainLoop();

2 Buttons untereinander anordnen mit dem Pack-Geometriemanager

Der Code erzeugt zwei Buttons die untereinander im Toplevel-Window angeordnet sind. Die Buttons wurden in der angegebenen Reihenfolge (erst $btn1, dann $btn2) nacheinander an die obere Kante des restlichen Raumes im Master gepackt, der gleichzeitig auf die passende Größe eingestellt wird. Auch die Größe der Buttons richtet sich nach der Größe Ihres Inhaltes, in diesem Fall jeweils ein Textstring.

Packer-Option -side

An welche Kante gepackt wird, kann durch die -side Option eingestellt werden. In diesem Fall wird der Defaultwert top verwendet. Durch die Angabe der Option -side kann die Anordnung der Widgets gesteuert werden.

Durch die Angabe von -side => 'left' ordnen sich die Buttons von links nach rechts an:

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $btn1 = $mw->Button(-text => 'Hallo');
my $btn2 = $mw->Button(-text => 'Welt');
$btn1->pack(-side => 'left');
$btn2->pack(-side => 'left');

$mw->MainLoop();

2 Buttons nebeneinander anordnen mit dem Pack-Geometriemanager

Da die Größe und Form des Masters jeweils an die Slaves angepasst wurde, besteht momentan kein Unterschied zwischen der Größe der Widgets und Ihrer Parzellen. Wenn man das Fenster etwas vergrößert, werden die Buttons an die Mitte der linken Seite gepackt. Genauer genommen werden ihre Parzellen an die linke Seite des Restlichen Raumes im Master plaziert und die Buttons befinden sich in der Mitte Ihrer Parzellen.

Vergrößerung des Masters bei pack

Packer-Option -fill

Mit der Option -fill wird gesteuert, in welche Richtungen ein Widget ausgedehnt wird.

In nachfolgendem Code liegt der Hallo-Button durch -side => 'top' am oberen Rand. Außerdem bewirkt -fill => 'both', dass die Buttons den gesamten Platz in x- und y-Richtung einnehmen. Statt both oder none kann auch x oder y stehen. Die Ausdehnung findet dann in der entsprechenden Richtung statt.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $btn1 = $mw->Button(-text => 'Hallo');
my $btn2 = $mw->Button(-text => 'Welt');
$btn1->pack(-side => 'top', -fill => 'both');
$btn2->pack(-side => 'left', -fill => 'both');

$mw->MainLoop();

Vergrößerung des Masters bei pack

Packer-Optionen -expand und -anchor

Der welt-Button befindet sich jezt in der unteren, linken Ecke seiner Parzelle, die durch -expand => 1 den gesamten, restlichen Platz im Window einnimmt. Da die fill-Option wieder den Wert none hat, ist die Button-Größe wieder an den angezeigten Textstring angepasst. Die Lage des Button-Widgets in seiner Parzelle wird durch die anchor-Option bestimmt. Ihr Argument ist eine Abkürzung der Himmelsrichtungen (n, ne, nw, w, s, sw, ...) oder center für Mitte, was der Standardwert wäre.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $btn1 = $mw->Button(-text => 'Hallo');
my $btn2 = $mw->Button(-text => 'Welt');
$btn1->pack(-side => 'top', -fill => 'both');
$btn2->pack(-fill => 'none', -expand => 1, -anchor => 'se');

$mw->MainLoop();

Kombination aus anchor und expand beim pack-Geometriemanager

Gleichmäßige Verteilung

Wird beiden Parzellen erlaubt zu expandieren, dann wird der Platz im Master-Widget gleichmäßig aufgeteilt.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $btn1 = $mw->Button(-text => 'Hallo');
my $btn2 = $mw->Button(-text => 'Welt');
$btn1->pack(-side => 'top', -fill => 'none', -expand => 1);
$btn2->pack(-side => 'top', -fill => 'none', -expand => 1);

$mw->MainLoop();

gleichmäßig verteile Buttons mit pack

Beide Buttons sind jetzt wieder an die obere Kante im Master, mittig in ihren expandierten Parzellen konfiguriert. Dass in diesem Fall der Platz für die Parzellen gleichmäßig aufgeteilt wird, ist noch besser sichtbar, wenn man die Buttons den Platz der Parzelle ausfüllen lässt (mit der Option -fill).

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $btn1 = $mw->Button(-text => 'Hallo');
my $btn2 = $mw->Button(-text => 'Welt');
$btn1->pack(-side => 'top', -fill => 'none', -expand => 1);
$btn2->pack(-side => 'top', -fill => 'none', -expand => 1);

$mw->MainLoop();

gleichmäßig verteile Buttons mit pack und fill

Pack-Reihenfolge

Natürlich hat die Reihenfolge des Packens einen Einfluss auf die Darstellung. Die Widgets werden in der gleichen Reihenfolge in den Master gebracht, wie sie dem Packer bekannt gemacht werden und er sie dann in seiner sogenannten Packing-Liste verwaltet. Die Reihenfolge in dieser Liste kann mit den Option -after oder -before manipuliert werden.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $btn1 = $mw->Button(-text => 'Hallo');
my $btn2 = $mw->Button(-text => 'Welt');

# Button 1 wird zuerst bekannt gemacht
$btn1->pack;

# steht dann aber nicht oben, weil -before verwendet wird
$btn2->pack(-before => $btn1);

$mw->MainLoop();

umgekehrte Pack-Reihenfolge

Der Grid-Manager

Der Gridmanager teilt den Raum im Master in Form eines Gitters auf. Einzelne Widgets lassen sich darin ganz einfach plazieren, indem man Zeile und Spalte angibt. Es ist möglich Widgets so zu konfigurieren, dass sie sich über mehrere Zeilen oder Spalten ausdehnen. Außerdem kann man, ähnlich wie beim Packer, das Plazierungs- und Ausdehnungsverhalten innerhalb einer Zelle mit der Angabe von Himmelsrichtungen beeinflussen.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

$mw->Label(
    -text => 'Zahl:',
)->grid(-row => 0, -column => 0,);

$mw->Entry()->grid(-row => 0, -column => 1, -sticky => 'we');

$mw->Button(
    -text => 'Drück mich',
)->grid(-row => 1, -column => 0, -columnspan => 2, -sticky => 'nswe');

$mw->gridRowconfigure(1, -weight => 1);
$mw->gridColumnconfigure(0, -weight => 1);
$mw->gridColumnconfigure(1, -weight => 1);

$mw->MainLoop();

Beispiel für Grid-Geometriemanager

Formular-Layout mit grid

Der Grid-Geometriemanager für Perl/Tk ordnet Widgets in einem Raster an, das aus Zeilen und Spalten besteht (ähnlich einer HTML-Tabelle).

Ein Formular zur Dateneingabe lässt sich mit deshalb mir grid sehr angenehm erstellen, da man Widgets einfach in den Spalten und Zeilen anordnet. Das ist genau das, was man für ein Formular machen möchte.

Üblicherweise verwendet man Labels und Entry-Widgets in Formularen. Das Entry-Widget nimmt die Daten entgegen und das Label beschreibt, was man da eingeben kann.

In der Regel sind die Beschriftungen im Label unterschiedlich lang und die Eingabefelder haben auch selten die gleiche Länge (Beispiel Datums-Eingabe vs. Freitext-Feld). Bei Grid ist das glücklicherweise egal. Die Widgets werden einfach zu einer Zeile und Spalte zugeordnet, und der Gridmanager übernimmt die Anordnung.

Das untenstehende Beispiel zeigt eine Eingabemaske mit 4 Spalten zur Erfassung von Daten. Das könnte z.B. ein Front-End für eine Datenbank sein. Der Quellcode kann natürlich gekürzt und geschickter erstellt werden, aber so sieht man ziemlich genau, was gemacht wird.

#!perl

use strict;
use warnings;
use Tk;

my $mw = tkinit();

my $form = $mw->Frame->pack(-fill => 'both', -expand => 1);

##################
# labels

my $label_name = $form->Label(-text => 'Full name:');
my $label_title = $form->Label(-text => 'Title:');
my $label_address = $form->Label(-text => 'Address:');
my $label_phone = $form->Label(-text => 'Office phone number:');
my $label_fax = $form->Label(-text => 'Fax:');

my $label_user_name = $form->Label(-text => 'User name:');
my $label_homesys = $form->Label(-text => 'Home system name:');
my $label_email = $form->Label(-text => 'E-mail:');
my $label_url = $form->Label(-text => 'URL:');

##################
# entries

my $entry_name = $form->Entry(-width => 20);
my $entry_title = $form->Entry(-width => 10);
my $entry_address = $form->Entry(-width => 20);
my $entry_phone = $form->Entry(-width => 10);
my $entry_fax = $form->Entry(-width => 10);

my $entry_user_name = $form->Entry(-width => 12);
my $entry_homesys = $form->Entry(-width => 12);
my $entry_email = $form->Entry(-width => 20);
my $entry_url = $form->Entry(-width => 20);

##################
# Set up grid layout (instead of packing)

$label_name->grid(-row => 0, -column => 0, -sticky => 'e');
$label_title->grid(-row => 1, -column => 0, -sticky => 'e');
$label_address->grid(-row => 2, -column => 0, -sticky => 'e');
$label_phone->grid(-row => 3, -column => 0, -sticky => 'e');
$label_fax->grid(-row => 4, -column => 0, -sticky => 'e');

$entry_name->grid(-row => 0, -column => 1, -sticky => 'snew');
$entry_title->grid(-row => 1, -column => 1, -sticky => 'snew');
$entry_address->grid(-row => 2, -column => 1, -sticky => 'snew');
$entry_phone->grid(-row => 3, -column => 1, -sticky => 'snew');
$entry_fax->grid(-row => 4, -column => 1, -sticky => 'snew');


$label_user_name->grid(-row => 0, -column => 2, -sticky => 'e');
$label_homesys->grid(-row => 1, -column => 2, -sticky => 'e');
$label_email->grid(-row => 2, -column => 2, -sticky => 'e');
$label_url->grid(-row => 3, -column => 2, -sticky => 'e');

$entry_user_name->grid(-row => 0, -column => 3, -sticky => 'snew');
$entry_homesys->grid(-row => 1, -column => 3, -sticky => 'snew');
$entry_email->grid(-row => 2, -column => 3, -sticky => 'snew');
$entry_url->grid(-row => 3, -column => 3, -sticky => 'snew');

##################
# Allow automatic resizing of grid layout

$form->gridRowconfigure(0, -weight => 1);
$form->gridRowconfigure(1, -weight => 1);
$form->gridRowconfigure(2, -weight => 1);
$form->gridRowconfigure(3, -weight => 1);
$form->gridRowconfigure(4, -weight => 1);
$form->gridColumnconfigure(0, -weight => 1);
$form->gridColumnconfigure(1, -weight => 2);
$form->gridColumnconfigure(2, -weight => 1);
$form->gridColumnconfigure(3, -weight => 2);

$mw->MainLoop();

Das Eingabe-Formular sieht dann so aus:

UI-Eingabemaske mit Grid-Geometriemanager

Man beachte, wie manche Labels mehr Platz als andere brauchen, aber trotzdem alles tabellarisch angeordnet ist.

Um den Text der Labels rechtsbündig zum Eingabefeld hin auszurichten wurde die Option -sticky => 'e' verwendet, also eine Ausrichtung des Grid-Zelleninhaltes nach Osten (e = engl. east). Die Eingabefelder hingegen sollen den gesamten, in ihrer Zelle zur Verfügung stehenden Platz verwenden. Das erreicht man, indem man die Ausdehnung in alle Richtungen (nswe) spezifiziert.

Allgemeines

Um ein Widget wieder von der Bildfläche verschwinden zu lassen, gibt es zwei verschiedene Methoden. Zunächst kann man dem jeweiligen Geometry-Manager mitteilen, dass er das zu löschende Widget nicht mehr kontrollieren soll. Dafür kennen alle drei Manager die forget-Option, die als Argument den Pfadnamen des zu löschenden Windows benötigen. Das jeweilige Widget existiert aber nach dieser Maßnahme immer noch und kann zu einem späteren Zeitpunkt wieder auf den Bildschirm gebracht werden. Um ein Widget und die in der Baumstruktur unter ihm stehenden endgültig aus der Applikation zu entfernen, gibt es den Tk-Befehl destroy. Als Argument benötigt er eine Liste der Pfadnamen der zu löschenden Widgets. Generell ist es möglich alle drei Manager miteinander zu kombinieren, d.h. verschiedene Widgets einer Applikation mit verschiedenen Geometry-Managern zu kontrollieren. Man sollte aber in diesem Fall genauere Kenntnisse über die jeweiligen Methoden haben.

Quellen

Top