GUI mit Perl/Tk

Perl/Tk-Tutorial

Kapitel 13: Steuerelemente - Menu, Menubutton, Message

Als nächstes lernen Sie Steuerelemente kennen, mit denen Sie Menüs erzeugen können, wie man sie von Standardanwendungen her kennt.

menu1.pl

#!perl

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

# Variablen-Deklarationen - die Inhalte werden in Labeln angezeigt:
my ($o_radio, $o_check1, $o_check2) = ('r1', 0, 0);

my $mw = MainWindow->new();

#----------------------------------------------------------
# Zuerst erstellen wir eine horizontale Menübar
#----------------------------------------------------------

my $mb = $mw->Frame(-relief => 'ridge', -bd => 5);

# Jetzt erstellen wir die Menübuttons für die Menuebar
my $m_file = $mb->Menubutton(
    -text      => "Datei",
    -underline => 1,
);
my $m_edit = $mb->Menubutton(
    -text      => "Bearbeiten",
    -underline => 0,
);
my $m_opts = $mb->Menubutton(
    -text      => "Optionen",
    -underline => 0,
);
my $m_help = $mb->Menubutton(
    -text      => "Hilfe",
    -underline => 0,
);
# underline = Buchstabennummer die Unterstichen werden soll


# Zeig es uns:
$mb    ->pack(-side => "top", -fill => "x");
$m_file->pack(-side => "left");
$m_edit->pack(-side => "left");
$m_opts->pack(-side => "left");
$m_help->pack(-side => "right");


#----------------------------------------------------------
# Jetzt erstellen wir die Untermenues:
#----------------------------------------------------------

# Die Dateimenues
$m_file->command(
    -label   => "Neu",
    -command => [\&file, "neu"],
);
$m_file->command(
    -label   => "Öffnen",
    -command => [\&file, "open"],
);
$m_file->command(
    -label   => "Speichern",
    -command => [\&file, "speichern"],
    -state   => "disabled",  #Deaktiviert
);
$m_file->command(
    -label   => "Speichern unter...",
    -command => [\&file, "speichern unter..."],
    -state   => "disabled",    #Deaktiviert
);
# Ein Seperator der immer unten positioniert wird  und von den anderen getrennt ist
$m_file->separator();
$m_file->command(
    -label => "Exit",
    -underline => 1,
    -command => sub { exit 0; },
);

# Berarbeiten Untermenues
$m_edit->command(
    -label   => "Kopieren",
    -command => [\&edit, "kopieren"],
);
$m_edit->command(
    -label   => "Ausschneiden",
    -command => [\&edit, "ausschneiden"],
);
$m_edit->command(
    -label => "Einfügen",
    -command => [\&edit, "einfuegen"],
);

# Optionsmenues
$m_opts->command(
    -label   => "Ausführen",
    -command => [\&opts, "ausfuehren"],
);
$m_opts->separator();
#Checkboxen und Radiobuttons
$m_opts->checkbutton(
    -label    => "Check 1",
    -variable => \$o_check1,
    -command  => [\&opts, "check"],
);
$m_opts->checkbutton(
    -label    => "Check 2",
    -variable => \$o_check2,
    -command  => [\&opts, "check"],
);
$m_opts->separator();
$m_opts->radiobutton(
    -label    => "Radio 1",
    -variable => \$o_radio,
    -value    => "r1",
    -command  => [\&opts, "radio"],
);
$m_opts->radiobutton(
    -label    => "Radio 2",
    -variable => \$o_radio,
    -value    => "r2",
    -command  => [\&opts, "radio"],
);

# Hilfe Menu
$m_help->command(
    -label   => "Inhalt",
    -command => [\&help, "inhalt"],
);
$m_help->command(
    -label   => "Hilfe",
    -command => [\&help, "hilfe"],
);
$m_help->separator();

#Ein Untermenue im Untermenue:
my $m_help_about = $m_help->cget('-menu')->Menu();
$m_help->cascade(
    -label => "Über...",
    -menu  => $m_help_about,
);

#Inhalt des Unter-Untermenues:
$m_help_about->command(
    -label   => "Perl",
    -command => [\&about, "perl"],
);
$m_help_about->command(
    -label   => "Tk",
    -command => [\&about, "tk"],
);
$m_help_about->command(
    -label   => "UNIX",
    -command => [\&about, "unix"],
);
$m_help_about->command(
    -label   => "C/C++",
    -command => [\&about, "C/C++"],
);

#----------------------------------------------------------
# Start des Programms:
#----------------------------------------------------------

$mw->MainLoop();


#----------------------------------------------------------
# Die Callback-Funktionen:
#----------------------------------------------------------

sub file {
    my ($arg) = @_;
    my $tw = $mw->Toplevel(-title => "Datei $arg");
    my $mes = $tw->Message(
        -text => "Datei $arg ist nicht aktiv",
        -width => '10c', -justify => 'center',
    )->pack(-side => 'top');
    my $but = $tw->Button(
        -text => "Schließen",
        -command => [$tw => 'destroy'],
    )->pack(-side => 'top');
} # /file

