GUI mit Perl/Tk

Perl/Tk-Tutorial

Kapitel 7: Steuerelemente - Checkbutton, Radiobutton und Listbox

Damit dieser Kurs möglichst kompakt bleibt, werden jetzt gleich 3 Steuerelemente auf einmal vorgestellt. Sie sehen ja auch an den vergangenen Beispielen, dass sich diese Elemente stets miteinander verzahnen.

Hier geht es um Checkbutton, Radiobutton und das Element Listbox.

radio_check_buttons1.pl

#!/usr/bin/perl

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

my $main   = MainWindow->new();

my $check1 = $main->Checkbutton(-text => "Kontrollkästchen 1")->pack();
my $check2 = $main->Checkbutton(-text => "Kontrollkästchen 2")->pack();

my $radio1 = $main->Radiobutton(-text => "Optionsfeld 1")->pack();
my $radio2 = $main->Radiobutton(-text => "Optionsfeld 2")->pack();
my $radio3 = $main->Radiobutton(-text => "Optionsfeld 3")->pack();

my $liste  = $main->Listbox(-relief  => 'sunken',
                            -width   => -1,      # Shrink to fit
                            -height  => 5,
                            -setgrid => 1,
                           )
                    ->pack();

$liste->insert('end', $_) for qw(Teil1 Teil2 Teil3 Teil4 Teil5);

$main->MainLoop();

So sieht das Ergebnis aus:

tk9.png

Man sieht auf dem erzeugten Fenster schon die angelegten Elemente, aber zumindest mit den Radiobuttons gibt es ein Problem, denn alle drei sind angewählt und lassen sich auch nicht deaktivieren. Das liegt daran, dass von einer Gruppe Radiobuttons genau einer aktiv ist und wir hier drei Gruppen haben. Die Gruppen werden über die mit -variable zugewiesene Variable gebildet, Radiobuttons, die zu einer Gruppe gehören, weist man die gleiche Variable zu.


Hier ein funktionstüchtiges, dafür etwas komplexeres Beispiel. Hier werden Frames verwendet, die eigentlich erst im nächsten Kapitel vorgestellt werden, also werfen Sie ggf. einen Blick in Kapitel 8, wenn Sie es genau wissen wollen, anderenfalls nehmen Sie erstmal hin, dass Frames Behälter für andere Steuerelemente sind.

radio_check_buttons2.pl

#!/usr/bin/perl

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

#
# Hauptfenster:
#
my $mw     = MainWindow->new();
$mw->title('Eiswähler');

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

#
# Checkbuttons:
#
my $f1     = $fo->Frame(
    -borderwidth => 3,
    -relief      => 'groove',
)->pack(
    -side        => 'right',
    -expand      => 1,
    -fill        => 'both',
);

my $sahne;
my $waffel;

$f1->Checkbutton(
    -text     => "Sahne",
    -variable => \$sahne,
)->pack(-anchor   => 'w');

$f1->Checkbutton(
    -text     => "Extrawaffel",
    -variable => \$waffel,
)->pack(-anchor   => 'w');

#
# Radiobuttons1:
#
my $f2 = $fo->Frame(
    -borderwidth => 3,
    -relief      => 'groove',
)->pack(
    -side        => 'right',
    -expand      => 1,
    -fill        => 'both',
);

my @streuselsorten = (
    'keine Streusel',
    'Schokoladenstreusel',
    'bunte Streusel',
    'Krokantstreusel',
    'Zartbitterabrieb',
);

my $streusel = 0;
for my $i (0..$#streuselsorten) {
    $f2->Radiobutton(
        -text     => $streuselsorten[$i],
        -variable => \$streusel,
        -value    => $i,
    )->pack(-anchor   => 'w');
}

#
# Radiobuttons2:
#
my $f3 = $fo->Frame(
    -borderwidth => 3,
    -relief      => 'groove',
)->pack(
    -side        => 'right',
    -expand      => 1,
    -fill        => 'both',
);

my @saucensorten = (
    'keine Sauce',
    'Erdbeersauce',
    'Schokoladensauce',
    'Tropico',
);

my $sauce = 0;
for my $i (0..$#saucensorten) {
    $f3->Radiobutton(
        -text     => $saucensorten[$i],
        -variable => \$sauce,
        -value    => $i,
    )->pack(-anchor   => 'w');
}