sub edit {
    my ($arg) = @_;
    my $tw = $mw->Toplevel(-title => "Bearbeitung $arg");
    my $mes = $tw->Message(
        -text => "Bearbeitung $arg ist nicht aktiv",
        -width => '10c', -justify => 'center',
    )->pack(-side => 'top');
    my $but = $tw->Button(
        -text => "Schließen",
        -command => [$tw => 'destroy'],
    )->pack(-side => 'top');
} # /edit

sub opts {
    my ($arg) = @_;
    print "opts   : $arg\n";
    print "radio  : $o_radio\n";
    print "check 1: $o_check1\n";
    print "check 2: $o_check2\n";
} # /opts

sub help {
    my ($arg) = @_;
    my $tw=$mw->Toplevel(-title => "Hilfe $arg");
    my $mes=$tw->Message(
        -text => "Hilfe $arg ist nicht aktiv",
        -width => '10c', -justify => 'center',
    )->pack(-side => 'top');
    my $but=$tw->Button(
        -text => "Schließen",
        -command => [$tw => 'destroy'],
    )->pack(-side => 'top');
} # /help

sub about {
    my ($arg) = @_;
    print "about: $arg\n";
} # /about

So sieht das Ergebnis aus:

tk21.png

Das Programm scheint auf den ersten Blick durch seine Länge etwas abschreckend. Es ist aber im Prinzip recht simpel und der allgemeine Vorgang eine Menü aufzubauen. Zuerst wird ein Hauptfenster erzeugt und anschließend ein Frame. Danach erstellen man mit

Menubutton()

die einzelnen Menübuttons. Anschließend erstellen Sie mittels

$m_file->command(-label => "Neu", -command => [\&file, "neu"]);

entsprechende Untermenüs mit dem Label und möglichst auch mit einer Callback-Funktion. Um ein weiteres Untermenü im Untermenü zu erstellen verwendet man:

$m_help_about = $m_help->cget('-menu')->Menu();

Die einzelnen Callback-Funktionen sind eigentlich Nutzlos und öffnen nur ein neues Fenster mit einer Message welches mit

my $mes = $tw->Message(
    -text => "Datei $arg ist nicht aktiv",
    -width => '10c', -justify => 'center',
)->pack(-side => 'top');

erzeugt wurde.


Mit obiger Methode erhält man ein Menü, das "irgendwie" so ähnlich funktioniert, wie man es sich vorstellt, aber nicht genauso, wie es sein soll.

Es gibt aber eine schönere Variante, in der man mit den Cursortasten wie gewohnt (zumindest unter Windows bin ich es so gewohnt, unter Linux bin ich eigentlich hauptsächlich Konsolen gewohnt...) navigieren kann.

Deshalb kommt nachfolgend ein etwas komplexeres Beispiel. Bei diesem etwas umfangreicheren Beispiel kann man gleich mal einige Dinge so sehen, wie sie in größeren Programmen gern gemacht werden (z.B. dass die Tk-Teile nicht einfach so auf der Hauptebene des Programms erzeugt werden, sondern in einer eigenen Funktion, und dass man sich gewisse (mehr oder weniger konstante Dinge in einem globalen Hash merkt)).

Da das Beispielprogramm recht lang ist, zeige ich hier nur den Ausschnitt, der für das Menü relevant ist. Das Beispiel funktioniert in Bezug auf die Bindungen noch eigenwillig, da das Text-Fensterelement alle möglichen eigenen Textbindungen zu haben scheint. Aber der Weg über Alt-d, u (z.B.) funktioniert, oder auch über Alt und Navigation mit den Cursorasten.

Das hier verwendete Text-Widget ist wiedermal ein Vorgriff, im nächsten Kapitel wird es offiziell vorgestellt, und dort sollte man sich dann an dieses Beispiel zurückerinnern.

menu2.pl

#!perl

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

#--------------------------------------------------------------------------
# Hauptfenster erzeugen:
#--------------------------------------------------------------------------
my $mw = MainWindow->new;

$mw->title('Menübeispiel');
$mw->geometry('1024x768');


#--------------------------------------------------------------------------
# Menu:
#--------------------------------------------------------------------------
# 1. Menu-Bar erstellen:
my $menu = $mw->Menu(
    -type => 'menubar',
    -font => 'SegoeUI 13',
);

# 2. Dem Fensterwidget sagen, dass unsere Menubar sein Menu ist:
$mw->configure(-menu => $menu);

# 3. Einträge in der Menu-Bar:
$menu->cascade(
    -label     => 'Datei',
    -underline => 0,
    -font      => 'SegoeUI 13',
);
$menu->cascade(
    -label     => 'Bearbeiten',
    -underline => 0,
    -font      => 'SegoeUI 13',
);
$menu->separator(); # dadurch wird der Rest rechtsbündig
$menu->cascade(
    -label     => 'Hilfe',
    -underline => 0,
    -font      => 'SegoeUI 13',
);