#
# Listbox:
#
my $listbox  = $fo->Listbox(
    -relief     => 'sunken',
    -width      => -1,       # Shrink to fit
    -setgrid    => 1,
    -selectmode => 'single',
)->pack(
    -side       => 'right',
    -expand     => 1,
    -fill       => 'both',
);

my @kugeln = (
    'Eine Kugel',
    'Zwei Kugeln',
    'Drei Kugeln',
    'Vier Kugeln',
    'Fünf Kugeln',
);
for my $kugel ( @kugeln ) {
    $listbox->insert('end', $kugel);
}
$listbox->selectionSet(2, 2); # Default : drei Kugeln

#
# OK-Button:
#
$mw->Button(
    -text    => 'OK',
    -command => sub {
        print "Sahne      : ", $sahne?'ja':'nein',         "\n",
              "Extrawaffel: ", $waffel?'ja':'nein',        "\n",
              "Streusel   : ", $streuselsorten[$streusel], "\n",
              "Sauce      : ", $saucensorten[$sauce],      "\n";
        my @l = $listbox->get(0, 'end');
        my @s = $listbox->curselection();
        print $l[$_], "\n" for (@s);
        $mw->destroy();
    },
)->pack(
    -side   => 'bottom',
    -expand => 0,
    -fill   => 'none',
    -ipadx  => 20,
    -pady   => 2,
);

$mw->MainLoop();

So sieht das Ergebnis aus:

tk9_b.png

Ausgabe des Programms:

Sahne      : ja
Extrawaffel: nein
Streusel   : Krokantstreusel
Sauce      : Erdbeersauce
Zwei Kugeln

Wie man sieht, kommt man nun an alle vom Benutzer eingestellten Werte heran.

Je nach Vorkenntnissen in Perl bedarf dieses Beispiel nun mehr oder weniger langer Kommentare, deshalb erkläre ich das Programm Abschnitt für Abschnitt:

Vorrede:

#!/usr/bin/perl

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

Der übliche Programmkopf, mit use utf8;, damit die Umlaute richtig dargestellt werden.

Definition des Hauptfensters:

my $mw     = MainWindow->new();
$mw->title('Eiswähler');

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

Hier wird das Hauptfenster $mw definiert, der Fenstertitel vergeben und ein Rahmen $fo angelegt, der oben gepackt wird und sich in alle Richtungen ausdehnen darf. In diesen Rahmen kommen alle weiteren Elemente mit Ausnahme des OK-Buttons.

Die Checkbuttons

Auswahl besonderer Extras:

my $f1     = $fo->Frame(
    -borderwidth => 3,
    -relief      => 'groove',
)->pack(
    -side        => 'right',
    -expand      => 1,
    -fill        => 'both',
);

my $sahne;
my $waffel;

$f1->Checkbutton(
    -text     => "Sahne",
    -variable => \$sahne,
)->pack(-anchor   => 'w');

$f1->Checkbutton(
    -text     => "Extrawaffel",
    -variable => \$waffel,
)->pack(-anchor   => 'w');

Hier wird ein Rahmen $f1 angelegt, in dem die Checkbuttons dargestellt werden. Der Frame wird in der Reliefart "groove" und mit einem 3 Pixel breiten Rand dargestellt, er ist also nicht nur als Trennung fürs Packen da, sondern auch als optische Abtrennung der Checkbuttons von den anderen Fensterelementen. Die Angabe -anchor => 'w' beim Packen der Checkbuttons bewirkt, dass diese im Frame linksbündig dargestellt werden.

Die Variablen $sahne und $waffel werden an die beiden Checkbuttons gebunden.

Die erste Radiobutton-Gruppe

Hier wird die Streuselsorte gewählt:

my $f2 = $fo->Frame(
    -borderwidth => 3,
    -relief      => 'groove',
)->pack(
    -side        => 'right',
    -expand      => 1,
    -fill        => 'both',
);

my @streuselsorten = (
    'keine Streusel',
    'Schokoladenstreusel',
    'bunte Streusel',
    'Krokantstreusel',
    'Zartbitterabrieb',
);

my $streusel = 0;
for my $i (0..$#streuselsorten) {
    $f2->Radiobutton(
        -text     => $streuselsorten[$i],
        -variable => \$streusel,
        -value    => $i,
    )->pack(-anchor   => 'w');
}

Hier wird ein Rahmen $f2 angelegt, in dem die Radiobuttons analog zum Rahmen $f1 dargestellt werden. Die Angabe -anchor => 'w' beim Packen der Checkbuttons bewirkt auch hier, dass diese im Frame linksbündig dargestellt werden.

Die einzelnen Streuselsorten werden im Array @streuselsorten vorgehalten, die Variable $streusel enthält den Index der ausgewählten Bestreuselung (oder -1, falls noch kein Radiobutton dieser Gruppe ausgewählt wurde).

Mit -variable => \$streusel wird die Variable $streusel an alle Radiobuttons dieser Gruppe gebunden, mit -value => $i wird für jeden Radiobutton festgelegt, welchen Wert die Variable bei der Anwahl des Buttons bekommen soll.

Andersherum werden aber auch alle diejenigen Radiobuttons, an die diese Variable gebunden wurde, aktiviert, wenn der Wert der Variablen auf irgend eine andere Weise auf diesen Wert gesetzt wird. Alle Radiobuttons dieser Gruppe mit anderen Werten werden dann deaktiviert.

Übrigens: Theoretisch können mehrere Radiobuttons den gleichen Wert zugewiesen bekommen, dann können alle diese Buttons aber nur gemeinsam an oder aus sein, was i.A. dem Sinn von Radionbuttons widerspricht.

Die zweite Radiobutton-Gruppe

Hier wird die Saucensorte gewählt:

my $f3 = $fo->Frame(
    -borderwidth => 3,
    -relief      => 'groove',
)->pack(
    -side        => 'right',
    -expand      => 1,
    -fill        => 'both',
);

my @saucensorten = (
    'keine Sauce',
    'Erdbeersauce',
    'Schokoladensauce',
    'Tropico',
);

my $sauce = 0;
for my $i (0..$#saucensorten) {
    $f3->Radiobutton(
        -text     => $saucensorten[$i],
        -variable => \$sauce,
        -value    => $i,
    )->pack(-anchor   => 'w');
}

Diese Radiobuttongruppe dient der Saucenauswahl und ist völlig analog zur Radiobuttongruppe 1 (Streuselauswahl). Hier heißt das Array mit den Saucen @saucensorten und die Index- bzw. Wert-Variable $sauce.

Wichtig ist, dass bei allen Radiobuttongruppen ein Defaultwert gesetzt wird, der einem der zugeordneten Werte entspricht, so dass beim Programmstart in jeder Gruppe ein Radiobutton angewählt ist. (In diesem Beispiel sind das die Werte 'keine Streuseln' und 'keine Sauce'.)

Die Listbox

Hier wird die Anzahl an Eiskugeln ausgewählt:

my $listbox  = $fo->Listbox(
    -relief     => 'sunken',
    -width      => -1,       # Shrink to fit
    -setgrid    => 1,
    -selectmode => 'single',
)->pack(
    -side       => 'right',
    -expand     => 1,
    -fill       => 'both',
);

my @kugeln = (
    'Eine Kugel',
    'Zwei Kugeln',
    'Drei Kugeln',
    'Vier Kugeln',
    'Fünf Kugeln',
);
for my $kugel ( @kugeln ) {
    $listbox->insert('end', $kugel);
}
$listbox->selectionSet(2, 2); # Default : drei Kugeln

Hier wird zunächst eine Listbox erzeugt, die ganz links angezeigt wird (da sie letztes Element einer Reihe von rechts angefügten Elementen ist). Als Refiefart wird mit -relief => 'sunken' 'sunken' ausgewählt, die Option -width => -1 veranlasst die Listbox so breit zu werden wie ihr breitester Eintrag. Die Option -setgrid => 1 gibt an, dass die Listbox Texteinträge anzeigt. Dies ist etwa bei Größenänderungen des Fensters interessant, dort werden dann immer nur ganze Zeilen mehr oder weniger in der Listbox angezeigt. Die Option "-selectmode => 'single'" schließlich bewirkt, dass immer nur ein Eintrag ausgewählt werden kann. Schließlich macht es nicht Sinn, in einer Bestellung (für ein Eis) etwa sowohl 2 als auch 4 Kugeln bestellen zu wollen. Näheres zu dieser und anderen Optionen (wie immer) unter perldoc Tk::Listbox und perldoc Tk::Options.

Mit $listbox->selectionSet(2, 2) wird der zweite Eintrag ("drei Kugeln") aktiviert. Übrigens: Nähme man keine Defaultbelegung vor, so wäre kein Eintrag ausgewählt und die Methode curselection würde eine leere Liste zurückliefern.