# 4. Die einzelnen Untermenüs definieren:
# Zunächst das Dateimenü:
my $menu_datei = $menu->Menu(
    -font => 'SegoeUI 13', # Schriftart des Menüs
    -tearoff   => 0,                  # Menü nicht abreißbar
    -menuitems => [
        [
            'command'    => 'Datei neu',
            -command     => sub{ datei_neu(); },
            -accelerator => 'Ctrl-N',
            -font        => 'SegoeUI 13',
            -underline   => 6,
        ],
        [
            'command'    => 'Datei öffnen',
            -command     => sub{ datei_oeffnen(); },
            -accelerator => 'F3',
            -font        => 'SegoeUI 13',
            -underline   => 6,
        ],
        [
            'command'    => 'Datei speichern',
            -command     => sub{ datei_speichern(); },
            -accelerator => 'F2',
            -font        => 'SegoeUI 13',
            -underline   => 6,
        ],
        [
            'command'    => 'Datei speichern unter',
            -command     => sub{ datei_speichern_unter(); },
            -accelerator => 'F12',
            -font        => 'SegoeUI 13',
            -underline   => 16,
        ],
        '-',
        [
            'command'    => 'Beenden',
            -command     => sub{ tk_ende(); },
            -accelerator => 'Ctrl-B',
            -font        => 'SegoeUI 13',
            -underline   => 0,
        ],
    ],
);
# Dazu gehört auch, der Menubar zu sagen, dass dieses Untermenü zu seinem
# Eintrag "Datei" gehört:
$menu->entryconfigure('Datei', -menu => $menu_datei);

# Analog für die beiden anderen Menüs:

# Bearbeiten-Menu:
my $menu_bearb = $menu->Menu(
    -font => 'SegoeUI 13',
    -tearoff   => 0,
    -menuitems => [
        [
            'command'    => 'Sortieren',
            -command     => sub{ bearbeiten('sortieren'); },
            -accelerator => 'F5',
            -font        => 'SegoeUI 13',
            -underline   => 0,
        ],
    ],
);
$menu->entryconfigure('Bearbeiten', -menu => $menu_bearb);

# Hilfemenü:
my $menu_hilfe = $menu->Menu(
    -font => 'SegoeUI 13',
    -tearoff   => 0,
    -menuitems => [
        [
            'command'    => "Hilfe",
            -command     => sub{ hilfe(); },
            -accelerator => 'F1',
            -font        => 'SegoeUI 13',
            -underline   => 0,
        ],
        [
            'command'    => "Historie",
            -command     => sub{ historie(); },
            -font        => 'SegoeUI 13',
            -underline   => 1,
        ],
        '-',
        [
            'command'    => "Über menu2",
            -command     => sub{ ueber(); },
            -font        => 'SegoeUI 13',
            -underline   => 0,
        ],
    ],
);
$menu->entryconfigure('Hilfe', -menu => $menu_hilfe);


#--------------------------------------------------------------------------
# Textwidget:
#--------------------------------------------------------------------------
my $text = $mw->Scrolled('Text',
    -scrollbars      => 'osoe',
    -width           => 120,
    -height          => 50,
    -exportselection => 1,
)->pack(
    -expand          => 1,
    -fill            => 'both',
);

#--------------------------------------------------------------------------
# Bindungen:
#--------------------------------------------------------------------------

# Menü aufrufbar machen mit Alt und dem unterstrichenen Buchstaben:
$mw->bind('<Alt-d>',     sub {$menu->postcascade('Datei'     );});
$mw->bind('<Alt-b>',     sub {$menu->postcascade('Bearbeiten');});
$mw->bind('<Alt-h>',     sub {$menu->postcascade('Hilfe'     );});


$mw->bind('<Control-n>', sub{ datei_neu(); });
$mw->bind('<F3>',        sub{ datei_oeffnen(); });
$mw->bind('<F2>',        sub{ datei_speichern(); });
$mw->bind('<F12>',       sub{ datei_speichern_unter(); });
$mw->bind('<Control-b>', sub{ tk_ende(); });

$mw->bind('<F5>',        sub{ bearbeiten('sortieren'); });

$mw->bind('<F1>',        sub{ hilfe(); });

$mw->MainLoop;


sub datei_neu {
    not_implemented();
} # /datei_neu


sub datei_oeffnen {
    not_implemented();
}


sub datei_speichern {
    not_implemented();
}


sub datei_speichern_unter {
    not_implemented();
}


sub bearbeiten {
    not_implemented();
}


sub hilfe {
    not_implemented();
}


sub historie {
    not_implemented();
}


sub ueber {
    not_implemented();
}


sub tk_ende {
    $mw->destroy;
} # /tk_ende


sub not_implemented {
    $mw->messageBox(
        -icon => 'info',
        -type => 'ok',
        -title => 'Nicht implementiert',
        -message => 'Der Quelltext dieser Funktion wurde noch nicht implementiert',
    );
}

So sieht das Ergebnis aus:

Menübeispiel an Hand eines Text-Editors in Perl/Tk

Top