Der Ok-Button

Ende des Programms und Ausgabe der durch den Benutzer vorgenommenen Einstellungen:

$mw->Button(
    -text    => 'OK',
    -command => sub {
        print "Sahne      : ", $sahne?'ja':'nein',         "\n",
              "Extrawaffel: ", $waffel?'ja':'nein',        "\n",
              "Streusel   : ", $streuselsorten[$streusel], "\n",
              "Sauce      : ", $saucensorten[$sauce],      "\n";
        my @l = $listbox->get(0, 'end');
        my @s = $listbox->curselection();
        print $l[$_], "\n" for (@s);
        $mw->destroy();
    },
)->pack(
    -side   => 'bottom',
    -expand => 0,
    -fill   => 'none',
    -ipadx  => 20,
    -pady   => 2,
);

Das umfangreichste hier ist die Funktion, die dem Button per -command zugewiesen wird, der Rest ist schon aus Kapitel 3 (Tk::Button) und Kapitel 6 (Geometriemanager pack) bekannt.

Zur Funktion: Hier werden nun die vom Benutzer vorgenommenen (oder in der Defaulteinstellung belassenen) Werte der Bedienelemente abgelesen und auf der Konsole ausgegeben.

Im Einzelnen erhält man diese Werte auf folgende Weise: Die Checkbuttonwerte (wahr oder falsch) liegen in den Variablen $sahne und $waffel vor. Die Informationen über Streusel und Sauce liegen als Indices der zugehörigen Arrays @streuselsorten und @saucensorten in den Variablen $streusel und $sauce vor.

Die ausgewählten Daten der Listbox werden so geholt, als ob mehrere Einträge gleichzeitig ausgewählt werden könnten. Dies ist eigentlich nicht nötig, da durch die Option -selectmode => 'single' nur die Auswahl eines Eintrages möglich ist, aber es schadet nicht, auch die anderen Fälle mit zu bearbeiten.

Die Methode get holt alle Einträge der Listbox im angegebenen Bereich (hier alle), die Methode curselection holt eine Liste aller ausgewählter Einträge der Listbox. Ist kein Eintrag ausgewählt, so ist die Liste leer.

Übrigens: Da mit $listbox->selectionSet(2, 2) ein Defaulteintrag für die Listbox erzeugt und mit -selectmode => 'single' die Auswahl von mehr als einem Eintrag verhindert wurde, ist eigentlich sichergestellt, dass @l genau ein Element hat. Somit könnte man dieses auch mit my ($l) = $listbox->curselection() ermitteln und ausgeben.

Das Nachspiel

Aufruf der MainLoop:

$mw->MainLoop();

Startet die Tk-Verarbeitungsschleife.


Wenn man den Listboxabschnitt folgendermaßen ändert:

my $listbox  = $fo->Listbox(
    -relief     => 'sunken',
    -width      => -1,       # Shrink to fit
    -setgrid    => 1,
    -selectmode => 'extended',
)->pack(
    -side       => 'right',
    -expand     => 1,
    -fill       => 'both',
);

my @kugeln = (
    'Vanille',
    'Schokolade',
    'Erdbeer',
    'Heidelbeer',
    'Joghurt',
    'Amarena',
    'Malaga',
    'Wallnuss',
    'Haselnuss',
    'weiße Schokolade',
    'After Eight',
);

for my $kugel ( @kugeln ) {
    $listbox->insert('end', $kugel);
}
$listbox->selectionSet(0, 2); # Default : Fürst Pückler Art

erhält man folgendes Ergebnis:

Eisauswahl nach Fürst-Pückler-Art

Ausgabe im oben gezeigten Fall:

Sahne      : ja
Extrawaffel: ja
Streusel   : Zartbitterabrieb
Sauce      : Schokoladensauce
Vanille
Schokolade
Malaga
Wallnuss
weiße Schokolade

Der Selectmode 'extended' ermöglicht die Windows-typische Auswahl mit Shift+Mausklick und Ctrl+Mausklick. Mit dem Selectmode 'multiple' kann man jeden Eintrag der Liste mit einem einzelnen Klick an- oder abwählen.


Wie immer findet man nähere Informationen in der mitgelieferten Perldokumentation, oder den Widget-Seiten zu Checkbutton, Radiobutton und Listbox.

Top