Perl: Anwendungen und fortgeschrittene Techniken [Reprint 2015 ed.] 9783486593259, 9783486259025

Das Buch baut auf dem vom selben Autorenteam erschienen 1. Band "Perl - Grundlagen und effektive Strategien" a

169 22 10MB

German Pages 289 [292] Year 2003

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
1 Vorwort
1.1 Ein Wort zu freier Software
2 Dank
3 Perl-Pakete
3.1 Was sind Perl-Pakete?
3.2 Deklaration von Paketen
3.3 Einbinden von Paketen mit use und require
3.3.1 Die Anweisung use
3.3.2 Die Anweisung require
3.3.3 Wo Perl nach Paketen sucht
3.4 Zugriff auf Bezeichner anderer Pakete
3.5 Pakete initialisieren und terminieren
3.6 Symbole aus anderen Symboltabellen importieren
3.7 Autoloading
4 Module in Perl
4.1 Eigene Perl-Module erzeugen
4.2 Arbeiten mit Perl-Modulen
4.2.1 Arbeiten mit Perl-Modulen in der Standard-Distribution
4.2.2 Arbeiten mit Perl-Modulen in der ActiveState-Distribution
5 Perl mit C-Modulen erweitern
5.1 Ein C-Modul entwickeln
6 Objektorientierte Programmierung
6.1 Grundlagen der Objektorientierten Programmierung
6.1.1 Objekte, Eigenschaften und Methoden
6.1.2 Kapselung von Daten
6.1.3 Klassen, Objekte und Instanzen
6.1.4 Instanzmethoden und -Eigenschaften, Klassenmethoden und -Eigenschaften
6.1.5 Vererbung
6.2 Objektorientierte Programmierung mit Perl
6.2.1 Klassen mit Perl erstellen
6.2.2 Anlegen und Aufrufen von Eigenschafts-Methoden
6.2.3 Methoden, die den Verhaltensweisen eines Objektes entsprechen
6.2.4 Klassen- und Instanzmethoden, Klassen- und Instanzvariablen
6.2.5 Vererbung
6.2.6 Überschreiben von Methoden
6.2.7 Aufrufen von überschriebenen Methoden der Oberklasse
6.2.8 Und die Polymorphie?
7 Datenbank-Programmierung mit Perl/DBI
7.1 Grundlagen des Datenbank-Designs
7.1.1 Relationale Datenstrukturen
7.1.2 Grundregeln des Datenbankdesigns
7.1.3 Normalformlehre
7.1.4 Beziehungsstrukturen
7.1.5 Aufnahmestruktur
7.2 Die Abfragesprache SQL
7.2.1 Der SQL-Standard
7.2.2 Abfragen mit SQL erstellen
7.2.3 Einfache Abfragen mit dem Befehl SELECT
7.2.4 Abfrageeinschränkung mit WHERE
7.2.5 Die Vergleichs-Operatoren von SQL
7.2.6 Die Verknüpfung von Relationen (JOIN)
7.2.7 Funktionen
7.2.8 Datenbanken mit SQL-Befehlen pflegen
7.3 Die Programmierung der Datenbank-Schnittstelle Perl/DBI
7.3.1 Das Konzept von Perl/DBI
7.4 Arbeiten mit DBI/DBD
7.4.1 Vorhandene Datenbank-Treiber abfragen
7.4.2 Verbindung zu einer Datenbank aufbauen
7.4.3 Die Datenbankverbindung schließen
7.4.4 Ausführen einfacher SELECT-Abfragen
7.4.5 Datensätze aus der Abfrage lesen
7.4.6 Datensätze des Resultsets einzeln übernehmen
7.4.7 Metadaten zum Resultset ermitteln
7.4.8 Den ersten Datensatz abfragen
7.4.9 Einzelne Spalten abfragen
7.4.10 Das Resultset als Ganzes übernehmen
7.4.11 Abfragen optimieren
7.4.12 Datenbanken mit SQL-Statements pflegen
7.4.13 Metadaten zur Datenbank ermitteln
8 CGI-Programmierung mit Perl
8.1 Einrichten und Konfigurieren des Apache-Webservers
8.1.1 Den Apache-Webserver auf einem Linux-System installieren
8.1.2 Den Apache-Webserver auf einem Windows-System installieren
8.1.3 Test Ihrer Apache-Installation
8.1.4 Den Apache-Webserver für die Ausführung von Perl/CGI-Skripten konfigurieren
8.2 Was ist CGI?
8.3 Ein kurzer HTML-Exkurs
8.3.1 Das HTML-Skelett
8.3.2 Kommentare
8.3.3 Text
8.3.4 Das textliche Erscheinungsbild
8.3.5 Abschnittsunterteilungen, Absätze und Zeilenumbrüche
8.3.6 Überschriften
8.3.7 Querlinien
8.4 Hyperlinks
8.5 Inline-Grafiken
8.6 Listen
8.6.1 Tabellen
8.6.2 Umlaute und Sonderzeichen
8.7 Ein einfaches CGI-Beispiel zur Einführung
8.8 Ein etwas umfangreicheres Beispiel
8.8.1 Die CGI-Umgebungsvariablen
8.9 Eingabe-Formulare mit Perl und CGI erstellen
8.9.1 Das -Tag von HTML
8.9.2 Das -Tag
8.9.3 Mehrzeilige Textfelder
8.9.4 Das -Tag
8.9.5 Formulardaten auslesen
8.9.6 Ein SQL-Monitor
8.10 Perl/CGI-Anwendungen mit Hilfe von Templates modularisieren
8.11 Perl/CGI und Grafik
8.11.1 Das Modul GD
8.11.2 Grafiken mit Perl/CGI laden und anzeigen
8.11.3 Dynamische Grafiken mit Perl/CGI erstellen
8.11.4 Speichern von Grafiken
8.11.5 Diagramme mit dem Modul GD: :Graph erstellen
9 Grafische Benutzerschnittstellen mit Perl/Tk
9.1 Das Perl/Tk-Toolkit
9.2 Ein einfaches Beispiel
9.3 Grundlegende Widget-Eigenschaften von Perl/Tk
9.3.1 Grundlegende Widget-Optionen
9.3.2 Das Erscheinungsbild von Widgets festlegen
9.3.3 Callbackfunktionen mit der -command-Methode
9.3.4 Bitmaps und Bilder in Widgets anzeigen
9.4 Die Standard-Widgets von Perl/Tk
9.4.1 Das Label-Widget
9.4.2 Das Button-Widget
9.4.3 Checkbutton- und Radiobutton-Widgets
9.4.4 Das Listbox-Widget
9.4.5 Das Scrollbar-Widget
9.4.6 Das Entry-Widget
9.5 Einige ’Nicht-Standard’-Steuerelemente von Perl/Tk
9.5.1 Das NoteBook-Widget von Perl/Tk
9.5.2 Das BrowseEntry-Widget von Perl/Tk
9.5.3 Das HList-Widget von Perl/Tk
9.5.4 Tabellarische Daten mit dem TixGrid-Widget anzeigen
9.6 Geometrie-Management mit Perl/Tk
9.6.1 Der Geometrie-Manager Pack
9.6.2 Widget-Positionierung mit Hilfe von Frames
9.6.3 Benutzerschnittstellen optimieren
9.6.4 Ein Beispiel mit dem Grid-Geometrie-Manager
9.7 Menüs mit Perl/Tk erstellen
9.8 Ein Editor mit Perl/Tk
9.9 Perl/CGI und Perl/Tk
10 Weitergabe der fertigen Anwendung
11 Nachwort
12 Anhang
12.1 Skripten, die bei der Arbeit verwendet wurden
12.1.1 rmtab.pl: Ersetzen von Tabulator-Zeichen durch Leerzeichen
12.1.2 rmcode.pl: Ersetzen von CODE-Tags durch LaTeX-Tags
13 Literaturverzeichnis
Index
Recommend Papers

Perl: Anwendungen und fortgeschrittene Techniken [Reprint 2015 ed.]
 9783486593259, 9783486259025

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Perl Anwendungen und fortgeschrittene Techniken von Helmut Seidel unter Mitwirkung von Prof. Dr. Jürgen Schröter Fachhochschule Landshut

Oldenbourg Verlag München Wien

Titelabbildung: Peter Kornherr Ebenfalls bei Oldenbourg erschien von den Autoren Schröter und Seidel der einführende Band „Perl - Grundlagen und effektive Strategien", ISBN 3-486-258893, der die wichtigsten Sprachelemente von Perl (Skalare, Arrays, Hashes, Referenzen, Formate, Reguläre Ausdrücke) erläutert und sehr intensiv auf die Objektorientierung und komplexe Datenstrukturen in Perl eingeht.

Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über abrufbar.

© 2004 Oldenbourg Wissenschaftsverlag GmbH Rosenheimer Straße 145, D-81671 München Telefon: (089) 45051-0 www.oldenbourg-verlag.de Das Werk einschließlich aller Abbildungen ist urheberrechtlich geschützt. Jede Verwertung außerhalb der Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Das gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Bearbeitung in elektronischen Systemen. Lektorat: Christian Kornherr Herstellung: Rainer Hartl Umschlagkonzeption: Kraxenberger Kommunikationshaus, München Gedruckt auf säure- und chlorfreiem Papier Druck: R. Oldenbourg Graphische Betriebe Druckerei GmbH ISBN 3-486-25902-4

Inhaltsverzeichnis 1

Vorwort

1

1.1

Ein Wort zu freier Software

2

2

Dank

5

3

Perl-Pakete

7

3.1

Was sind Perl-Pakete?

9

3.2

Deklaration von Paketen

10

3.3 3.3.1 3.3.2 3.3.3

Einbinden von Paketen mit use und r e q u i r e Die Anweisung use Die Anweisung r e q u i r e Wo Perl nach Paketen sucht

11 11 12 13

3.4

Zugriff auf Bezeichner anderer Pakete

14

3.5

Pakete initialisieren und terminieren

17

3.6

Symbole aus anderen Symboltabellen importieren

20

3.7

Autoloading

23

4

M o d u l e in Perl

27

4.1

Eigene Perl-Module erzeugen

27

4.2 4.2.1 4.2.2

Arbeiten mit Perl-Modulen Arbeiten mit Perl-Modulen in der Standard-Distribution Arbeiten mit Perl-Modulen in der ActiveState-Distribution

28 28 32

5

Perl mit C - M o d u l e n erweitern

35

5.1

Ein C-Modul entwickeln

36

6

Objektorientierte Programmierung

41

6.1 6.1.1 6.1.2 6.1.3

Grundlagen der Objektorientierten Programmierung Objekte, Eigenschaften und Methoden Kapselung von Daten Klassen, Objekte und Instanzen

42 42 44 45

VI 6.1.4

Inhaltsverzeichnis

6.1.5

Instanzmethoden und -Eigenschaften, Klassenmethoden und -Eigenschaften Vererbung

45 47

6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8

Objektorientierte Programmierung mit Perl Klassen mit Perl erstellen Anlegen und Aufrufen von Eigenschafts-Methoden Methoden, die den Verhaltensweisen eines Objektes entsprechen Klassen- und Instanzmethoden, Klassen- und Instanzvariablen Vererbung Überschreiben von Methoden Aufrufen von überschriebenen Methoden der Oberklasse Und die Polymorphie?

49 49 52 57 62 63 67 68 69

7

Datenbank-Programmierung mit P e r l / D B I

71

7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5

Grundlagen des Datenbank-Designs Relationale Datenstrukturen Grundregeln des Datenbankdesigns Normalformlehre Beziehungsstrukturen Aufnahmestruktur

72 72 74 75 77 80

7.2 7.2.1 7.2.2 7.2.3 7.2.4 7.2.5 7.2.6 7.2.7 7.2.8

Die Abfragesprache SQL Der SQL-Standard Abfragen mit SQL erstellen Einfache Abfragen mit dem Befehl SELECT Abfrageeinschränkung mit WHERE Die Vergleichs-Operatoren von SQL Die Verknüpfung von Relationen (JOIN) Funktionen Datenbanken mit SQL-Befehlen pflegen

81 81 82 83 86 88 90 92 93

7.3 7.3.1

Die Programmierung der Datenbank-Schnittstelle Perl/DBI Das Konzept von Perl/DBI

96 96

7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.4.6 7.4.7 7.4.8 7.4.9 7.4.10 7.4.11 7.4.12

Arbeiten mit DBI/DBD Vorhandene Datenbank-Treiber abfragen Verbindung zu einer Datenbank aufbauen Die Datenbankverbindung schließen Ausführen einfacher SELECT-Abfragen Datensätze aus der Abfrage lesen Datensätze des Resultsets einzeln übernehmen Metadaten zum Resultset ermitteln Den ersten Datensatz abfragen Einzelne Spalten abfragen Das Resultset als Ganzes übernehmen Abfragen optimieren Datenbanken mit SQL-Statements pflegen

98 98 99 104 105 106 107 114 119 120 123 126 129

Inhaltsverzeichnis

VII

7.4.13

Metadaten zur Datenbank ermitteln

132

8

C G I - P r o g r a m m i e r u n g mit P e r l

135

8.1 8.1.1 8.1.2 8.1.3 8.1.4

Einrichten und Konfigurieren des Apache-Webservers Den Apache-Webserver auf einem Linux-System installieren Den Apache-Webserver auf einem Windows-System installieren Test Ihrer Apache-Installation Den Apache-Webserver für die Ausführung von Perl/CGI-Skripten konfigurieren

136 136 137 137

8.2

Was ist CGI?

139

8.3 8.3.1 8.3.2 8.3.3 8.3.4 8.3.5 8.3.6 8.3.7

Ein kurzer HTML-Exkurs Das HTML-Skelett Kommentare Text Das textliche Erscheinungsbild Abschnittsunterteilungen, Absätze und Zeilenumbrüche Überschriften Querlinien

140 140 141 141 141 142 143 143

8.4

Hyperlinks

143

8.5

Inline-Grafiken

144

8.6 8.6.1

Listen Tabellen

145 145

8.6.2

Umlaute und Sonderzeichen

146

8.7

Ein einfaches CGI-Beispiel zur Einführung

147

8.8 8.8.1

Ein etwas umfangreicheres Beispiel Die CGI-Umgebungsvariablen

151 152

8.9 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 8.9.6

Eingabe-Formulare mit Perl und CGI erstellen Das -Tag von HTML Das -Tag Mehrzellige Textfelder Das -Tag Formulardaten auslesen Ein SQL-Monitor

154 155 156 160 161 163 163

8.10

Perl/CGI-Anwendungen mit Hilfe von Templates modularisieren

171

8.11 8.11.1 8.11.2 8.11.3 8.11.4 8.11.5

Perl/CGI und Grafik Das Modul GD Grafiken mit Perl/CGI laden und anzeigen Dynamische Grafiken mit Perl/CGI erstellen Speichern von Grafiken Diagramme mit dem Modul GD: : Graph erstellen

182 183 183 187 192 194

138

VIII

Inhaltsverzeichnis

9

Grafische Benutzerschnittstellen mit P e r l / T k

197

9.1

Das Perl/Tk-Toolkit

197

9.2

Ein einfaches Beispiel

198

9.3 9.3.1 9.3.2 9.3.3 9.3.4

Grundlegende Widget-Eigenschaften von Perl/Tk Grundlegende Widget-Optionen Das Erscheinungsbild von Widgets festlegen Callbackfunktionen mit der -command-Methode Bitmaps und Bilder in Widgets anzeigen

202 202 203 206 206

9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.4.6

Die Standard-Widgets von Perl/Tk Das Label-Widget Das Button-Widget Checkbutton- und Radiobutton-Widgets Das Listbox-Widget Das Scrollbar-Widget Das Entry-Widget

208 209 211 212 216 219 221

9.5 9.5.1 9.5.2 9.5.3 9.5.4

Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk Das NoteBook-Widget von Perl/Tk Das BrowseEntry-Widget von Perl/Tk Das HList-Widget von Perl/Tk Tabellarische Daten mit dem TixGrid-Widget anzeigen

223 223 226 230 236

9.6 9.6.1 9.6.2 9.6.3 9.6.4

Geometrie-Management mit Perl/Tk Der Geometrie-Manager Pack Widget-Positionierung mit Hilfe von Frames Benutzerschnittstellen optimieren Ein Beispiel mit dem Grid-Geometrie-Manager

240 241 242 244 249

9.7

Menüs mit Perl/Tk erstellen

253

9.8

Ein Editor mit Perl/Tk

256

9.9

Perl/CGI und Perl/Tk

266

10

Weitergabe der fertigen Anwendung

267

11

Nachwort

269

12

Anhang

271

12.1 12.1.1

Skripten, die bei der Arbeit verwendet wurden rmtab.pl: Ersetzen von Tabulator-Zeichen durch Leerzeichen

271 271

12.1.2

rmcode.pl: Ersetzen von CODE-Tags durch LaTeX-Tags

272

13

Literaturverzeichnis

275

Index

277

1

Vorwort

Als Professor Schröter die Idee zu diesem Buch-Projekt hatte, war ich sofort Feuer und Flamme. Viele Gedanken über das, was ich schreiben würde, gingen mir durch den Kopf. Langsam machte sich Ernüchterung breit. Jedes der Themen, die in diesem Buch behandelt werden sollten, würde spielend ein eigenes Buch dieses Umfanges füllen. Also, wie den Stoff begrenzen? Wie ein Buch schreiben, das nicht schon andere Autoren vor mir geschrieben haben? Diese Fragen haben mich eine ganze Weile verfolgt. Verschiedene Ansätze führten in die Irre und wurden wieder verworfen. Schließlich besann ich mich auf das, was ich bin: Ein Perl-Programmierer, der täglich viele Stunden damit verbringt, Anwendungen der verschiedensten Art in Perl zu schreiben. Warum sollte ich also nicht mein Buch schreiben? Ein Buch, in dem ich die Techniken und Vorgehensweisen zusammenfasse, die ich bei meiner Arbeit anwende. Sicherlich, dachte ich, kann man über jedes der hier angesprochenen Themen weit mehr sagen; aber, so stellte ich mir die Frage, was von all dem Stoff benötige ich für meine tägliche Arbeit tatsächlich? Die Idee begann Form zu gewinnen. Ich wollte ein Buch schreiben, das ich mir neben meinen Computer legen und in dem ich die Punkte nachlesen kann, die ich immer wieder vergesse. Es sollte daher auch Themen einschließen, die nicht unmittelbar zu Perl gehören, ohne die es allerdings nicht möglich ist, umfangreichere Programme zu erstellen. Zu diesen Themen gehört beispielsweise eine Einführung in das Design von Datenbanken und in die Grundlagen der Abfragesprache SQL genauso wie eine kurze Einführung in die Seitenbeschreibungssprache HTML oder die Einrichtung des Apache Webservers. Den breitesten Raum in diesem Buch nimmt natürlich die Programmiersprache Perl selbst ein. Wir zeigen Ihnen, wie Sie Ihre Anwendungen mit Packages modularisieren und mit Modulen eigene Funktionsbibliotheken anlegen können. Auch Techniken, von denen so mancher C-Programmierer nur träumen mag, wie das Ausführen von Code zur Kompilierzeit oder das Nachladen von Code zur Laufzeit, stellen wir Ihnen vor. Falls Sie in einem Projekt an die Grenzen der Möglichkeiten von Perl stoßen, was unserer Meinung nach fast gar nicht möglich ist, zeigen wir Ihnen, wie einfach es ist, Module in C zu erstellen und diese mit Perl-Code zu verbinden. Der Begriff der Objektorientierten Programmierung ist in aller Munde; wir stellen ausführlich dar, was sich hinter diesem Begriff verbirgt, und wie Sie diese Techniken in Ihren Projekten einsetzen können. Anwendungen, die nicht in der einen oder anderen Weise mit Datenbanken kommunizieren, sind heute fast nicht mehr denkbar. Daher verfügt auch Perl über eine hervorragende Datenbankschnittstelle, die wir Ihnen umfassend vorstellen.

1 Vorwort

2

Perl ist mit dem Internet groß geworden, daher können wir den Bereich der CGIProgrammierung nicht beiseite lassen. Wir erläutern, wie Sie Ihre Testumgebung einrichten, stellen die Grundlagen der Seitenbeschreibungssprache HTML dar und tauchen dann tief in die CGI-Programmierung ein. Wir erklären Ihnen, wie Sie Datenbanken aus Webseiten heraus ansprechen, wie Sie Ihren Code mit Hilfe von Templates modularisieren und vieles mehr. Vor allem möchten wir Ihnen aber auch zeigen, dass der Einsatz von Perl und CGI auch dann lohnt, wenn Sie keine Web-Anwendungen schreiben möchten. Das Perl/Tk-Toolkit zum Erstellen von grafischen Benutzerschnittstellen ist eine der mächtigsten Erweiterungen zu Perl. In unserem Buch stellen wir Ihnen dieses Toolkit in einem ausführlichen Querschnitt vor und zeigen auch einige Steuerelemente in Ihrer Anwendung, die in der Literatur mitunter vergessen werden, die aber in komplexen Programmen von größtem Nutzen sind. Mit den beiden Bänden dieses Buches erhalten Sie einen umfassenden Einblick in die Softwareentwicklung mit Perl, von den Grundlagen bis zur fertigen Anwendung. Dabei setzen wir im vorliegenden zweiten Band voraus, dass Sie die Grundlagen der PeriProgrammierung beherrschen, und auf Ihrem Rechner eine Perl-Installation vorhanden ist. Ich würde mir wünschen, dass der Funke der Begeisterung, den mir meine tägliche Arbeit mit Perl beschert, auch auf Sie überspringt und Sie feststellen, dass Perl tatsächlich eine der mächtigsten Programmiersprachen ist, die derzeit auf dem Markt zu finden sind und, dass sie längst dem Stadium einer Skriptsprache entwachsen ist. Perl kann sich heute durchaus mit System-Sprachen wie C, C + + und Java messen und lässt diese in einigen Bereichen recht "alt aussehen". Viel Spaß bei der Lektüre dieses Buches!

1.1

Ein Wort zu freier Software

Als ich vor nunmehr sechs Jahren damit begann, mich mit dem damals noch recht jungen Betriebssystem LINUX zu beschäftigen, warfen mir die meisten Menschen vor, ich sei verrückt. Doch mich faszinierte die Idee der freien Software, deren Quellcodes nicht in Panzerschränken versteckt sind, sondern bei der jedermann zum Programm gleich die Quellcodes mitbekommt. Und das zumeist umsonst 1 ! 1999 beschloss ich, die Hauptaktivität meiner Firma in Richtung LINUX zu verlegen. Bis heute habe ich diesen Entschluss nicht eine Sekunde bereut! Die Linuxer sind ein hilfreiches Völkchen, und im Laufe der Jahre bin ich mit vielen Menschen rund um den Globus in Kontakt gekommen, die mir (und ich hoffentlich auch Ihnen) bei so manchem Problem mit Tipps und teilweise seitenlangen "Howto"-Mails zur Seite standen. Dieses Buch ist ausschließlich mit freier Software entstanden! Hier ein kurzer unvollständiger Überblick über die Software, die ich bei der Erstellung dieses Buches eingesetzt habe: Es ist ein weit verbreiteter Irrtum, dass freie Software immer kostenlos sei! Der Begriff frei bezieht sich allerdings auf die Tatsache, dass jeder das Recht hat, den Quellcode dieser Software einzusehen und nach Belieben zu verändern. 1

1.1 Ein Wort zu freier Software

3

Betriebssysteme. Auf meinen Computern setze ich derzeit zwei verschiedene LINUXDistributionen ein: Im Firmenbereich, wo es vor allem auf Sicherheit ankommt, sowie auf den Servern setze ich Debian-LINUX ein, das kostenlos unter der Web-Adresse: h t t p : //www. debian. org bezogen werden kann. Dort finden Sie auch Adressen von Firmen, die CD-Sätze dieser Distribution vertreiben. Debian ist keine Anfänger-Distributior und die Installation ist nicht ganz einfach. Auf meinen Desktop-Systemen verwende ich derzeit RedHat-LINUX in der aktuellen Version 9.0. Auch diese Distribution kann über die Web-Seite des Herstellers kostenlos (in einer drei-CD-Version) heruntergeladen werden ( h t t p : //www.redhat .de). Daneben ist RedHat-LINUX im Handel in verschiedenen Paketierungen zu erwerben. Textverarbeitungsprogramme und Editoren. Der größte Teil dieses Buches wurde mit den Editoren (in der Reihenfolge der Beliebtheit) Nedit, Emacs21 und Vim erstellt. Alle Editoren verfügen über Syntaxhighlighting (also das farbige Hervorheben bestimmter Schlüsselworte) für Perl und LaTeX und sind Bestandteil der genannten LINUXDistributionen. Bei der Korrektur leistete das umfangreiche Office-Paket OpenOffice wertvolle Dienste. OpenOffice kann in der deutschen Version kostenlos unter der folgenden InternetAdresse bezogen werden: h t t p : / / d e . o p e n o f f i c e . o r g . Der Text wurde mit LaTeX gesetzt, einem leistungsfähigen, aber nicht ganz einfach zu bedienenden Textsatz-System. LaTeX ist Bestandteil aller LINUX-Distributionen. Webserver und Webbrowser. Als Webserver verwende ich Apache, der weiter unten beschrieben wird. Als Browser Mozilla, der ehemalige Netscape-Browser. Auch diese beiden Pakete finden Sie auf allen gängigen LINUX-Distributionen. Datenbank-Systeme. Als Datenbank-Systeme nutze ich PostgreSQL und MySQL, die ebenfalls in jeder LINUX-Distribution vorhanden sind. Grafik. Alle Grafiken und Bildschirmhardcopies haben ich mit dem Grafikprogramm The Gimp und dem Zeichenprogramm Xfig erstellt. Raten Sie mal, wo Sie diese Anwendungen finden. Wenn Sie nichts mit LINUX zu tun haben möchten, brauchen Sie nicht traurig zu sein! Fast alle genannten Anwendungen sind mittlerweile auch für Windows-Systeme erhältlich und auch dort kostenlos. Lediglich Xfig ist meines Wissens bisher nicht portiert worden. Die Installation von PostgreSQL unter Windows ist nicht ganz einfach, so dass Sie vielleicht besser auf MySQL ausweichen.

2

Dank

An einem solchen Buchprojekt haben viele Menschen mitgewirkt. Diese Mitwirkung kann man in zwei Kategorien einteilen: freiwillig und unfreiwillig. Zunächst möchte ich mich bei den freiwilligen Helfern bedanken: An erster Stelle gilt mein Dank dem Mitautor und Initiator dieses Buches, Herrn Professor Dr. Jürgen Schröter, der mir nicht nur stets mit Rat und Hilfe zur Seite stand, sondern der mir auch die Möglichkeit gab, mein Wissen an der Fachhochschule Landshut an den Mann und die Frau zu bringen. Ebenfalls in die Kategorie der freiwilligen Helfer gehören "unsere" Lektoren Frau Irmela Wedler und Herr Christian Kornherr vom Oldenbourg Verlag, die unser Projekt stets engagiert betreuten. Auch Ihnen gilt mein Dank. Bedanken möchte ich mich auch bei Herrn Peter Kornherr für den gelungenen Umschlagentwurf und dafür, dass er unsere Hündin Lilly (die allerdings in die andere Kategorie gehört) auf den Umschlag dieses Buches gezaubert hat. Folgende Personen haben Ihre Hilfe zu diesem Buch eher unfreiwillig beigesteuert: Mein größter Dank gehört meiner Frau Susanne, die am Entstehungsprozess dieses Buches in vielfältiger Weise beteiligt war: Sie hat mich und meine Launen ertragen und hat das Buch Seite für Seite gelesen, korrigiert und so manchen komplizierten Schachtelsatz in verständliches Deutsch übersetzt. Und das, obwohl sie Computer eigentlich gar nicht mag! Danke! Mein Dank gebührt auch meinem Partner und Mitgeschäftsführer der idomeo GmbH Herrn Franz Held, der einige wichtige Projekte zu Gunsten des Buches verschob. Und schließlich möchte ich meiner Tochter Emily danken, die auf viel Zeit mit mir verzichten musste.

Landshut 2003 Helmut Seidel

3

Perl-Pakete

Jeder, der schon einmal ein Perl-Skript von mehr als 1000 Zeilen erstellt hat, kennt die Probleme, die damit verbunden sind, ein solches Programm zu pflegen. Schon nach kurzer Zeit ist das Rad Ihrer Wheel-Maus heiß gelaufen, und Ihnen stehen die Tränen in den Augen, vom Hin- und Herscrollen in Ihrer Datei. Auch ein anderes Szenario kennt fast jeder Programmierer. Sie haben für eine bestimmte Aufgabenstellung einen komplizierten Algorithmus entwickelt und in Ihre Anwendung integriert. Nach geraumer Zeit erstellen Sie eine weitere Anwendung, die diesen Algorithmus erneut verwenden soll. Hierfür bietet sich mit Ihrem bisherigen Wissensstand folgende Lösung an: Zunächst suchen Sie die Anwendung, in der Sie den Algorithmus formuliert haben. Glücklich können sich diejenigen schätzen, die ein Programmiertagebuch führen, indem alle Tätigkeiten und Lösungen verzeichnet sind. Sie können nun die Stelle suchen, an der Ihr Algorithmus ausformuliert ist und ihn mit Hilfe der Copy & Paste Funktionalität Ihres Editors in Ihr aktuelles Projekt einfügen. Nun müssen Sie die Namen aller globalen Variablen des Code-Blockes an das aktuelle Projekt anpassen. Ich hoffe, die Vorstellung dieses Vorganges treibt nicht nur mir den Angstschweiß auf die Stirn. Es grenzt schon an ein Wunder, wenn der so entstandene Code ohne Probleme funktionierte. Aber stellen Sie sich mal vor, nach einer weiteren längeren Zeitspanne, sagen wir mal von einem Jahr, ändern sich die Anforderungen an Ihren Algorithmus, weil sich beispielsweise die gesetzlichen Vorschriften geändert haben (etwa in einem Warenwirtschaftssystem). Sie müssen also den Code beider Anwendungen entsprechend anpassen. Und wieder schaffen Sie sich eine unüberschaubare Anzahl von möglichen Fehlerquellen, wieder endloses Testen von zwei Anwendungen ... Und was, wenn der Algorithmus so allgemein ist, dass Sie ihn nicht nur in zwei, sondern vielleicht in fünf oder gar zehn Skripten untergebracht haben? Wir brechen dies Horror-Szenarium an dieser Stelle ab ... Was, so mögen Sie sich vielleicht fragen, haben die beiden vorgestellten Szenarien gemeinsam? Die Antwort ist: in beiden Fällen können Sie sich das Leben erheblich vereinfachen, wenn Sie Ihre Anwendung modularisieren. Modularisieren bedeutet, dass eine Anwendung in überschaubare und inhaltlich zusammengehörige Einheiten zerlegt und in einzelnen Dateien hinterlegt wird. In den meisten Programmiersprachen werden diese Einheiten Module oder Bibliotheken genannt. Das Modularisieren von Anwendungen bietet Ihnen zwei nicht zu unterschätzende Vorteile: 1. Da Ihre Anwendung nun aus kleinen überschaubaren Einheiten besteht, ist sie leicht und bequem zu warten. Natürlich sollten Sie darauf achten, keine "SuperModule" mit vielen tausend Code-Zeilen zu schreiben. Vielmehr sollten Ihre Mo-

8

3 Perl-Pakete dule eine überschaubare Größe besitzen. Als Faustregel verwendet der Autor eine Referenzgröße von ungefähr 100 Zeilen als Maximum für ein Modul 1 . 2. Code, welchen Sie in Modulen abgelegt haben, können Sie auf besonders einfache Art und Weise wiederverwenden, wenn Sie einige grundlegende Regeln beachten. Wir werden in diesem und dem folgenden Kapitel genau darstellen, wie Sie Ihre eigenen Funktions-Bibliotheken anlegen, bzw. wie Sie die Bibliotheken (Module) anderer Entwickler in Ihre Anwendungen einbinden.

In diesem Kapitel werden wir uns mit den Packages in Perl beschäftigen. Das Verständnis dieses Themas ist grundlegend für die Perl-Konzepte der Module sowie der Objektorientierten Programmierung. Sie sollten daher das vorliegende Kapitel in jedem Fall durcharbeiten. Im Einzelnen werden wir folgende Themen behandeln: • Zunächst möchten wir Ihnen die Frage beantworten, was Pakete sind und den wichtigen Begriff des Namensraumes einführen. • Anschließend beantworten wir die Frage, wie Pakete deklariert werden und vor allem, was Sie dabei beachten sollten. • Wie Sie auf Variablen, Subroutinen etc. aus anderen Paketen zugreifen, wird das Thema des folgenden Abschnitts sein. • Wir werden dann erläutern, wie Sie Pakete in Ihre Anwendungen einbinden. • Besonders bei größeren Projekten kann es notwendig sein, Pakete bzw. Ihre Anwendung zu initialisieren. Hierunter verstehen wir, Code auszuführen, wenn die Anwendung oder das Paket kompiliert wird. Ebenso kann es notwendig sein, Code auszuführen, nachdem die Arbeit mit der Anwendung abgeschlossen wurde. Beides wird von Perl in hervorragender Weise unterstützt. Wie dies geschieht, wird das Thema des anschließenden Absatzes sein. • Für einen schnellen und bequemen Zugriff auf Variablen und Routinen eines anderen Paketes kann es nützlich sein, die Symbole des betreffenden Paketes in den aktuellen Namensraum zu importieren. Auch dieses möchten wir weiter unten erläutern. • Den Abschluss dieses Kapitels bildet eine Erklärung des ausgesprochen mächtigen Mechanismus des Autoloadings, mit dem es beispielsweise möglich ist, Code zur Laufzeit der Anwendung nachzuladen. 'Wenn Sie in einem Modul eine grafische Benutzerschnittstelle implementieren, werden Sie den Wert von 100 Zeilen sicher um ein Vielfaches überschreiten. Sie sollten sich daher angewöhnen, den Code für solche Benutzerschnittstellen vom restlichen Programmcode zu trennen und stets in einem eigenen Modul zu definieren.

3.1 Was sind Perl-Pakete?

3.1

9

Was sind Perl-Pakete?

Bisher haben Sie gelernt, dass Variablen, die ohne die Modifier my oder l o c a l deklariert wurden, globale Variablen und damit in der gesamten Anwendung sichtbar sind. Genau genommen war das nur die halbe Wahrheit. Tatsächlich gibt es in Perl solche globalen Variablen überhaupt nicht. Vielmehr beschränkt sich in Perl die Sichtbarkeit einer Variablen auf das umschließende Package. Ein Package ist also nichts anderes als ein eigener Namensraum, der die Sichtbarkeit von Variablen, Arrays, Hashes, Subroutinen, Datei- und Verzeichnishandies2 auf das umschließende Package einschränkt. Wird Programmcode nicht explizit einem Paket zugeordnet, übernimmt dies Perl für uns und ordnet den entsprechenden Code dem Package main zu. Folglich existiert in Perl-Anwendungen kein Code, der nicht einem Paket zugeordnet ist. Dies bedeutet, dass eigentlich auch keine wirklich globalen Variablen sondern lediglich modulglobale Variablen existieren. Alle Bezeichner eines Namensraumes werden in einer eigenen Symboltabelle abgelegt. Eine Symboltabelle wird dabei von einer Hash-Struktur gebildet, auf die, wie wir weiter unten sehen werden, mit den üblichen Hash-Methoden zugegriffen werden kann. Der Gültigkeitsbereich eines Paketes erstreckt sich dabei stets bis zur nächsten PaketDeklaration oder, falls keine folgt, auf den umschließenden Block, in dem das aktuelle Paket deklariert wurde. Falls Sie gar keine Pakete explizit deklariert haben oder die Paket-Deklaration unmittelbar an den Anfang einer Quellcode-Datei gestellt haben, ist dies die gesamte Datei. Was ist aber an diesem Umstand so bemerkenswert? Die Antwort auf diese Frage ist eigentlich ganz einfach: Nur so ist es überhaupt möglich, Anwendungen zu modularisieren und Code zu Bibliotheken zusammenzufassen. Wäre es anders, müssten Sie bei jeder neuen Variablen, die Sie verwenden, prüfen, ob Sie nicht schon irgendwo in Ihrem Anwendungs-Dschungel eine Variable dieses Namens verwendet haben. Stellen Sie sich nur einmal vor, Sie haben eine Variable $x als Zählvariable einer f or-Schleife verwendet, nun rufen Sie innerhalb des Schleifenkörpers eine Funktion aus einem Modul auf, die ebenfalls eine Variable dieses Namens verwendet und dieser einen festen Wert zuweist. Ihre f or-Schleife würde mit Sicherheit nicht so reagieren, wie Sie es erwarteten. Doch zum Glück war dies nur der Ausschnitt aus einem Albtraum 3 . Da in Perl allerdings der Gültigkeitsbereich von Bezeichnern stets eingeschränkt ist, können Sie problemlos Module und Bibliotheken erstellen, ohne Angst haben zu müssen, mehrmals den gleichen Variablennamen verwendet zu haben. Erst durch dieses Konzept wird auch die Programmierung in Teams möglich.

2

Wir werden im weiteren Verlauf zusammenfassend von Bezeichnern sprechen. I n meinen Seminaren ist es übrigens eine beliebte Fehlerquelle, in einer Anwendung mehrmals dieselben Variablennamen (einmal im Hauptprogramm, einmal in einer Unterroutine etc.) zu verwenden. "Er nimmt mir d a was nicht ..." ist in diesem Falle der Anfang einer gängigen Frage! Prinzipiell sollten Sie, um Ihrer Gesundheit willen, stets das P r a g m a strict verwenden und auf (modul-) globale Variablen ganz verzichten! 3

10

3.2

3 Perl-Pakete

Deklaration von Paketen

Um ein Paket zu erstellen, verwenden Sie die package-Anweisung von Perl, welche die folgende allgemeine Syntax besitzt. package Paketname;

Also beispielsweise: package Kontoverwaltung;

In der Regel sollten Sie in einer Datei jeweils nur eine einzige package-Anweisung als erste Anweisung der Datei 4 aufnehmen. Damit weisen Sie den gesamten Code einer Datei diesem Paket zu. Eine solche Datei sollte zudem stets die Dateinamenserweiterung .pm besitzen. Die Endung .pm steht übrigens für Perl Module. Nur Dateien, die diese Endung besitzen, können, wie wir im nächsten Abschnitt sehen werden, mit Hilfe der Anweisung use in eine Quellcode-Datei eingebunden werden. Gemäß der Philosophie von Perl: "Der Programmierer wird schon wissen, was er tut" können innerhalb einer Datei auch mehrere Pakete deklariert werden. Zudem kann beliebig zwischen diesen Paketen gewechselt werden. Beachten Sie hierzu das folgende Beispiel.

package GiroKonto; $inhaber = "Micky Maus"; $kontostand = 5000;

# Package GiroKonto deklarieren # Einige Package-Variablen einlegen

package SparKonto; $inhaber = "Daisy Duck"; $kontostand = 3500;

# Ein weiteres Package deklarieren # auch hier ein paar Variablen

# Zurueck zum Girokonto package GiroKonto; # Kontodaten GiroKonto ausgeben print "$inhaber hat $kontostand Euro auf seinem Konto.\n"; # Zurueck zum Sparkonto package SparKonto; \# Kontodaten SparKonto ausgeben print "$inhaber hat $kontostand Euro auf seinem Konto.\n";

Es ist also tatsächlich möglich, in einer Quellcode-Datei mehrfach Variablen gleichen Namens zu deklarieren, sofern Sie in unterschiedlichen Packages angelegt wurden. Dies funktioniert, da die Variablen jeweils in der Symboltabelle des aktuellen Packages abgelegt wurden. Allerdings sei an dieser Stelle nochmals darauf hingewiesen, dass es sich bei dem oben dargestellten Beispiel um einen ausgesprochen s c h l e c h t e n Stil handelt. 4 Sofern das von Ihnen angelegte Paket allerdings der Startpunkt einer größeren Anwendung ist, also das Paket main ersetzt, sollte die package-Anweisung erst nach der SheBang-Zeile stehen.

3.3 Einbinden von Paketen mit use und r e q u i r e

11

Genauso wie es möglich ist, mehrere Pakete in einer Datei zu deklarieren, ist es auch möglich, ein Paket über mehrere Dateien zu verteilen, indem mehrfach die gleiche package-Deklaration verwendet wird. Aber auch dies ist ein schlechter Stil und sollte in jedem Falle vermieden werden.

3.3

Einbinden von Paketen mit use und require

Nachdem Sie nun ein Paket mit wichtigen Routinen für Ihre Anwendung geschrieben haben, möchten Sie dieses natürlich auch nutzen. Um dies tun zu können, müssen Sie das betreffende Paket zunächst in Ihre Anwendung einbinden. Einbinden bedeutet, dass der Code eines Paketes 5 in die aktuelle Quellcode-Datei eingelesen wird 6 . Dieser Einlesevorgang kann entweder über die Anweisung use oder mit Hilfe der Anweisung require geschehen.

3.3.1

Die Anweisung use

Der sicherste Weg, ein Paket in Ihre Anwendung einzubinden, ist das Verwenden der Anweisung use. Die allgemeine Syntax der use-Anweisung ist use Paketname;

Um das Paket Kontoverwaltung.pm in Ihre Anwendung einzubinden, verwenden Sie die Anweisung use KontoVerwaltung;

Mit Hilfe von use können allerdings nur Pakete eingebunden werden, die in Dateien gespeichert sind, deren Dateinamenserweiterung .pm ist. Damit die Datei korrekt geladen werden kann, ist es zudem notwendig, dass beim Laden ein boolscher Erfolgswert zurückgegeben wird. Die letzte ausführbare Anweisung im globalen Gültigkeitsbereich des Paketes sollte daher wie folgt lauten. return 1;

Weniger gut lesbar, aber auch möglich ist folgende Variante. 1; Damit kann der allgemeine Aufbau einer Paket-Datei wie folgt dargestellt werden. # Deklaration des Paketes package Paketname; # evtl. Einbinden von weiteren Modulen/Paketen use Paketname; 5 Wir sollten eigentlich von einem Modul sprechen. Da wir aber diesen Begriff erst im nächsten Kapitel erläutern, sprechen wir hier noch etwas unhandlich von einem Paket. 6 C / C + + - P r o g r a m m i e r e r n wird dies bekannt vorkommen. Der Vorgang erinnert an das Einbinden von (header)-Dateien mit Hilfe der Präprozessor-Anweisung #include.

12

3 Perl-Pakete

# Deklaration Modulglobaler Variablen $var = wert; # Einen wahren Rueckgabewert erzeugen return 1; # Deklaration der Unterroutinen (Methoden) des Paketes sub unterroutinel { }

sub u n t e r r o u t i n e 2 { >

Der use-Befehl wird bereits zum Zeitpunkt des Kompilierens7 der Quellcode-Datei ausgeführt. Kann Perl also das gewünschte Paket nicht korrekt einbinden, wird die Anwendung gar nicht erst gestartet. So können Sie sicher sein, dass Sie nicht zur Laufzeit Ihrer Anwendung eine unangenehme Überraschung erleben, weil ein einzubindendes Paket nicht gefunden wurde.

3.3.2

Die Anweisung require

Wie oben beschrieben, können mit Hilfe des use-Befehls nur Dateien eingebunden werden, welche die Dateinamenserweiterung . pm besitzen; zudem ist es nicht möglich, Pakete dynamisch zur Laufzeit zu laden. Beides wird mit Hilfe der Anweisung r e q u i r e möglich. Um beispielsweise eine Datei mit Namen Kontoverwaltung.pl in Ihre Anwendung einzubinden, verwenden Sie die folgende Anweisung. require KontoVerwaltung;

Sofern Sie keine Dateinamenserweiterung angeben, nimmt r e q u i r e als Endung .pm an. Auch für die Anwendung von r e q u i r e ist es notwendig, dass die zu ladende Datei einen wahren boolschen Wert zurückgibt. Die Anweisung r e q u i r e bietet zudem eine zweite Syntax-Variante an. r e q u i r e "Kontoverwaltung.pm"; Diese Variante unterscheidet sich von der ersten in einigen Punkten. So ist es nun möglich, Dateien einzubinden, deren Datei-Endung nicht .pm lautet, also beispielsweise Kontoverwaltung.pl. Ein weiterer wichtiger Unterschied ist, dass diese Variante das einzubindende Paket nur im aktuellen Verzeichnis findet. Liegt das Paket an einem 7 Genau genommen ist der Zeitpunkt des Einbindens bereits die Parsing-Phase, eigentlichen Kompiliervorgang durchlaufen wird.

die noch vor dem

3.3 Einbinden von Paketen mit use und r e q u i r e

13

anderen Ort, so muss der Pfad explizit, wie im folgenden Beispiel gezeigt, angegeben werden. require

"/home/micky/kontoPackages/Kontoverwaltung.pm";

Die Anweisung require wird erst zur Laufzeit der Anwendung ausgeführt. Es ist also möglich, dynamisch Code in Ihre Applikation zu laden. Dies ist vor allem dann sinnvoll, wenn bestimmte Programmfunktionen nur sehr selten aufgerufen werden, wie beispielsweise eine Setup-Routine oder Ähnliches. Beim Aufruf prüft require zunächst, ob das gewünschte Paket bereits geladen wurde. Ist dies der Fall, wird der Aufruf ignoriert. Ist dies nicht der Fall, versucht die Anwendung das angegebene Paket zu laden. Der Entwickler selbst ist dafür verantwortlich, sicherzustellen, dass das angeforderte Paket auch existiert. Ist dies nicht so, kommt es zu einem Programmabsturz, das heißt, Perl beendet mit einem Fehler die Ausführung Ihrer Anwendung. Wenn es also nicht notwendig ist, Pakete dynamisch zu laden oder Pakete einzubinden, deren Dateinamenserweiterung nicht auf .pm endet 8 , sollten Sie in jedem Fall die useAnweisung verwenden.

3.3.3

Wo Perl nach Paketen sucht

Nicht immer möchten Sie die einzubindenden Dateien im aktuellen Programmverzeichnis ablegen. Schon nach kurzer Zeit hätten Sie bei häufig verwendeten Paketen ähnliche Probleme wie bei der (nun obsoleten) Verwendung der Copy & Paste-Methode. Es stellt sich also die Frage, wo Perl Pakete sucht, die Sie mit use oder require einbinden möchten. Falls der Perl-Interpreter die angegebenen Dateien nicht im aktuellen Anwendungsverzeichnis findet, sucht Perl in allen im Array ®INC eingetragenen Verzeichnissen nach dem geforderten Paket. Sofern Sie ein eigenes Bibliotheksverzeichnis anlegen möchten, müssen Sie dieses Array um Ihre Pfadangaben erweitern. Hierzu gibt es im Wesentlichen drei Möglichkeiten: 1. Der einfachste Weg ist der, den Kommandozeilenschalter - I 9 zu verwenden. perl -I/home/micky/perlPackages -1/h.ome/micky/kontoPackages meinProgreumn.pl

Wie gezeigt, geben Sie also beim Aufruf Ihrer Anwendung vor dem Namen des auszuführenden Programmes die Pfade zu Ihren Paket-Bibliotheken an. Rufen Sie die Anwendung des Öfteren auf, ist es sinnvoll, ein kurzes Shellskript oder unter Windows eine Batch-Datei zu schreiben, die den Aufruf für Sie erledigt. 8 9

Und von Ihnen auch nicht umbenannt werden kann. Für include = engl, einschließen

14

3 Perl-Pakete 2. Wenn Sie sehr viele Bibliotheken verwenden, wird es mit der Zeit müßig, bei jedem Aufruf erneut den Rattenschwanz von Include-Pfaden anzugeben. Sofern Sie stets die gleichen Bibliotheks-Pfade verwenden, können Sie diese auch dauerhaft in der Umgebungsvariablen PERL5LIB hinterlegen. Diese Variable enthält eine durch Doppelpunkte getrennte Pfadliste. Dabei ist zu beachten, dass Unterverzeichnisse zu den angegebenen Pfaden automatisch mit eingeschlossen werden. Um diese Variable auf einem UNIX/LINUX-System zu verändern, tragen Sie die Ergänzungen in eine der Dateien .profile oder . bashrc des jeweiligen Users oder in die systemglobale Datei profile im Verzeichnis /etc in der folgenden Art ein. PERL5LIB="/home/micky/kontoPackages: /home/micky/perlPackages:$PERL5LIB" export PERL5LIB

Nutzer von Windows-Systemen müssen die entsprechenden Eintragungen in der Datei autoexec.bat vornehmen, die im Rootverzeichnis des Laufwerkes C:\zu finden sind. Bevor Sie die Umgebungsvariable PERL5LIB dauerhaft verändern, sollten Sie aber wirklich sicher sein, was Sie tun! Im Zweifelsfalle werfen Sie einen Blick in das Handbuch Ihres Betriebssystems. 3.

Schließlich können Sie auch das Array @INC ergänzen, bevor Sie require oder use aufrufen. # OINC Einpassen unshift(@INC, "/home/micky/perlPackages"); unshift(@INC, "/home/micky/kontoPackages"); # Pakete einbinden require "Kontoverwaltung.pm"; require "InputTools.pm";

Um use zu verwenden, müssen Sie das Array, wie im Abschnitt "Initialisieren und Zerstören von Paketen", verändern, bevor die Anwendung kompiliert wird.

3.4

Zugriff auf Bezeichner anderer Pakete

Sie sind nun in der Lage, Packages zu erstellen und in Ihre Anwendung einzubinden. Was allerdings noch fehlt, um mit Paketen sinnvoll arbeiten zu können, ist eine Möglichkeit, auf die Bezeichner anderer Pakete Zugriff zu erlangen. Das Geheimnis, um dies zu erreichen, ist der sogenannte voll qualifizierte Name, der aus Typenzeichen und dem Namen des Paketes, gefolgt von einem zweifachen Doppelpunkt sowie dem Namen des Bezeichners besteht. Also in allgemeiner Form:

3.4 Zugriff auf Bezeichner anderer Pakete

15

[Typenzeichen]Paketname::bezeichner

Also beispielsweise zum Zugriff auf die Variable $kontostand aus dem Paket Konto. $Konto::kontostand;

Analog geschieht auch der Zugriff auf Array- oder Hash-Variablen. OKonto::buchungsdaten[0] ; '/,Konto:: kontoInhaberNamen{"Maus"};

Beachten Sie hierbei, dass das Typenzeichen stets vor dem Paketnamen steht und nicht vor dem Variablen-Bezeichner! Um eine Subroutine in einem anderen Paket aufzurufen, verwenden Sie die folgende Syntax. Paketname::subroutine(argument, argument[,...])

Also beispielsweise. Konto::einzahlen($aBetrag);

Lassen Sie uns dieses Vorgehen anhand eines Beispieles illustrieren. Wir gehen davon aus, dass Sie ein Paket Konto erstellt haben, in dem die Variablen kontoinhaber und kontostand sowie die beiden Subroutinen einzahlen und auszahlen deklariert sind. Beide Subroutinen erwarten als Argument einen Betrag. Zunächst erstellen wir das Paket Konto wie folgt. package Konto; # Datei: Konto.pm # Globale Variablen deklarieren $kontoinhaber = ""; $kontostand = 0.0; # ein 'wahrer' Wert muss zurueckgegeben werden return 1; sub einzahlen^ # Einzahlungsbetrag uebernehmen my $betrag = $_ [0]; # Wenn Einzahlung groesser als 0, buchen if ($betrag > 0.0) { $kontostand += $betrag; } eise {

16

3 Perl-Pakete print "Sie muessen einen Buchungsbetrag angeben!\n";

>

>

sub auszahlend # Auszahlungsbetrag uebernehmen my $betrag = $_[0]; # Wenn Auszahlung moeglich, Betrag abbuchen if ($betrag < $kontostand) { $kontostand -= $betrag; )• eise { print "Sie haben nicht genug Geld auf Ihrem Konto\n"; print "Auszahlung nicht moeglich!\n";

>

>

Nun schreiben wir eine kurze Anwendung, mit der wir einige Variablenwerte setzen und die Subroutinen aufrufen. #!/usr/bin/perl -w # Paket einbinden use Konto; # Kontoinhaber setzen print "Geben Sie den Namen des Kontoinhabers ein: "; my $name = ; chomp($name); $Konto::kontoinhaber = $name; # Eine Einzahlung auf das Konto vornehmen print "Geben Sie einen Einzahlungsbetrag ein: "; my $eBetrag = ; chomp($eBetrag); Konto::einzahlen($eBetrag); # Eine Auszahlung vornehmen print "Geben Sie den Auszahlungsbetrag ein: "; my $aBetrag = ; chomp($aBetrag); Konto::auszahlen($aBetrag); # Daten ausgeben print "\n\n$Konto:Kontoinhaber hat $Konto::kontostand " . "Euro auf seinem Konto\n";

17

3.5 Pakete initialisieren und terminieren

Wenn Sie die Skripte abtippen und starten, wird die in Abbildung 3.1 dargestellte Ausgabe erzeugt. Sie sollten dabei aber unbedingt beachten, dass beide Skripten im selben Verzeichnis stehen, sofern Sie nicht die Umgebungsvariable OINC verändern möchten. V

-

B *

ßatei gearbeiten Ansicht Terminal gehe zu {Jlfe [helmut@nb-seidel helmut]$ cd perl_buch/packages/examples/ [helmut # Kontrollausgabe - in der fertigen Anwendung loeschen print "\pfad im Hauptprogramm: $pfad\n;

Wie Sie sehen, können in einer BEGIN-Routine nicht nur Perls Spezialvariablen verändert werden, sondern auch "ganz normale" Variablen. Interessant ist dabei, dass Perl sich

20

3 Perl-Pakete

die in der Initialisierungs-Routine gesetzten Variablenwerte auch im Hauptprogramm merkt! Gerade bei großen Anwendungen, die möglicherweise noch an mehrere Kunden mit unterschiedlichen Betriebssystemen ausgeliefert werden, ist dieses Verhalten Gold wert. Bei komplexen Anwendungen kann es auch notwendig sein, nach Beenden der Applikation Code auszuführen, um beispielsweise Programmzustände auf der Festplatte zu sichern. Beim "planmäßigen" Ende der Applikation ist dies auch kein Problem: Sie schreiben einfach eine Unterroutine, die aufgerufen wird, bevor die Anwendung ihre Arbeit beendet. Doch leider leben wir nicht in dieser heilen Welt, wo Anwendungen stets zur rechten Zeit unter idealen Umständen enden. Vielmehr kommt es immer wieder zum abrupten Abbruch von Programmen, weil bestimmte Voraussetzungen für die Applikation plötzlich nicht mehr erfüllt sind. 11 In diesem Falle kann die Routine zum Beenden der Anwendung natürlich nicht mehr aufgerufen werden. Für derartige Fälle bietet Perl den END-Block an. Die Anweisungen innerhalb dieses Blockes werden immer ausgeführt, bevor die Anwendung die Kontrolle an das Betriebssystem zurück reicht, auch dann, wenn die Applikation mit einem Fehler beendet wurde. Betrachten Sie hierzu das folgende Beispiel. # E i n unsinniger Befehl, u m die Anwendungsausfuehrung zu b e e n d e n dies ist ein vollkommen unsinniger Befehl; sub END { # Die folgenden Anweisungen werden n o c h ausgefuehrt, # obwohl die Anwendung mit einem Fehler beendet w i r d print "\nDie Anwendung hat einen Fehler v e r u r s a c h t e n " ; print "Die Anwendung w i r d beendet!\n";

> Wie Sie sehen, werden die Anweisungen innerhalb des END-Blockes noch ausgeführt, obwohl die Anwendung einen Fehler erzeugt hat. Bedenken Sie aber, bevor Sie Ihre Anwendungen nun mit END-Blöcken spicken, dass dies stets nur der letzte Ausweg sein sollte und Sie nicht davon befreit, sinnvolle Fehlerabfang-Routinen zu schreiben!

3.6

Symbole aus anderen Symboltabellen importieren

Mitunter kann es recht lästig werden, immer dann, wenn man ein neues Konto anlegen möchte, den folgenden Rattenschwanz eintippen zu müssen. Kontoverwaltung::neu('Maus');

Viel angenehmer wäre es, könnte man einfach 11

Also weniger poetisch ausgedrückt: weil ein Fehler aufgetreten ist!

3.6 Symbole aus anderen Symboltabellen importieren

Abb. 3.3: Die Ausgabe des Beispielskriptes

21

end.pl

neu('Maus');

verwenden. Und genau dies kann erreicht werden, indem man zum use-Befehl in einer optionalen Liste angibt, welche Symbole in den aktuellen Namensraum importiert werden sollen. use Kontoverwaltung('neu', 'suchen', 'loeschen'); neu('Maus); # statt Kontoverwaltung::neu('Maus');

Damit man allerdings bei use eine Liste der zu importierenden Namen angeben kann, muss das Paket, das diese Liste liefert, in die Lage versetzt werden, die Namen zu exportieren. Zusätzlich muss das Paket wissen,welche Symbole zu exportieren sind, wenn beim use-Befehl keine Liste angegeben wurde. Um diese Aufgabe zu erledigen, stellt Perl das Standard-Modul Exporter zur Verfügung. Mit Hilfe dieses Moduls kann das Paket Kontoverwaltung auf folgende Weise realisiert werden. package Kontoverwaltung; use Exporter; # Modul Exporter einbinden ©ISA = ('Exporter'); # Von Exporter erben # Siehe hierzu Abschnitt zur Objektorientierten Programmierung # Liste der Symbole, die exportiert werden koennen

22

3 Perl-Pakete

@Export_Ok = ('neu', 'suchen', 'loeschen'); # Implementierung der Funktionen sub neu { >

sub suchen { >

sub loeschen { >

Das Array $name, "vorname" => $vorname, "gebdatum" => $gebdatum, "anschrift" => $anschrift, "beruf" => $beruf, "einkommen" => Seinkommen, "bargeld" => $bargeld,

52

6 Objektorientierte Programmierung

>;

>

# Instanz auf den Namen der Klasse taufen bless $r_kontoinhaber, $klasse; # Instanz zurueckgeben return $r_kontoinhaber;

# einen wahren Wert zurueckgeben return 1;

Nachdem Sie die Klassendatei angelegt und abgespeichert haben, können wir ein neues Objekt unserer Klasse anlegen. Wir verwenden zum Aufruf von Methoden einer Klasse den Pfeiloperator ->: #!/usr/bin/perl -w # Datei: klassel.pl # Einbinden der Klasse Kontoinhaber use Kontoinhaber; # neue Kontoinhaber-Instanz erzeugen und mit Werten vorbelegen Seigenschaften = ('Duck', 'Daisy', '12.03.1902', 'Erpelweg 4, 12345 Entenhausen', 'Klatschreporterin', '1234.56', '55.66'); my $ki = Kontoinhaber->new(@eigenschaften);

6.2.2

Anlegen und Aufrufen von Eigenschafts-Methoden

Wie weiter oben im Abschnitt Kapselung von Daten besprochen, sollte der Zugriff auf die Eigenschaften eines Objektes nicht direkt, sondern über speziell hierfür vorgesehene Methoden erfolgen. Methoden werden, wie wir gesehen haben, mit Hilfe von Funktionen realisiert. Wir verwenden dabei zum Lesen und Setzen dieselbe Methode, der wir den gleichen Namen geben, wie der entsprechenden Eigenschaft. Dabei testen wir zunächst, ob ein Parameter übergeben wurde. Ist dies der Fall, wird die Eigenschaft gesetzt. Anschließend gibt die aufgerufene Methode den Wert der in Frage stehenden Eigenschaft an den Aufrufer zurück. Damit erhält unsere Klasse Kontoinhaber folgendes Aussehen. package Kontoinhaber; # Konstruktor erstellen sub new {

6.2 Objektorientierte Programmierung mit Perl # Klassennamen uebernehmen my $klasse = shift; # Argumente uebernehmen my ($name, $vorname, $gebdatum, $anschrift, $beruf, $einkommen, $bargeld) = # Anlegen eines anonymen Hashes zur Aufnahme der # Eigenschaftswerte und uebernehmen der Werte my $r_kontoinhaber = { "name" => $name, "vorname" => $vorname, "gebdatum" => $gebdatum, "anschrift" => $anschrift, "beruf" => $beruf, "einkommen" => $einkommen, "bargeld" => $bargeld,

>; # Instanz auf den Namen der Klasse taufen bless $r_kontoinhaber, $klasse; # Instanz zurueckgeben return $r_kontoinhaber;

} sub name { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'name'} = shift if # Eigenschaftswert zurueck geben return $r_kontoinhaber->{'name'};

sub vorname { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'vorname'} = shift if # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{' vorname' }•;

sub gebdatum { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'gebdatum'} = shift if Q_; # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'gebdatum'};

53

54

6 Objektorientierte Programmierung

sub anschrift { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert übergeben wurde, speichern $r_kontoinhaber->{'anschrift'} = shift if # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'anschrift'};

>

sub beruf { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'beruf'} = shift if # Eigenschaftswert zurueckgeben return $r_kontoinhaber->-['beruf'};

>

sub einkommen { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'einkommen'} = shift if @_; # Eigenschaftswert zurueck geben return $r_kontoinhaber->{'einkommen'};

} sub bargeld { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'bargeld'} = shift if @_; # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'bargeld'};

} # einen wahren Wert zurueckgeben return 1;

Beachten Sie, dass wir im oben stehenden Skript der Übersichtlichkeit halber auf alle Plausibilitätskontrollen verzichtet haben. Im wirklichen Programmieralltag sollten Sie hierauf allerdings auf keinen Fall verzichten. So sollten Sie beispielsweise ein unsinniges Geburtsdatum mit einer Meldung an den Aufrufer quittieren und den falschen Wert zurückweisen. Wir stellen Ihnen im folgenden Beispiel die Methode zum Abfragen und Setzen der Eigenschaft gebdatum vor, die das übergebene Datum darauf überprüft, ob

6.2 Objektorientierte Programmierung mit Perl

55

die Jahreszahl vierstellig ist. Ist dies nicht der Fall, wird eine Fehlermeldung ausgegeben, und das ursprüngliche D a t u m wird zurückgegeben.

sub gebdatum { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern if (®_) { my $dat = shift; # Datum zerlegen my ($tag, $mon, $jahr) = split(/\./, $dat); # Testen ob Jahreszahl 4-stellig if (length($jahr) < 4) { # Meldung ausgeben print "2-stellige Jahreszahlen ($jahr) nicht erlaubt!\n"; print "weiter "; # Return-Taste abwarten ; # weitere Plausibilitaetskontrollen } eise { $r_kontoinhaber->{'gebdatum'} = $dat

>

>

# Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'gebdatum'};

Sie können nun auf die Eigenschaften zugreifen, diese auslesen und beliebig verändern. Das folgende Skript erstellt eine Instanz der Klasse Kontoinhaber und greift anschließend sowohl lesend als auch schreibend auf einzelne Eigenschaften der Instanz zu. Hierfür verwenden wir wiederum die Pfeilnotation.

#!/usr/bin/perl -w # Datei: klassel.pl # Einbinden der Klasse Kontoinhaber use Kontoinhaber; # neue Kontoinhaber-Instanz erzeugen und mit Werten vorbelegen Oeigenschaften = ('Duck', 'Daisy', '12.03.1902', 'Erpelweg 4, 12345 Entenhausen', 'Klatschreporterin',

56

6 Objektorientierte Programmierung

'1234.56', '55.66'); my $ki = Kontoinhaber->new(@eigenschaften); # Eigenschaftswerte ausgeben print $ki->name .", " . $ki->vorname . ", " . $ki->gebdatum . "\n"; # Eigenschaftswerte aendern $ki->gebdatum('13.01.1964'); # Eigenschaftswerte ausgeben print $ki->name .", " . $ki->vorname . ", " . $ki->gebdatum . "\n";

Natürlich können Sie auch, wie dies beispielsweise in VisualBasic üblich ist, zwei Funktionen für den Zugriff auf die Eigenschaften schreiben. Eine zum Lesen der Eigenschaft, getEigenschaft, und eine zweite, setEigenschaf t, zum Setzen der Eigenschaft. Es ist eine Frage der persönlichen Vorliebe. Für den Zugriff auf die Eigenschaft name müssen Sie in diesem Falle folgende beide Methoden erstellen.

# Eigenschaft zurueckgeben sub getName { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Eigenschaftswert zurueck geben return $r_kontoinhaber->{'name'};

} # Eigenschaft setzen sub setName { # Referenz auf Objekt uebernehmen my $r_kontoinhaber = shift; # Falls ein Wert ubergeben wurde, speichern $r_kontoinhaber->{'name'} = shift if 0_; # Eigenschaftswert zurueckgeben return $r_kontoinhaber->{'name'};

>

Mit den restlichen Eigenschaften verfahren Sie analog. Wie gesagt, es ist eine Frage der persönlichen Vorliebe, welche der vorgestellten Varianten Sie bevorzugen. Allerdings sollten Sie, wenn Sie sich für eine Variante entschieden haben, diese auch konsequent beibehalten! Beim Zugriff auf die Eigenschaften Ihrer Instanz müssen Sie den lesenden und den schreibenden Zugriff unterscheiden.

6.2 Objektorientierte Programmierung mit Perl

57

#!/usr/bin/perl -w # Datei: klassel.pl # Einbinden der Klasse Kontoinhaber use Kontoinhaberl; # neue Kontoinhaber-Instanz erzeugen und mit Werten vorbelegen Qeigenschaften = ('Duck', 'Daisy', '12.03.1902', 'Erpelweg 4, 12345 Entenhausen', 'Klatschreporterin', '1234.56', '55.66'); my $ki = Kontoinhaberl->new(@eigenschaften); # Eigenschaftswerte ausgeben print $ki->getName . "\n";

# Ausgabe: Duck

# Eigenschaftswerte aendern $ki->setName('Klaustrophobia'); # Eigenschaftswerte ausgeben print $ki->getName . "\n";

# Ausgabe: Klaustrophobia

Im Gegensatz zu anderen Programmiersprachen ist Perl nicht dem "AbsicherungsWahn" verfallen. In Perl bedeutet die Kapselung der Eigenschaften nicht die totale Abschottung der Klasse. Es hindert Sie also niemand daran, direkt auf Eigenschaften einer Klasse zuzugreifen und diese zu verändern. Perl geht vielmehr davon aus, dass Sie genügend Reife besitzen, um dies nicht zu tun. Besonders gefährlich wird ein solcher direkter Zugriff durch den Umstand, dass Klassen die Implementierung vor dem Aufrufer verbergen. Wurde die Realisierung einer Klasse verändert, so kann es sein, dass direkte Aufrufe ins Leere zielen. Aufrufer, die über die hierfür vorgesehenen Methoden auf die Eigenschaften einer Klasse zugreifen, merken von diesen Änderungen nichts.

6.2.3

Methoden, die den Verhaltensweisen eines Objektes entsprechen

Bisher haben wir lediglich Zugriffsmethoden für die Eigenschaften der Klasse erstellt, die diese direkt modifizieren oder abfragen. Klassen besitzen aber, wie wir weiter oben gesehen haben, auch Methoden, die den Verhaltensweisen von realen Objekten entsprechen. Um dies zu illustrieren, wenden wir uns der Klasse Konto zu, die über die beiden Methoden einzahlen und auszahlen verfügt. Auch diese Methoden werden mit Hilfe von Funktionen realisiert und unterscheiden sich vom Prinzip her kaum von den bereits vorgestellten Methoden zur Manipulation von Eigenschaftswerten. Erstellen wir zunächst den allgemeinen Rahmen der Konto-Klasse mit ihren Eigenschaftsmethoden.

6 Objektorientierte Programmierung

58 package Konto;

# Konstruktor erstellen sub new { # Klassennamen uebernehmen my $klasse = shift; # Argumente uebernehmen my ($kontoNr, $kontoinhaber, $kontostand, ) = 0_; # Anlegen eines anonymen Hashes zur Aufnahme der # Eigenschaftswerte und Uebernehmen der Werte my $r_konto = { "kontoNr" => $kontoNr, "kontoinhaber" => $kontoinhaber, "kontostand" => $kontostand,

>; # Instanz auf den Namen der Klasse taufen bless $r_konto, $klasse; # Instanz zurueckgeben return $r_konto;

> sub kontoNr { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Falls ein Wert ubergeben wurde, speichern $r_konto->-['kontoNr'} = shift if {'kontoNr'};

> sub kontoinhaber { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Falls ein Wert ubergeben wurde, speichern $r_konto->{'kontoinhaber'} = shift if # Eigenschaftswert zurueckgeben return $r_konto->{'kontoinhaber'};

} sub kontostand { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Falls ein Wert ubergeben wurde, speichern $r_konto->{'kontostand'} = shift if ®_; # Eigenschaftswert zurueckgeben return $r_konto->{'kontostand'};

6.2 Objektorientierte Programmierung mit Perl

59

> # einen wahren Wert zurueckgeben return 1;

Die Methode auszahlen soll die folgenden Aufgaben erfüllen: • Testen, ob beim Aufruf ein Parameter übergeben wurde und, ob es sich dabei um eine Zahl größer 0 handelt. • Ist dies der Fall, ist zu testen, ob der Auszahlungsbetrag kleiner oder gleich dem Kontostand ist. • Nur, wenn beide Bedingungen erfüllt sind, soll eine Auszahlung stattfinden. War dies nicht der Fall, so ist eine Fehlermeldung auszugeben, die den Aufrufer darüber informiert, was er falsch gemacht hat. • Die Methode gibt bei Erfolg den neuen Kontostand und bei einem Misserfolg -1 zurück. Der Fehlerwert -1 ist wichtig, damit der Aufrufer erkennt, ob wirklich ein Fehler vorlag oder nur alles Geld abgehoben wurde. Wir können diese Methode in der folgenden Weise implementieren.

#

...

sub auszahlen { # Referenz auf Objekt uebernehmen my $r_konto = shift; # testen, ob ein Argument uebergeben wurde if (C_) { # Argument uebernehmen my $ks = shift; # testen, ob eine Zahl > 0 uebergeben wurde if ($ks > 0) { # testen, ob ausreichend Geld auf dem Konto ist if ($ks {'kontostand'}) { # neuen Kontostand setzen $r_konto->{'kontostand'} - = $ks; # kontostand zurueckgeben return $r_konto->{'kontostand'}; } eise { print "Nicht genuegend Geld auf dem Konto vorhanden!\n";

} } eise { print "Keine Zahl oder 0 uebergeben!\n";

60

6 Objektorientierte Programmierung

} > else { print "Kein Argument uebergeben!\n";

> }

# -1 als Fehlerwert zurueckliefern return -1;

# einen wahren Wert zurueckgeben return 1;

Fügen Sie den oben angegebenen Code in Ihre Konto-Klasse vor der r e t u r n - A n w e i s u n g ein. U m zu testen, verwenden Sie folgenden Code.

#!/usr/bin/perl # Datei: kontol.pl # Einbinden der Klasse Konto use Konto; # neue Konto-Instanz erzeugen und mit Werten vorbelegen Seigenschaften = (100, 'Daisy Duck', 123.45); my $ko = Konto->new(Seigenschaften); # Eigenschaftswerte ausgeben print $ko->kontoNr . ", " . $ k o - K o n t o i n h a b e r . ", " . $ko->kontostand . "\n"; # Funktion auszahlen ohne Argument aufrufen - Fehler: print $ko->auszahlen() . "\n"; # Fehler: -1 # Funktion auszahlen mit einem falschen Argument aufrufen print $ko->auszahlen('heinz') . "\n"; # Funktion auszahlen mit einem zu grossen Wert aufrufen print $ko->auszahlen('567,89') . "\n"; # jetzt aber! print $ko->auszahlen(56.78) . "\n";

Sie sollten stets darauf achten, dass Sie, u m Methoden zu testen, alle Fehlermöglichkeiten berücksichtigen! Ansonsten können Sie sicher sein, dass Ihr Kunde als erstes die von Ihnen nicht getestete Fehlfunktion produziert, die in diesem Falle natürlich prompt zum Systemabsturz und lauten Telefonaten führt.

6.2 Objektorientierte Programmierung mit Perl

61

Die Methode einzahlen ist ein wenig einfacher zu realisieren, da wir hier nicht prüfen müssen, ob ausreichend Geld auf dem Konto vorhanden ist. Fügen Sie den nachstehenden Code hinter der Methode auszahlen in die soeben erstellte Klasse ein.

#

...

sub einzahlen { # Referenz auf Objekt uebernehmen my $r_konto = shift; # testen, ob ein Argument uebergeben wurde if («_) { # Argument uebernehmen my $ks = shift; # testen, ob eine Zahl > 0 uebergeben wurde if ($ks > 0) { # neuen Kontostand setzen $r_konto->{'kontostand'} += $ks; # kontostand zurueckgeben return $r_konto->{'kontostand'}; } eise { print "Keine Zahl oder 0 uebergeben!\n";

>

} eise { print "Kein Argument uebergeben!\n";

> # -1 als Fehlerwert zurueckliefern return -1;

} # einen wahren Wert zurueckgeben return 1;

Und schließlich das Skript zum Testen der Klasse. Fügen Sie auch hier den untenstehenden Code an das Ende Ihres Skriptes kontol.pl an.

print "Funktion einzahlen testen:\n"; # Funktion einzahlen ohne Argument aufrufen - Fehler: print $ko->einzahlen() . "\n"; # Fehler: -1 # Funktion einzahlen mit einem falschen Argument aufrufen print $ko->einzahlen('bertram') . "\n"; # aber j etzt! print $ko->einzahlen('98,76') . "\n";

62

6 Qbjektorientierte Programmierung

# und zum Test nochmal alles ausgeben print $ko->kontoNr . ", " . $ko->kontoinhaber . $ko->kontostand . "\n";

6.2.4

" .

Klassen- und Instanzmethoden, Klassen- und Instanzvariablen

Vielleicht haben Sie schon bemerkt, dass wir in unseren Beispielen bereits Klassen- und Instanzmethoden verwendet haben. In der Konto-Klasse ist der Konstruktor new eine Klassenmethode, die keine besondere Instanz der Klasse benötigt. Wir haben ja weiter oben dargestellt, dass diese zum Zeitpunkt des Aufrufes eines Konstruktors noch gar nicht existiert. Die Methoden einzahlen und auszahlen, wie auch die Methoden zum Setzen und Abfragen der Eigenschaften, sind Instanzmethoden, da sie, um sinnvoll eingesetzt werden zu können, eine konkrete Instanz der Konto-Klasse benötigen. Wie Sie sehen, unterscheiden sich in Perl Instanz- und Klassenmethoden nicht durch bestimmte Schlüsselwörter voneinander,wie das in vielen anderen Programmiersprachen der Fall ist. In Java oder C + + werden beispielsweise Klassenmethoden durch den Modifizierer s t a t i c gekennzeichnet. Beim Aufruf von Methoden sieht man allerdings deutlich den Unterschied, ob es sich um eine Klassenmethode

my $ko = Konto->new(@eigenschaften);

oder um eine Instanzmethode handelt

$ko->einzahlen('98,76');

Der Pfeiloperator (->) übergibt dabei an die aufgerufene Methode als ersten Parameter das Element, das auf seiner linken Seite steht. Im ersten Aufruf also den Namen der Klasse /Konto, im zweiten Aufruf eine Referenz auf die Instanz $ko. Aufgrund des Blessing-Mechanismus ist es Perl somit möglich, die Aufrufe zuzuordnen. In unseren Beispiel-Skripten haben wir dieses Argument stets aus der Parameterliste herausgeshiftet und zwischengespeichert. Der Zugriff auf das Objekt erfolgt stets mit Hilfe dieses Argumentes.

6.2 Objektorientierte Programmierung mit Perl

6.2.5

63

Vererbung

Wie bereits besprochen, ist die Vererbung eines der wichtigsten Konzepte der OOP schlechthin. Und es wird Sie nicht verwundern, dass auch Perl dieses Konzept in hervorragender Weise unterstützt. Perl realisiert die Vererbung mit Hilfe des Spezial-Arrays OISA. Der Begriff ISA sollte dabei als is a, zu deutsch ist ein gelesen werden. Dieses Array, das als Package-globale Variable deklariert sein muss, enthält eine Liste der Klassen, von denen die aktuelle Klasse erbt. Beachten Sie dabei, dass, sofern Sie das Pragma s t r i c t verwenden, es nur mit Hilfe des Pragmas v a r s möglich ist, Package-globale Variablen zu deklarieren. Um die Klasse GiroKonto zu erstellen, die von der Klasse Konto erbt, geben Sie den folgenden Code an. package GiroKonto; use Konto; # Modul Konto einbinden QISA = qw(Konto}; # von Klasse Konto erben

# Implementierung der Klasse

# Wahrer Rueckgabewert return 1;

Sofern Sie das Pragma strict verwenden package GiroKonto; use Konto; # Modul Konto einbinden use strict; use vars 'QISA'; OISA = qw(Konto}; # von Klasse Konto erben

# Implementierung der Klasse

64

6 Objektorientierte Programmierung

# Wahrer Rueckgabewert return 1;

Beachten Sie, dass es notwendig ist, das Modul der Oberklasse, in unserem Falle Konto .pm, mit Hilfe einer use oder einer require- Anweisung einzubinden, damit Perl auf die Methoden der Oberklasse zugreifen kann. Sie können mit dem folgenden Skript ein Objekt der Klasse GiroKonto anlegen und initialisieren. # Datei: GiroKonto.pl # Einbinden der Klasse Konto use GiroKonto; # neue Konto-Instanz erzeugen und mit Werten vorbelegen Seigenschaften = (100, 'Daisy Duck', 123.45); my $gko = GiroKonto->new(Seigenschaften); # Eigenschaftswerte ausgeben print $gko->kontoNr . ", " . $gko-Kontoinhaber . ", " . $gko->kontostand . "\n";

Die Klasse GiroKonto besitzt nun die gesamte Funktionalität und alle Eigenschaften der Klasse Konto, ohne dass Sie auch nur eine einzige Zeile Code hierfür gebraucht hätten. Wenn Sie die Oberklasse ergänzen oder ändern, spiegelt sich dies automatisch in allen abgeleiteten Klassen wider. Rufen Sie nun über eine Instanz der Klasse GiroKonto die Methode auszahlen auf, sucht Perl, wenn es die entsprechende Methode nicht findet, automatisch in allen im Array OISA aufgelisteten Oberklassen nach der Methode. Die Suche erstreckt sich dabei auch auf die in der Oberklasse im Array OISA angegebenen Klassen. Dies bedeutet, dass Sie nicht alle Oberklassen der Klassen-Hierarchie angeben müssen, sondern nur die jeweils direkten. Im Sinne der OOP tun Sie sich übrigens keinen Gefallen, wenn Sie versuchen, es besonders genau zu machen und jeweils die gesamte Vererbungshierarchie anzugeben! Stellen Sie sich vor, an irgend einer Stelle der Hierarchie fügen Sie eine Klasse ein, Sie wären gezwungen die OISA-Arrays in der gesamten Hierarchie zu ändern. Ein weiterer Punkt ist an dieser Stelle bemerkenswert: Perl unterstützt, ähnlich wie in C + + , die Mehrfachvererbung und nicht nur wie in Java die Einfachvererbung. Lassen Sie uns beide Begriffe kurz erläutern. In Java kann eine Klasse direkt nur jeweils von einer Oberklasse erben, das bedeutet, die Klasse GiroKonto kann nur von der Klasse Konto erben, nicht aber gleichzeitig von der

6.2 Objektorientierte Programmierung mit Perl

65

Klasse einer anderen Vererbungshierarchie, wie beispielsweise der Klasse Kontoinhaber. Dieser Umstand wird als Einfachvererbung bezeichnet. Der Vorteil liegt darin, dass eine potentielle Fehlerquelle ausgeschlossen ist. Andererseits müssen Sie in Fällen, in denen eine Mehrfachvererbung sinnvoll ist, erhebliche "Klimmzüge" veranstalten, um diesem Umstand Rechnung zu tragen. C + + und Perl hingegen verfolgen einen im Sinne der OOP flexibleren, allerdings auch gefährlicheren Ansatz. In beiden Sprachen ist es möglich, eine Klasse von Oberklassen verschiedener Vererbungs-Hierarchien erben zu lassen. Unsere Klasse GiroKonto könnte seine Funktionalität durchaus von den Klassen Konto und Kontoinhaber erben. Diese Möglichkeit wird in der Sprache der OOP Mehrfachvererbung genannt. Sie sollten diese Technik vor allem am Beginn Ihrer Objektorientierten Programmier-Tätigkeit tunlichst vermeiden, aber wenn Sie wissen was Sie tun, ist die Mehrfachvererbung ein mächtiger Mechanismus, der Ihnen viel Arbeit ersparen kann. Im folgenden Beispiel erstellen wir die Klasse SparKonto, die von der Klasse Konto abgeleitet wird. Wir werden in der abgeleiteten Klasse die Eigenschaft guthabenzinsSatz sowie die Methode jahreszinsBerechnen zufügen. Wir initialisieren die Klasse diesmal, indem wir zunächst einen leeren anonymen Hash anlegen, die neue Klasse taufen und anschließend die Eigenschaften durch Aufruf der entsprechenden Eigenschaftsmethoden initialisieren. Dabei werden die Aufrufe zur Initialisierung der Eigenschaften kontoNr, kontoinhaber und kontostand direkt an die Oberklasse weitergeleitet, die Initialisierung der Eigenschaft guthabenZinsSatz erfolgt mit Hilfe der entsprechenden Methode der neu angelegten Klasse. package SparKonto; use Konto; # von Klasse Konto erben OISA = qw(Konto); # Konstruktor der Klasse Konto ueberschreiben sub new -f # Klassennamen uebernehmen my $klasse = shift; # Argumente uebernehmen my ($kontoNr, $kontoinhaber, $kontostand, $guthabenzinsSatz ) = ®_; # Instanz initialisieren my $r_konto = {>; # Instanz auf den Namen der Klasse taufen bless $r_konto, $klasse; # Instanz durch. Aufruf der Eigenschaftsmethoden initialisieren # geerbte Eigenschaften: $r_konto->kont oNr($kontoNr);

66

6 Objektorientierte Programmierung $r_kont o->konto inhaber($kont o inhaber); $r_konto->kontostand($kontostand); # in der Unterklasse neu definierte Eigenschaften $r_konto->guthabenzinsSatz(IguthabenzinsSatz); # Aufrufen des Konstruktors der Oberklasse

>

# Instanz zurueckgeben return $r_konto;

# Methode zum Setzen und Abfragen des Guthabenzinssatzes sub guthabenzinsSatz { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Falls ein Wert ubergeben wurde, speichern $r_konto->{'guthabenzinsSatz'} = shift if Q_;

>

# Eigenschaftswert zurueck geben return $r_konto->{'guthabenzinsSatz'};

# Methode zum Berechnen des Jahreszinses sub jahreszinsBerechnen { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Guthabenzinssatz lesen my $zins = $r_konto->{'guthabenzinsSatz'}; # Jahreszins berechnen my $jahreszins = $r_konto->{'kontostand'} / 100 * $zins; # Jahreszins zurueckgeben return $jahreszins;

} # Weihren Rueckgabewert erzeugen return 1;

Speichern Sie den oben abgedruckten Programm-Code im gleichen Verzeichnis, in d e m sich bereits die Klasse Konto befindet unter dem Namen Sparkonto. pm. Sie können die Funktion der Klasse mit Hilfe des folgenden Skriptes testen.

#!/usr/bin/perl # Datei: SparKonto.pl

6.2 Objektorientierte Programmierung mit Perl

67

# Einbinden der Klasse Konto use SparKonto; # neue Konto-Instanz erzeugen und mit Werten vorbelegen Qeigenschaften = (100, 'Daisy Duck', 1234.56, 2.5); my $sko = SparKonto->new(Qeigenschaften); # Eigenschaftswerte ausgeben print $sko->kontoNr . ", " . $sko->kontoinhaber . ", " . $sko->kontostand . ", " . $sko->guthabenZinsSatz . "\n"; # Jahreszins ausgeben print "Auf Ihre Einlagen erhalten Sie einen Jahreszins von: "; print $sko->jahresZinsBerechnen . "\n";

6.2.6

Überschreiben von Methoden

Wie oben dargestellt, ist es mitunter notwendig, Methoden, die in der Oberklasse definiert sind, in der Unterklasse neu zu definieren. Man nennt diesen Vorgang Uberschreiben. In Perl überschreiben wir Methoden einfach dadurch, dass wir diese in einer Unterklasse neu anlegen. Perl beginnt mit seiner Suche nach der auszuführenden Methode stets innerhalb der aktuellen Klasse. Ist Perl bei seiner Suche erfolgreich, versucht es nicht mehr weiter oben in der Hierarchie weitere Methoden gleichen Namens zu finden, sondern führt die gefundene Methode gleich aus. Im folgenden Beispiel überschreiben wir die Methode kontostand in der Klasse SparKonto.

package SparKonto;

# Methode Kontostand ueberschreiben sub kontostand { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Kontostand uebernehmen my $ks = shift if Q_; # Die folgende Anweisung dient dazu, das Ueberschreiben # zu demonstrieren print "Ich fuehre die ueberschriebene Methode" . " der Klasse SparKonto aus!\n"; # und wie gehabt ... # Falls ein Wert ubergeben wurde, speichern

68

6 Objektorientierte Programmierung $r_konto->{'kontostand'} = shift if 0_; # Eigenschaftswert zurueckgeben return $r_konto->{'kontostand'};

Fügen Sie den oben abgedruckten Code in die Klasse Sparkonto ein, und führen Sie die Anwendung Sparkonto.pl erneut aus. An Hand der ausgegebenen Meldung stellen wir fest, dass tatsächlich die in der Klasse Spar Konto neu definierte Methode und nicht die in der Oberklasse definierte ausgeführt wird.

6.2.7

Aufrufen von überschriebenen Methoden der Oberklasse

Mitunter sind Methoden, die solche aus der Oberklasse überschreiben, lediglich Erweiterungen der dort bereits festgelegten Funktionalität. In diesem Fall ist es sinnvoll, in der Methode der Unterklasse diese zusätzliche Funktionalität zu implementieren und anschließend die entsprechende Methode der Oberklasse aufzurufen. Zu diesem Zweck bietet Perl das Pseudopaket SUPER an, mit dessen Hilfe es möglich ist, eine Methode der Oberklasse aufzurufen. Der Vorteil der Verwendung dieses Pseudopaketes liegt darin, dass der Name der aufzurufenden Klasse nicht "hart" codiert werden muss. Dieser Vorteil kommt vor allem dann zu tragen, wenn Sie die Klassenhierarchie verändert haben und beispielsweise die ursprüngliche Oberklasse durch eine andere mit neuem Namen ersetzt wurde. Um diesen Vorgang zu illustrieren, überschreiben wir in unserer Klasse Sparkonto die Methode kontoinhaber der Oberklasse Konto. package SparKonto;

# Methode Kontoinhaber ueberschreiben sub kontoinhaber { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Namen des Kontoinhabers uebernehmen $ki = shift if ; # Methode in der Superklasse aufrufen my $ki = $r_konto->SUPER::kontoinhaber($ki);

6.2 Objektorientierte Programmierung mit Perl

69

# Eigenschaftswert zurueckgeben return $ki;

>

Der dargestellte Aufruf der Oberklasse mit Hilfe des Pseudopaketes SUPER ist allerdings nur dann sinnvoll, wenn von der Möglichkeit der Mehrfachvererbung nicht Gebrauch gemacht wurde, da sonst Perl nicht weiß, in welcher Oberklasse nach der entsprechenden Methode gesucht werden soll. Erbt die aktuelle Klasse von mehr als einer Klasse, muss die gewünschte Oberklasse für den Zugriff explizit, wie im folgenden Beispiel, angegeben werden.

package SparKonto;

# Methode Kontoinhaber ueberschreiben sub kontoinhaber { # Referenz auf Objekt uebernehmen my $r_konto = shift; # Namen des Kontoinhabers uebernehmen $ki = shift if ; # Methode in der Superklasse aufrufen my $ki = $r_konto->Konto::kontoinhaber($ki); # Eigenschaftswert zurueckgeben return $ki;

6.2.8

Und die Polymorphie?

Wir haben nun die Geheimnisse der Objektorientierten Programmierung gelüftet, und Sie sind in der Lage, selbst Objektorientierte Anwendungen mit Perl zu erstellen. Sie haben wahrscheinlich gemerkt, dass Sie mit Hilfe des Objektorientierten Paradigmas eine Menge Code einsparen können. Bei genauer Planung schreiben Sie wiederverwertbaren und modularen Code. Sie erhöhen die Sicherheit Ihrer Anwendung und minimieren den Aufwand zur Programmpflege. Arbeiten Sie in Teams, ist der Objektorientierte Ansatz geradezu ideal. Aber Achtung! Wie gesagt, die OOP ist nichts für Leute, die sich mit zwei Litern Kaffee, drei Schachteln Zigaretten und einer Schnellpizza an den Computer setzen und wild drauf los programmieren. OOP braucht sorgfältige Planung, damit sie sinnvoll eingesetzt werden kann!

70

6 Objektorientierte Programmierung

Zum Abschluss dieses Kapitels möchten wir Ihnen noch den Begriff Polymorphie erklären, der stets im Zusammenhang mit der OOP auftaucht. Polymorphie bedeutet nichts anderes, als dass verschiedene Klassen über Methoden mit demselben Namen verfügen können. So kann beispielsweise sowohl eine Klasse Hund als auch eine Klasse L u f t b a l l o n über die Methode p l a t z verfügen 11 . Das Objekt weiß dabei stets aufgrund seiner Klassenzugehörigkeit, welche der Methoden p l a t z auszuführen ist. Bedeutung erhält dieser Mechanismus vor allem innerhalb einer Vererbungshierarchie, wenn die Methode Nahrungsauf na Time an mehreren Stellen überschrieben wurde. Hier ist es von grundlegender Bedeutung, dass beispielsweise der Mensch in einer Klassenhierarchie von Tieren die ihm zugeschriebene Methode und nicht die weiter oben in der Hierarchie bei den Insekten definierte verwendet. Aber das wurde ja alles bereits erklärt. Nehmen Sie also das Wort Polymorphie als das, was es ist: Ein hübsches Wort, das Sie immer dann anbringen können, wenn Sie Ihren Intelligenzquotienten unter Beweis stellen müssen.

11 Das Beispiel stammt übrigens aus dem lesenswerten Buch zur Programmiersprache C + + [Kloeppel97].

7

Datenbank-Programmierung mit

Eine Anwendung, die nicht über eine Datenbank-Anbindung verfügt, ist heute kaum mehr vorstellbar. So ist ein Maßstab, ob eine Programmiersprache eine "gute" Programmiersprache ist, sicherlich die Frage, wie leicht oder kompliziert es mit den Mitteln der betreffenden Sprache ist, auf Datenbanksysteme zugreifen zu können. Als DatenbankEntwickler sollten Sie zudem einige weitere Fragen stellen, bevor Sie mit der Arbeit beginnen: • Wie hoch ist der Abstraktionsgrad, den die Datenbankschnittstelle der von mir gewählten Programmiersprache bietet? Ist es also ohne großen Aufwand möglich, das zugrundeliegende Datenbank-Management-System zu wechseln, wenn beispielsweise die Datenbank umfangreicher wird als in der Planungsphase angenommen. • Nicht zuletzt von Bedeutung ist die Frage nach der Geschwindigkeit der Datenzugriffskomponenten, besonders dann, wenn es sich beim geplanten Projekt um eine Web-Anwendung handelt oder der zu bearbeitende Datenbestand sehr groß ist. Denn, was nützt die komfortabelste Datenbank-Schnittstelle in Ihrer Programmiersprache, wenn die von Ihnen erstellte Anwendung im wahrsten Sinne des Wortes "absäuft"? Selbstverständlich müssen Sie immer Kompromisse eingehen. So werden Sie zumeist eher auf Komfort als auf Geschwindigkeit verzichten können. Perl zählt, wenn Sie die oben aufgeführten Punkte betrachten, sicherlich zu den besten verfügbaren Programmiersprachen (wir vermeiden hier bewusst den Superlativ!). Perl bietet mit DBI/DBD eine komfortable Datenbankschnittstelle mit einem sehr hohen Abstraktionsgrad. Bei einem Wechsel des Datenbank-Management-Systems müssen Sie in der Regel nur eine Zeile Programmcode ändern 1 . Zudem ist Perl/DBI schnell2! Einen weiteren Vorteil bietet die Plattformunabhängigkeit von Perl. Ihre Datenbank-Anwendung ist ohne jede Änderung auf einem LINUX-, einem Windows- oder Mac-System lauffähig. Das Kapitel zur Datenbank-Programmierung gliedert sich in drei Teile: • Der erste Teil befasst sich mit den Grundlagen des Designs von relationalen Datenbank-Systemen. Perl bietet Ihnen, wie bereits angedeutet, ein leistungsfä1 Und wenn Sie Ihre Anwendung gut geplant haben und die Verbindungsdaten aus einer Initialisierungsdatei einlesen, nicht einmal das. 2 Sogar sehr schnell - in einem Test des Autors war die Perl-Lösung ungefähr zwanzig mal schneller als eine vergleichbare Lösung, die mit VisualBasic und A D O erstellt wurde. Allerdings war die zweite Anwendung nicht sehr gut optimiert.

72

7 Datenbank-Programmierung mit Perl/DBI higes und vor allem schnelles Datenbank-Interface. Dieses kann seine Leistungsfähigkeit aber nur dann voll zur Geltung bringen, wenn die zugrundeliegende Datenbank gewissen Mindestanforderungen bezüglich der Gestaltung und Datenorganisation genügt. Sind diese nicht erfüllt, kommt jeder Versuch, mit Perl auf Ihre Datenbank zuzugreifen ungefähr dem Versuch gleich, mit Ihrem neuen Ferrari auf Feldwegen fahren zu wollen. Wir werden uns in diesem Abschnitt zudem damit befassen, wie Sie den Datenbank-Server MySQL auf Ihrem System einrichten und mit den notwendigen Rechten versehen. Zudem erläutern wir, welche Perl-Module Sie für das Durcharbeiten dieses Kapitels benötigen. • Perl/DBI nutzt zum Zugriff auf Datenbanken ausschließlich die Abfragesprache SQL3. Daher sind zumindest grundlegende Kenntnisse in dieser Abfragesprache nötig, um mit der Datenbank-Schnittstelle von Perl auf Ihre Datenbanken zugreifen zu können. Dieses Wissen vermitteln wir Ihnen im zweiten Abschnitt dieses Kapitels. • Der dritte Teil befasst sich ausführlich mit der Programmierung der Datenbankschnittstelle Perl/DBI. Wir werden Ihnen die Arbeit mit der Schnittstelle anhand von Beispielen vorstellen und dabei auch einige Tipps und Tricks einführen. Schließlich möchten wir Sie auf mögliche Fallstricke bei der Arbeit mit Perl/DBI hinweisen.

7.1

Grundlagen des Datenbank-Designs

Bevor wir uns mit der Abfragesprache SQL und der Programmierung von Perl/DBI beschäftigen können, sollten Sie die Grundlagen des Datenbankdesigns kennen. In der jahrelangen Praxis des Autors hat sich gezeigt, dass die meisten Probleme von DatenbankAnwendungen bereits in der Design-Phase angelegt sind. Eine nachlässig oder schlecht konzipierte Datenbank wird in den seltensten Fällen ihre Aufgaben zufriedenstellend erfüllen und spätestens dann, wenn es notwendig ist, die Datenstruktur anzupassen, gibt so manche Datenbank-Anwendung salopp gesagt "ihren Geist" auf. Zudem sollten Sie stets bedenken, dass eine schlecht entworfene Datenbankstruktur aus jedem Ferrari (sprich: schnelles Datenbank-Management-System) einen "lahmen Gaul" macht! Im folgenden Abschnitt werden wir uns daher zunächst kurz mit dem Aufbau von relationalen Datenbanken und anschließend mit ihrem Design befassen.

7.1.1

Relationale Datenstrukturen

In relationalen Datenbanken werden die Daten in einzelnen Tabellen organisiert und verwaltet. Über Tabellen-Grenzen hinweg werden die Daten der Tabellen verknüpft. Dabei ist der Aufbau einer solchen Datenbank nicht so einfach, wie es auf den ersten Blick scheinen mag, da relationale Datenbanken in der Praxis aus einer Vielzahl solcher Tabellen bestehen können - oft weit mehr als einhundert Tabellen, so dass komplexe Strukturen entstehen. Ausgesprochen als sickel.

7.1 Grundlagen des Datenbank-Designs

73

Bevor wir uns mit dem Design von Datenbanken beschäftigen können, sollten wir einige wichtige Begriffe erklären. Tabellen werden in der relationalen Terminologie Relation genannt, woher das relationale System auch seinen Namen hat 4 . Eine Relation besteht dabei aus einem Kopf, in dem die Spalten definiert sind und einem Rumpf, der aus den Dateneinträgen der Datenbank besteht. Eine Spalte einer Datenbank-Relation wird Attribut genannt. Einen einzelnen Datensatz einer Datenbank, also eine Zeile einer Relation, nennt man Tupel. Bei dem Entwurf der Relationen sind prinzipiell folgende Ziele zu berücksichtigen: • In einer Datentabelle dürfen keine doppelten Tupel enthalten sein. Das bedeutet, jeder Datensatz muss genau eine Tatsache bzw. ein Objekt bezeichnen. In der relationalen Terminologie werden solche Objekte als Entitäten bezeichnet. • Die einzige Ordnung der Datensätze einer Relation ist die Reihenfolge ihrer Anlage. Andere Ordnungen, wie beispielsweise eine alphabetische Sortierung, werden erst bei der Ausgabe der Daten erzeugt, aber niemals in der Datenrelation selbst abgelegt. Der Versuch, eine solche Ordnung innerhalb einer Datenbank zu erzeugen, würde zum einen zu extremen Bearbeitungszeiten bei der Anlage neuer Tupel führen, zum anderen wegen der permanenten Neusortierung innerhalb der Relation auf Dauer zu Inkonsistenzen und Funktionsstörungen. • Auch die Attribute sollten nicht geordnet sein. Bei der Anlage einer Relation ergibt sich eine solche Ordnung meist von selbst aus dem logischen Zusammenhang heraus. Bei der späteren Pflege der Datentabelle werden neue Attribute einfach an das Ende der Relation angehängt. Eine Ordnung der Attribute wird in der Regel erst bei der Abfrage der Daten angelegt. • Alle Attribute einer Tabelle dürfen in jedem Datensatz nur genau einen Wert enthalten. Man nennt dies atomar. • Kein Attribut sollte in einer Tabelle mehrfach vorkommen. Auch auf die gesamte Datenbank bezogen, sollte es keine solchen mehrfach Vorkommen von Attributen geben. Dieses mehrfache Auftreten von Attributen wird in der Fachsprache Redundanz oder deutsch Weitschweifigkeit genannt. Wir werden diese Begriffe im nächsten Abschnitt noch weiter untersuchen. Befolgt eine Relation alle angesprochenen Regeln, wird sie als normalisiert bezeichnet. Wir werden weiter unten besprechen, auf welche Weise diese Normalisierung erreicht wird. 4 In der Literatur findet man auch oft die Erklärung, das relationale System beziehe seinen Namen von dem englischen Begriff relation, was ins Deutsche übersetzt soviel wie Beziehung bedeutet. Das Datenbank-System beziehe seinen Namen nach dieser Lesart aus dem Umstand, dass relationale Datenbanken aus einer Vielzahl von verknüpften, also über Beziehungen miteinander verbundenen Tabellen bestehen. Auch wenn diese Definition nicht ganz zutreffend ist, erklärt sie dennoch das Wesen relationaler Datenbanken recht gut. Wir haben daher nichts dagegen einzuwenden, wenn Sie sich diese Definition merken.

74

7 Datenbank-Programmierung mit Perl/DBI

7.1.2

Grundregeln des Datenbankdesigns

Nachdem wir im vorhergehenden Teil die grundlegende Begrifflichkeit des relationalen Datenmodells erörtert haben, wenden wir uns im folgenden den grundlegenden Modellen und Vorgehensweisen des Datenbankdesigns zu. Es sei in diesem Zusammenhang darauf hingewiesen, dass es für das Funktionieren einer relationalen Datenbank von grundlegender Bedeutung ist, diese Regeln genau zu beachten. Datenbanken, die diesen Prinzipien nicht folgen, werden bestenfalls leidlich arbeiten; im produktiven Einsatz hingegen sind sie nicht verwendbar. Keine Redundanzen. Für jedes Datenelement wird genau eine Spalte vorgesehen, es darf nicht in mehreren Spalten vorkommen, wie etwa im Feld Vor- und Familienname und im Feld Vorname. Doppelt vorhandene Datenelemente bringen keinen weiteren Informationsgewinn, führen aber zu einer Verschwendung von Speicherplatz und Arbeitszeit. Zudem ist durch eine solche Redundanz die Konsistenz der Datenbank erheblich gefährdet. Stellen Sie sich vor, im Feld Vor- und Familienname findet sich der Eintrag "Helmut Seidel", im Feld Vorname hingegen der Eintrag "Helmuth". Welcher der beiden Angaben möchten Sie nun glauben? Besteht eine Datenbank aus mehreren Relationen, müssen diese auch untereinander frei von Überschneidungen sein. Entitäten - keine Prozessdaten. In einer Relation werden nur sogenannte Entitäten (Tatsachen) aufgenommen. Entitäten sind z.B. eine Person, eine Maschine, eine Bestellung, etc. 5 Jede Entität wird durch ihre Eigenschaften bzw. Attribute eindeutig beschrieben. Welches dieser Attribute für die Datenverarbeitung bedeutsam ist, hängt dabei vom Verarbeitungszweck ab. Werte, die durch Rechenoperationen mit Attributen ermittelt werden können, sind keine Entitäten sondern Prozessdaten und werden nicht in die Relation aufgenommen, da sie wiederum eine Form von Redundanz darstellen. Durch die Verwendung von Prozessdaten kann es in Datenbanken zu erheblichen Problemen kommen. Hierzu ein Beispiel: In einer Datenbank sind die Felder Geburtsdatum und Alter aufgenommen. Beim Attribut Alter handelt es sich allerdings um ein Prozessdatum, da es sich aus dem Geburtsdatum sowie dem Tagesdatum errechnen lässt. Die resultierende Datenbank müsste nun täglich gepflegt werden, damit alle Datensätze das aktuelle Alter enthalten 6 . Stellen Sie sich den damit verbundenen Aufwand bei ca. 10.000 Datensätzen oder mehr bitte einmal vor! Eindeutigkeit. Zu beachten ist ferner, dass die Beschreibung einer Entität stets eindeutig sein muss. So darf beispielsweise die Beschreibung einer Person nur auf eine ganz bestimmte Person zutreffen. Um diese Eindeutigkeit zu erreichen, erweitert man den Datensatz in der Regel um ein Identifikationsmerkmal, etwa eine Artikel- oder Lieferantennummer. Solch ein Identifikationsmerkmal nennt man in der Datenbankterminologie Schlüssel, Schlüsselbegriff oder Schlüsselattribut. In neuerer Zeit hat sich auch der Begriff Primärschlüssel für ein solches Attribut eingebürgert, wir werden zumeist diesen Begriff verwenden. 5

Objektorientiert gedacht, ließe sich eine Entität auch als Instanz eines Objektes beschreiben. Zwar haben Menschen in der Regel nur ein einziges Mal im Jahr Geburtstag, aber dummerweise hat fast jeder Mensch (in einer Datenbank) an einem anderen Tag Geburtstag! 6

7.1 Grundlagen des Datenbank-Designs

7.1.3

75

Normalformlehre

Die Normalformlehre beschreibt, wie Relationen aufgebaut werden sollten, um Redundanzen und Zugriffsprobleme zu vermeiden. Bereits bei seiner Vorstellung des relationalen Datenmodells 1970 führte E. F. Codd die ersten drei Normalformen ein. Diese haben bis heute unverändert ihre Bedeutung erhalten, wurden allerdings in der Folgezeit um weitere Normalformen erweitert, die aber nicht die entscheidende Wichtigkeit der ersten drei besitzen. Um diese zu erklären, werden wir eine kleine CD-Datenbank (CD_DATA) mit folgenden Attributen erstellen.

Attributs-Name cd_titel kuenst1er_name cd_ersch_jahr song_name song_laenge rl_name rl_adresse rl_plz rl_ort

Bedeutung Name der CD Name des Künstlers Erscheinungsjahr der CD Name eines Liedes Länge eines Liedes Name des Record-Labels Straße und Hausnummer des Record-Labels Postleitzahl des Record-Labels Ort des Record-Labels

Die erste Normalform. Eine Relation ist in der ersten Normalform, wenn alle zugrundeliegenden Gebiete nur atomare Werte enthalten. Ein Gebiet bezeichnet dabei die Menge der möglichen Einträge in einem Datensatz. Das Gebiet des Feldes r l _ p l z bezeichnet also die Menge aller möglichen Postleitzahlen. Dies bedeutet, kein Attribut darf mehrere Werte in einem Datensatz besitzen. Nach der ersten Normalform müssen daher die Felder song_name und song_laenge aus der Relation herausgenommen werden. Es ergibt sich nun folgende Datenstruktur. cd_relation cd_id cd_titel kuenstler_name cd_ersch_jahr rl_name rl_adresse rl_plz rl_ort song_relation song_id song_name

76

7 Datenbank-Programmierung mit Perl/DBI

song_laenge Beachten Sie, dass wir mit den zusätzlichen Attributen cd_id und song_id in jeder Relation ein Primärschlüssel-Feld angelegt haben, welches die Tupel später eindeutig bestimmen wird. Die zweite Normalform. Jedes Element des Datensatzes, das kein Schlüsselattribut ist, muss voll vom Schlüssel abhängen. Besteht ein Schlüssel aus mehreren Teilschlüsseln, so ist das Element aus der Relation herauszunehmen, das nur von einem Teilschlüssel abhängt. In unserem Beispiel kann ein Künstler mehrere CDs eingespielt haben. Damit ist aber das Attribut kuenstler_name nicht vom Schlüsselbegriff cd_id abhängig. Es ist daher sinnvoll, das Feld kuenstler_name ebenfalls aus der c d _ r e l a t i o n herauszunehmen. cd_relation cd_id cd_titel cd_ersch_jahr rl_name rl_adresse rl_plz rl_ort song_relation song_id song_name song_laenge kuenstler_relation kuenstler_id kuenst1er_name

Die dritte Normalform. Alle Elemente dürfen nur vom Schlüsselattribut, nicht aber von anderen Elementen des Datensatzes abhängen. Zwischen weiteren Elementen darf es also keine Abhängigkeiten geben. Zu einem Warengruppenschlüssel gehört beispielsweise eine bestimmte Bezeichnung. Diese ist aber nicht direkt vom Schlüsselattribut sondern vom Warengruppenschlüssel abhängig. Daher ist die Warengruppenbezeichnung in einer gesonderten Tabelle zu erfassen. Der Warengruppenschlüssel stellt in diesem Fall einen Fremd- oder Sekundärschlüssel dar. Wird in einem Satz der Schlüssel von zwei oder mehreren Elementen gebildet, spricht man von einem zusammengesetzten Schlüssel. In unserem Beispiel müssten nach der dritten Normalform die Angaben zum Record-Label in einer eigenen Relation untergebracht werden. cd_relation cd_id cd_titel

7.1 Grundlagen des Datenbank-Designs

77

cd_ersch_jahr song_relation song_id song_name song_laenge kuenstler_relation kuenstler_id kuenst1er_name record_label_relation rl_id rl_name rl_adresse rl_plz rl_ort

Zweckmäßigkeitsüberlegungen. Manche Datenelemente zeichnen sich dadurch aus, dass sich eine Änderung in ihren Werten auf die gesamte Tabelle auswirkt. Wird beispielsweise der Umsatzsteuersatz verändert, hat dies Auswirkungen auf sämtliche Datensätze einer Artikeltabelle. Hier ist es sinnvoll, die Steuersätze in einer Umsatzsteuertabelle zu speichern. Diese Tabelle enthält einen Steuerschlüssel, dem jeweils ein bestimmter Steuersatz zugeordnet ist. Alle Artikel-Datensätze greifen nun über diesen Steuerschlüssel auf den Steuersatz zu. Kommt eine zukünftige Regierung plötzlich auf die Idee, die Umsatzsteuer zu senken 7 , braucht dieser neue Steuersatz lediglich in der Umsatzsteuer-Relation geändert werden. Alle Artikel der Artikel-Relation spiegeln sofort diese Änderung. Dies ist nicht nur deutlich einfacher, als alle Artikel-Datensätze zu ändern, sondern bewahrt Sie auch vor peinlichen Fehlern, wenn Sie versehentlich vergessen haben, einige Datensätze zu ändern. Denormalisierung. Die record_label-Relation enthält die Attribute Postleitzahl und Ort ( r l _ p l z und r l _ o r t ) . Zwischen diesen Attributen besteht eine Abhängigkeit, was einen Verstoß gegen die dritte Normalform darstellt. Da aber voraussichtlich jede Plattengesellschaft eine eigene PLZ hat, würde die Aufteilung in zwei getrennte Relationen einen Mehraufwand bei der Datenbankpflege bedeuten und keinerlei Gewinn bringen. Es ist daher in diesem speziellen Fall sinnvoll, auf die Normalisierung zu verzichten. Diesen Vorgang nennt man Denormalisierung.

7.1.4

Beziehungsstrukturen

Zum Abschluss der Datenbankdesign-Phase müssen die bisher erstellten Relationen über die Schlüsselattribute miteinander verbunden werden. Dies geschieht, indem in die Re7

Ich hoffe dem Leser ist klar, dass dies nur ein frommer Wunsch des Autors ist!

78

7 Datenbank-Programmierung mit Perl/DBI

lation, die nicht den Primärschlüssel besitzt, ein Verweis auf diesen eingefügt wird. Derartige Schlüssel werden als Fremd- oder Sekundär-Schlüssel bezeichnet. Die Tabelle, in der das verbindende Element den Primärschlüssel bildet, nennt man Master-Tabelle-, die, in der das verbindende Element einen sogenannten Fremdschlüssel bildet, nennt man Detail- Tabelle. Nach dem Einfügen der Fremdschlüssel in unsere Datenbank ergibt sich folgendes Bild. cd_relation cd_id cd_titel cd_ersch_jahr kuenstler_id rl_id song_relation song_id cd_id song_name song_laenge kuenstler_relation kuenstler_id kuenstler_name record_label_relation rl_id rl_name rl_adresse rl_plz rl_ort

Im nächsten Schritt gilt es, die Art der Beziehung zwischen den einzelnen Relationen festzulegen. Hierzu bedient man sich am Besten einer sogenannten Beziehungsgrafik, in der die Verknüpfungen der einzelnen Relationen untereinander durch Linien dargestellt werden. Im Einzelnen lassen sich drei Typen von Beziehungsstrukturen unterscheiden, die im Folgenden beschrieben werden. Die 1 : 1 - Beziehung. Diese Form der Beziehung wird nur relativ selten verwendet. Bei der 1 : 1 Beziehung entspricht jeder Tupel in Relation A null oder einem Tupel in Relation B. Als Beispiel kann eine Personaldatenbank dienen, bei der aus Gründen der Datensicherheit die Angaben zur Person von denen zum Einkommen der betreffenden Person getrennt werden. Manchmal kann es sinnvoll sein, eine 1 : 1 - Beziehung zu verwenden, wenn nicht jedem Datensatz in Relation A ein Datensatz in Relation B entspricht, da hierdurch sowohl

79

7.1 Grundlagen des Datenbank-Designs

einkommen

personal pfirsnnal-nr name

Abb.

7.1: Die 1:1-

1 • 1

pfirsnnal-nr nettogehalt

Beziehung

Speicherplatz als auch Verarbeitungszeit eingespart wird. In unserer Beispieldatenbank findet sich keine 1 : 1 Beziehung. Die 1 : n - Beziehung. Diese Beziehungsform ist bei relationalen Datenbanksystemen die am häufigsten anzutreffende. Bei dieser Beziehung sind jedem Datensatz in Tabelle A null, ein oder auch mehrere Tupel in Tabelle B zugeordnet. So sind in unserer Beispieldatenbank jeder CD mehrere Songs zugeordnet.

Abb.

7.2: Die 1 : n - Beziehung

Die Tabelle, in welcher der Verbindungsschlüssel der Primärschlüssel ist, heißt Mastertabelle, die Tabelle, in welcher er der Fremdschlüssel ist, heißt Detailtabelle. Die n : m - Beziehung. Dies ist die am schwierigsten darzustellende Beziehungsform. Dabei sind jeder Zeile in Relation A null, ein oder mehrere Datensätze in Relation B zugeordnet, andererseits sind jedem Datensatz in Relation B wiederum null, ein oder mehrere Datensätze in Relation A zugeordnet. In unserem Beispiel findet sich diese Beziehungsform zwischen der c d _ r e l a t i o n und der k u e n s t l e r _ r e l a t i o n . So kann auf der einen Seite ein Künstler mehrere CDs aufgenommen haben, auf der anderen Seite können aber auch auf einer CD mehrere Künstler mitwirken. Diese Relationsform lässt sich in den meisten Datenbank-Management-Systemen nicht direkt sondern nur über Zwischentabellen darstellen, indem diese Beziehung in zwei 1 : n - Beziehungen aufgelöst wird. Wenn Sie über die vorgestellte CD-Datenbank noch einmal in Ruhe nachdenken, werden Sie feststellen, dass zwischen der c d _ r e l a t i o n und der s o n g _ r e l a t i o n ebenfalls eine n:m-Beziehung besteht. Tatsächlich ist es nicht nur üblich, dass sich auf einer CD mehrere Songs (früher nannte man sie Lieder) finden, sondern im Zeitalter von sogenannten Samplern und Greatest Hits-CDs kann es durchaus vorkommen, dass sich ein Lied auf mehreren CDs in Ihrer Sammlung befindet. In unserer Beispiel-Datenbank ignorieren

80

7 Datenbank-Programmierung mit Perl/DBI

cd_relation cdjd

ccLkuenstler 1 :n

cd_titel

Abb.

7.3:

kuenstlerjd cdjd

n : 1

kuenstler_rel. kuenstler id kuenstler_name

Die n : m - Beziehung

wir diesen Umstand der Übersichtlichkeit halber. Wenn Sie die vorgestellte Datenbank verwenden möchten, um Ihre CD-Sammlung zu verwalten, sollten Sie diesem Umstand Rechnung tragen; Sie erleben ansonsten garantiert eines Tages eine unangenehme Überraschung!

7.1.5

Aufnahmestruktur

Nachdem nun die Datenstruktur festgelegt ist, müssen noch die Datentypen der einzelnen Attribute ergänzt werden. Die wichtigsten Datentypen, die in allen Datenbanksystemen nach SQL-Standard enthalten sind, lauten

Datentyp INT FLOAT DOUBLE

DATE TIME

Beschreibung Ganzzahlen mit oder ohne Vorzeichen Fließkommazahlen einfacher Genauigkeit Fließkommazahlen doppelter Genauigkeit. Dies ist der Standarddatentyp, den Sie für Fließkommawerte verwenden sollten. Sie sollten DOUBLE auch für Geldbeträge verwenden, obwohl es in einigen Datenbank-Management-Systemen auch Datentypen wie MONEY oder CURRENCY gibt. Diese stellen allerdings keine Standard-Typen dar und sollten daher nur mit Vorsicht verwendet werden. Ein Standard-Datums-Wert Ein Standard-Zeit-Wert

Ebenso legen Sie in diesem Schritt zu jedem Feld, falls möglich, den zulässigen Wertebereich fest. Zu jedem Schlüsselfeld notieren Sie, ob es sich hierbei um einen Primäroder um einen Sekundärschlüssel handelt. Bevor Sie nun die so gewonnene Datenstruktur in eine Datenbank umsetzen, sollten sie diese noch ausführlich überdenken und, nach Möglichkeit, mit den zukünftigen Anwendern genauestens erörtern. Bedenken Sie, dass Fehler in der Planungsphase relativ leicht zu beseitigen sind, in der fertiggestellten Datenbank kann dies jedoch einen erheblichen Arbeitsaufwand bedeuten.

7.2 Die Abfragesprache SQL

7.2

81

Die Abfragesprache SQL

Im letzten Abschnitt haben wir uns mit dem Design von relationalen Datenbanksystemen befasst. Dabei konnten wir erkennen, dass der Entwurf von relationalen Systemen keine triviale Aufgabe ist. Schon bei einfachen Datenbankanwendungen kommt man schnell auf eine stattliche Anzahl von Relationen. In unserem Beispiel einer CDDatenbank haben wir dabei ganz auf sogenannte Service-Tabellen verzichtet, in denen mögliche Werte für häufig verwendete Attribute enthalten sind. Nachdem der Datenbankentwurf vollendet und mit den zukünftigen Anwendern gründlich diskutiert ist, besteht der nächste Schritt darin, den Datenbankentwurf zu implementieren, also ihn umzusetzen. Die hierfür notwendigen SQL-Befehle werden weiter unten behandelt. Bevor Sie eine Datenbank implementieren, sollten Sie allerdings das Handbuch des von Ihnen verwendeten Datenbank-Management-Systems zu Rate ziehen, da sich hier mitunter Datenbank spezifische Unterschiede ergeben 8 . Nun ist der Zeitpunkt gekommen, die neu erstellte Datenbank mit Leben, das heißt mit Daten zu füllen. Dabei stellt sich schnell die Frage, wie es möglich ist, die einmal eingegebenen Daten wieder sichtbar zu machen. Spätestens hier kommt die Abfragesprache SQL (sprich: Sickel) ins Spiel. Wie im weiteren Verlauf ersichtlich wird, dient SQL nicht nur zur Datenbankabfrage; gerade bei Client-Server-Systemen ist es üblich, die gesamte Datenbankerstellung über SQL zu realisieren. Noch ein Punkt soll an dieser Stelle erwähnt werden: Nicht alle gebräuchlichen Datenbank-Management-Systeme basieren auf SQL. Gerade im industriellen Bereich kommen Datenbanksysteme zum Einsatz, die grundlegend anders organisiert sind. Als Beispiel möchte ich das BerkeleyDB-Datenbanksystem erwähnen. Dieses System kann kostenlos von folgender Internetadresse bezogen werden: http://www.sleepycat.com.

7.2.1

Der SQL-Standard

Die Datenbankzugriffssprache SQL (Structured Query Language9, dt. ungefähr: Strukturierte Abfrage Sprache) wurde mehrfach normiert. Die erste Normierung fand im Jahre 1986 statt und wird als SQL-1-Norm bezeichnet. 1989 wurde diese Norm ergänzt und als Norm SQL-1+-^Soxm bezeichnet. Nach einer erheblichen Erweiterung des Funktionsumfanges entstand im Jahre 1992 die SQL-2-Norm und nur ein Jahr später wurde, nach erneuter erheblicher Funktionssteigerung, von der ISO (International Standardisation Organisation) der 5QL-3-Standard bekanntgegeben. Dieser umfasst vor allem die sogenannten objektrelationalen und postrelationalen Erweiterungen für Datenbanksysteme. Bei dem letzten der SQL-Standards handelt es sich um ein ausgesprochen umfangreiches Werk mit ca 1500 Seiten! Leider ist der SQLStandard in den einzelnen Datenbank-Management-Systemen höchst unterschiedlich umgesetzt. Microsoft Access beispielsweise setzt lediglich den SQL-1+ Standard von 1989 mit einigen Access-spezifischen Erweiterungen um. Den gleichen Standard ver8 F ü r die freien Datenbanksysteme MySQL und PostgreSQL können Sie die Handbücher, teilweise in mehreren Sprachen, aus dem Internet herunterladen. MySQL: http://www.mysql.org und PostgreSQL: http://www.postgresql.org 9 In der Literatur findet man auch des Öfteren die Umsetzung Standard Query Language für den Begriff SQL; Sie haben also die freie Wahl!

82

7 Datenbank-Programmierung mit Perl/DBI

wendet der Marktführer Oracle. Der SQL-2 Standard wurde bisher nur selten realisiert, ganz zu schweigen vom SQL-3-Standard. Das einzige Datenbank-Management-System, das den Standard von 1992 und einige Teile des 1993-Standards umsetzt, ist das freie System PostgreSQL für UNIX-Systeme, das kostenlos von folgender Webadresse bezogen werden kann: h t t p : / / w w w . p o s t g r e s g l . o r g - Eine Version für MS- Windows NT/2000/XP liegt zwar theoretisch vor, ist aber ausgesprochen schwierig einzurichten, da die Anwendung auf dem Zielrechner kompiliert werden muss. Leichter ist es, bei den genannten Betriebssystemen die frei verfügbare UNIX-Umgebung für Windows-Systeme Cygwin zu installieren, die neben einer Unzahl von nützlichen Anwendungen wie Perl und Bash auch einen PostgreSQL-Server und Client enthält 1 0 . Wenn Sie es leichter, aber nicht ganz so umfangreich haben möchten, raten wir Ihnen, das freie DatenbankSystem MySQL zu verwenden. MySQL liegt in Versionen für UNIX, MacOs und für alle Windows-Versionen vor. Besonders angenehm für Windows-Nutzer ist, dass es ein Setup-Programm gibt, mit dem die Einrichtung zu einem Kinderspiel wird 1 1 . LINUXAnwender haben in der Regel Glück, da beide Datenbank-Management-Systeme in den meisten LINUX-Distributionen enthalten sind. Die Ausführungen der folgenden Abschnitte beziehen sich auf das Datenbank-System MySQL. Da, wo sich Abweichungen zu einem PostgreSQL-System ergeben, werden wir dies gesondert vermerken 12 . Ein weiteres Problem bei der Umsetzung der Standards ist, dass fast jeder Hersteller mehr oder weniger deutlich von den SQL-Standards abweicht, indem produkt-spezifische Erweiterungen integriert werden. Diese Erweiterungen können zwar von einem gewissen Nutzen sein, aber auch zu großen Problemen führen, wenn eine Datenbank von einem System auf ein anderes portiert werden soll. Also beispielsweise dann, wenn eine AccessDatenbank im Laufe der Jahre so stark gewachsen ist, dass nun auf ein Client-ServerSystem, wie beispielsweise MySQL, ausgewichen werden soll. Wurden Access-spezifische Erweiterungen des SQL-Standards verwendet, ist eine Übertragung der Abfragen mitunter nur nach aufwändigen Anpassungen möglich 13 . Da aber SQL-Abfragen besonders dann, wenn sie sich über mehrere Relationen erstrecken, ausgesprochen kompliziert werden können, rate ich dringend davon ab, solche Erweiterungen zu verwenden.

7.2.2

Abfragen mit SQL erstellen

Nachdem wir nun den SQL-Standard kurz erläutert haben, möchten wir Ihnen im Folgenden eine Einführung in die Arbeit mit SQL-Befehlen geben. Dabei beschränken wir uns weitgehend auf die SQL-l+-Norm, also die SQL-Befehle, die in den meisten Datenbank-Management-Systemen umgesetzt sind. 10

Sie können Cygwin kostenlos unter folgender Internetadresse beziehen: h t t p : / / s o u r c e s . r e d h a t . com/cygwin. 11 MySQL kann unter der folgenden Internetadresse bezogen werden: http://www.mysql.org. Beachten Sie , dass der Einsatz von MySQL im kommerziellen Bereich nicht immer kostenlos ist! Sollten Sie mit Ihrer Anwendung unter diese Kategorie fallen, kann für einige Euro eine kommerzielle Lizenz erworben werden. 12 Der vorgestellte Stoff besitzt prinzipiell für alle derzeit verfügbaren Datenbank-ManagementSysteme Gültigkeit, da wir ausschließlich SQL-Features besprechen werden, die zum 1989-Standard gehören. 13 Wenn Sie mit Perl/DBI arbeiten möchten (wovon wir ausgehen, da Sie ansonsten diese Zeilen gar nicht lesen würden), sind Sie auf Standard-SQL angewiesen, da Perl/DBI keine produkt-spezifischen Erweiterungen unterstützt!

7.2 Die Abfragesprache SQL

83

Um die in den folgenden Abschnitten vorgestellten Beispiele nachvollziehen zu können, muss auf Ihrem System der Datenbank-Monitor gestartet sein. Auf einem LINUXSystem gehen Sie hierzu wie folgt vor. 1. Öffnen Sie ein Terminal-Fenster. 2. Geben Sie am Eingabe-Prompt den Befehl mysql an, wenn Sie mit einer MySQLDatenbank arbeiten, oder p s q l DATENBANKNAME, sofern Sie mit einer PostgreSQLDatenbank arbeiten. Falls Sie mit einem Windows-System arbeiten, müssen Sie ebenfalls zunächst ein TerminalFenster öffnen, das auf einem Windows-System MS-DOS Eingabeaufforderung oder ähnlich heißt. Falls dies nicht schon geschehen ist, starten Sie nun den MySQL-Server, mit Hilfe des folgenden Befehls, C:\mysql\bin\mysqld sofern Sie MySQL im Standard-Pfad installiert haben. Sie brauchen nun ein zweites Terminal-Fenster, da Sie das erste nicht schließen dürfen, ohne dass der MySQL-Server-Prozess beendet wird. In diesem Fenster können Sie nun den MySQL-Monitor starten. c: \mysql\bin\mysql. Wenn Sie den MySQL- oder PostgreSQL-Monitor gestartet haben, können Sie die in den folgenden Kapiteln angegebenen Beispiele wie dargestellt verwenden. Dabei können Sie beliebig viele Zeilenumbrüche eingeben. Jedes SQL-Statement wird mit einem Semikolon (;) und einem Druck auf die Eingabe-Taste abgeschlossen und ausgeführt. Bevor Sie allerdings Abfragen in einer Datenbank ausführen können, müssen Sie diese öffnen. Dies geschieht bei MySQL mit Hilfe der folgenden Befehlszeile. use CD_DATA;

7.2.3

Einfache Abfragen mit dem Befehl SELECT

Der SQL-Befehl SELECT wählt Daten aus einer Datenbank aus. Im einfachsten Fall besteht eine SELECT-Abfrage aus vier Teilen: • Dem Schlüsselwort SELECT14, • der Angabe der auszugebenden Spalten der Relation, • Dem Schlüsselwort FROM • und dem Namen einer Relation oder Abfrage, auf die sich die Abfrage beziehen soll. 14 SQL ist im Gegensatz zu Perl nicht case-sensitive, unterscheidet also nicht zwischen Groß- und Kleinschreibung. Wir schreiben in diesem Buch der Klarheit halber alle SQL-Statements groß.

84

7 Datenbank-Programmierung mit Perl/DBI

Möchte man beispielsweise alle Spalten der Relation cd_relation, die wir weiter oben als Beispiel erstellt haben, ausgeben, ist der folgende SQL-Befehl an die Datenbank zu übermitteln. SELECT * FROM cd.relation;

Dabei sind folgende Regeln zu beachten: • SQL ist eine formatfreie Sprache. Das bedeutet, anders als in vielen Programmiersprachen wie z. B. Perl und C, wird in SQL nicht zwischen der Groß- und Kleinschreibung unterschieden. Der obige Befehl ließe sich daher auch in der folgenden Art notieren. select * from cd_relation;

Es ist aber ratsam, bei der Angabe von Relations- und Attribut-Namen (wie cd_relation) die Groß- und Kleinschreibung zu beachten, da es hier bei einigen Datenbank-Management-Systemen (wie dBASE 15 ) zu Problemen kommen kann. Zudem hat es sich in den letzten Jahren eingebürgert, SQL-Anweisungen wie SELECT und FROM, zur besseren Lesbarkeit komplett in Großbuchstaben zu schreiben, wie dies auch bei HTML Skripten üblich ist. • Ein SQL-Befehl kann sich über mehrere Zeilen erstrecken, was in der Praxis übrigens fast die Regel ist. • Jeder SQL-Befehl wird mit einem Semikolon abgeschlossen. • Der Stern (*) in der obigen Abfrage dient dabei als Platzhalter für alle Spalten der Relation c d _ r e l a t i o n . Wird der obenstehende SQL-Befehl auf unsere Beispiel-Datenbank angewendet, hat dies zur Folge, dass alle Spalten aller Tupel, also die komplette Relation c d _ r e l a t i o n , ausgegeben werden. Zumeist möchte man allerdings nicht alle Spalten einer Relation in die Abfrage einbeziehen, sondern nur einige Spalten anzeigen. Möchten Sie sich beispielsweise aus der Relation r e c o r d _ l a b e l _ r e l a t i o n die Attribute rl_name und r l _ o r t anzeigen lassen, ist der SELECT-Befehl wie folgt einzugeben. 15 d B A S E zählt eigentlich nicht zu den relationalen Datenbank-Systemen, unterstützt aber eine Grundmenge an SQL-Statements. In der Industrie besitzt dieses Datenbank-System immer noch eine recht hohe Verbreitung, besonders in der Version IV. Der Autor verwendet dieses System nach wie vor gerne für kleine Projekte.

85

7.2 Die Abfragesprache SQL SELECT rl.name, rl_ort FROM record_label_relation;

Es müssen also unmittelbar nach dem Schlüsselwort SELECT die Attribute (Spalten) durch Kommas getrennt angegeben werden, die im Ergebnis angezeigt werden sollen. Bei der Ausgabe werden die Attributsnamen als Spaltenüberschriften angezeigt. Eine SQL-Ausgabe in MySQL könnte daher ungefähr wie folgt aussehen. I Record_Label_Name I Record_Label_Ort | I Virgin I Hopsing

I London I Peking

I I

Manchmal kann es vorkommen, dass man nicht die Attributsnamen als Spaltenbeschriftung der Ausgabe sondern kürzere oder prägnantere Namen wünscht. Auch dies lässt sich mit Hilfe des SELECT-Befehls bewirken. SELECT rl.name AS Label, rl.ort AS Ort FROM record_label_relation;

Diese neuen Namen werden als AZias-Namen bezeichnet. Möchte man während der folgenden Arbeit auf die Ergebnismenge, das sogenannte Dynaset, zugreifen, beispielsweise von einem Perl-Skript oder einer anderen Abfrage aus, hat dies über den Alias-Namen zu geschehen. In allen bisherigen Beispielen wurde jeweils der gesamte Tabellen-Inhalt ausgegeben. Dies kann zur Folge haben, dass im Ergebnis mitunter recht lange Listen, von manchmal mehreren tausend Einträgen entstehen. Da die einzelnen Tupel in der Reihenfolge ausgegeben werden, in der sie in der Relation gespeichert sind 16 , kann das Suchen eines bestimmten Eintrages recht aufwändig sein. Hier wäre es hilfreich, die Ergebnismenge nach bestimmten Attributen zu sortieren. Genau das lässt sich mit dem Befehl ORDER BY bewirken. SELECT rl.name, rl_ort FROM record_label_relation ORDER BY rl.name;

Im obigen Beispiel wird die Ergebnismenge aufsteigend, alphabetisch nach dem Feldinhalt der Spalte rl_name sortiert, ausgegeben. Wünscht man hingegen eine absteigende Sortierung, so ist dies mit dem Schlüsselwort DESC ausdrücklich anzugeben, wie folgendes Beispiel zeigt. 16

U n d das ist in der Regel in der Reihenfolge der Eingabe oder gar völlig willkürlich.

86

7 Datenbank-Programmierung mit Perl/DBI

SELECT rl.name, rl.ort FROM record_label_relation ORDER BY rl_name DESC;

Zudem ist es möglich, die Sortierung auf mehrere Spalten auszudehnen. Stellen Sie sich beispielsweise vor, Sie möchten aus einer Adressdatenbank alle Adressen ausgedruckt bekommen. Dabei möchten Sie nicht nur nach dem Feld name sortieren, sondern auch nach dem Feld vorname, damit im Ergebnis eine "Duck Daisy" vor einem "Duck Donald" steht. Geben Sie hierzu den folgenden SELECT-Befehl ein.

SELECT name, vorname FROM adressen ORDER BY name, vorname;

Die ORDER BY-Klausel wird dabei von links nach rechts ausgewertet. Das bedeutet, das Ergebnisset wird zunächst nach dem Feld Name sortiert, "Maus" steht also hinter "Duck", aber vor "Valiant" 17 . Gibt es in einer Datenbank mehrere Personen mit dem Namen "Duck", werden diese nun erneut nach dem Feld Vorname sortiert, also "Duck Daisy" wird vor "Duck Donald" angezeigt. Auch wenn Sie nach mehreren Feldern sortieren, können Sie für jedes Feld nach dem Feldnamen die Sortier-Richtung bestimmen. Möchten Sie im obigen Beispiel die Namen absteigend, aber die Vornamen aufsteigend sortieren, ist der folgende SQL-Begriff zu verwenden.

SELECT name, vorname FROM adressen ORDER BY name DESC, vorname;

7.2.4

Abfrageeinschränkung mit

WHERE

In den vorhergehenden Beispielen haben wir jeweils den gesamten Tabelleninhalt abgefragt und ausgegeben. In den weitaus meisten Situationen möchten Sie aber nur eine Teilmenge des Datenbestandes einer Tabelle im Dynaset enthalten sehen. So möchten Sie etwa aus der c d _ r e l a t i o n nur diejenigen Einträge sehen, bei denen im Attribut c d _ e r s c h _ j a h r der Wert 2002 eingetragen ist. Um Abfragen auf diese Weise einzuschränken, verwenden Sie den Befehl WHERE. Eine derartige Abfrage könnte wie die folgende aussehen. 17

Für nicht Comic-Fans: Prince Valiant ist der englische Original-Name von Prinz

Eisenherz.

7.2 Die Abfragesprache SQL

87

SELECT cd_titel, cd_ersch_jahr FROM cd_relation WHERE cd_ersch_jahr = 2002;

Beachten Sie dabei, dass Zeichenketten (Strings) anders als Zahlen in einfachen Hochkommas eingeschlossen werden müssen. Bei Microsoft Access ist es auch möglich, stattdessen Anführungszeichen (") zu verwenden. Schauen wir uns im nächsten Beispiel eine Abfrage an, bei der nicht nach Gleichheit, sondern nach größer als gesucht wird. Wir möchten aus der CD-Datenbank alle RecordLabels herausfiltern, bei denen im Feld r l _ p l z ein Wert steht, der größer als 80000 ist. SELECT rl_name, rl_adresse, rl_plz, rl_ort FR0M record_label_relation WHERE rl.plz > 80000;

Beachten Sie bitte: Wird in Abfragen nach Werten in Zahlenfeldern gesucht, dürfen die Zahlen nicht in Anführungszeichen oder Hochkommas stehen. Im vorangehenden Beispiel ist zudem zu bedenken, dass ein Datensatz, der genau den Wert '80000' enthält, nicht gefunden würde, da nur Datensätze, bei denen die Postleitzahl größer als 80000 ist, in das Dynaset aufgenommen werden 18 . Manchmal ist es aber notwendig, mehrere Bedingungen miteinander zu verknüpfen. Nehmen wir an, Sie möchten alle Adressen von Plattenfirmen ausgeben, die ihren Firmensitz im Postleitzahlenbereich von 80000 (einschließlich) bis 89999 haben. Um dies zu erreichen, müssen Sie zwei Bedingungen miteinander verknüpfen: Die Postleitzahl muss größer oder gleich 80000 und gleichzeitig kleiner als 90000 sein. In SQL sieht die entsprechende Abfrage folgendermaßen aus. SELECT rl_name, rl_adresse, rl_plz, rl_ort rl_name, rl_adresse, rl_plz, rl_ort FR0M record_label_relation WHERE rl_plz >= 80000 AND rl_plz < 90000;

Neben der vorgestellten AND-Verknüpfung beherrscht SQL auch die aus Perl bekannte 0R-Verknüpfung. Im folgenden Beispiel werden alle Plattenfirmen angezeigt, die Ihren Sitz in München oder Landshut haben. 18

Zu den logischen Operatoren von SQL schlagen Sie bitte weiter unten nach.

88

7 Datenbank-Programmierung mit Perl/DBI

SELECT Name, Vorname, Anrede, Stra"se, PLZ, Ort rl_name, rl_adresse, rl_plz, rl_ort FROM record_label_relation WHERE rl_ort = 'M"unchen' OR rl_ort = 'Landshut';

7.2.5

Die Vergleichs-Operatoren von SQL

In den oben genannten Beispielen haben wir in den Bedingungsklauseln sogenannte Vergleichs-Operatoren verwendet. Vergleichs-Operatoren dienen dazu, einen Ausdruck auf seine Wahrheit zu testen. Alle Ausdrücke, die auf Wahrheit testen, geben einen sogenannten booleschen Wahrheitswert zurück. Die booleschen Wahrheitswerte sind entweder true (wahr), das heißt, der Ausdruck ist wahr, oder false (falsch), das heißt der Ausdruck ist nicht wahr. In der folgenden Tabelle haben wir die wichtigsten VergleichsOperatoren und Ihre Bedeutung zusammengestellt.

Vergleichsoperator =

> >=

< = und M_ier';

Dabei steht das Zeichen " _ " (Unterstrich) für genau ein beliebiges Zeichen. Manchmal kann es allerdings vorkommen, dass Sie nicht wissen, wieviele Zeichen "beliebig" sein sollen. Nehmen Sie einmal an, Sie möchten alle Adressen finden, bei denen im Ortsnamen die Zeichenkette »hall« vorkommt 19 . Verwenden Sie in SQL folgende Abfrage. SELECT name, vorname, einrede, strasse, plz, ort FROM adressen WHERE ort LIKE ''/.hall'/.';

Dieser Ausdruck findet alle Ortsnamen, die mit beliebig vielen Zeichen beginnen, anschließend die Zeichenfolge "hall" enthalten und schließlich mit beliebig vielen Zeichen enden. Einfach ausgedrückt, werden alle Ortsnamen gefunden, die an beliebiger Stelle die Zeichenfolge "hall" enthalten. lg "Hall" ist d a s keltische W o r t für Salz. W e n n Sie e i n e n Ort finden, der d i e S i l b e Hall in s e i n e m N a m e n t r ä g t (z. B. H a l l s t a t t , B a d R e i c h e n h a l l o d e r S c h w ä b i s c h H a l l ) , d a n n w i s s e n Sie, d a s s a n d i e s e m O r t in keltischer Zeit (ca. 6 0 0 v. Chr. bis ca. 15 v . C h r . ) S a l z a b b a u s t a t t g e f u n d e n h a t .

90

7 Datenbank-Programmierung mit Perl/DBI

Beachten Sie bitte, dass bei einigen PC-Datenbanken, wie MS-Access, anstelle des Operators '/, das Zeichen * und anstelle von _ das Zeichen ? verwendet werden muss! Schließlich möchten wir Ihnen noch den Operator NULL erläutern. NULL testet, ob in einem Datenfeld überhaupt ein Wert eingetragen wurde. Um beispielsweise alle Adressen zu finden, bei denen Ihnen kein Vornamen bekannt ist, verwenden Sie den folgenden SQL-Begriff.

SELECT name, vorname, anrede, strasse, plz, ort FROM Adressen WHERE vorname IS NULL;

Alle genannten Operatoren lassen sich mit dem Operator NOT negieren. Das bedeutet, IS NOT NULL ergibt im Gegensatz zu IS NULL nur dann true, wenn im betreffenden Feld Werte eingetragen sind. Die entsprechende Abfrage wird also in Ihr Gegenteil gewendet.

7.2.6

Die Verknüpfung von Relationen (JOIN)

In unseren bisherigen Beispielen haben sich die Abfragen immer auf eine einzelne Tabelle bezogen. Die Stärke von relationalen Datenbanken liegt aber gerade in der Möglichkeit, die Informationen auf mehrere Relationen zu verteilen. Diese Datenverteilung macht jedoch nur dann Sinn, wenn es auch möglich ist, die Daten aus mehreren Tabellen verknüpft abzufragen. Zu diesem Zweck kennt SQL die sogenannten Joins oder Verknüpfungen, die im Folgenden erläutert werden sollen. Die Verknüpfungen werden dabei immer über die WHERE-Klausel aufgebaut. Natural Join. Der sogenannte Natural Join ist die einfachste Art, zwei oder mehrere Tabellen miteinander zu verknüpfen. Eine einfache Verknüpfung zweier Tabellen besitzt folgende Form.

SELECT cd_titel, song_name, song_laenge FROM cd_relation, song_relation WHERE cd_relation.cd_id = song_relation.cd_id;

Im obigen Beispiel konnten bei der Aufzählung der Spalten die Tabellennamen fortgelassen werden, da sie nur bei nicht eindeutigen Spaltennamen angegeben werden müssen. Die allgemeine Form lautet daher.

SELECT relation.attribut, relation.attribut, relation.attribut FROM relation, relation, ... WHERE relation.attribut = relation.attribut;

7.2 Die Abfragesprache SQL

91

Die Verknüpfung von zwei oder mehreren Relationen mit Hilfe des Natural Join ist zwar sehr einfach anzuwenden, besitzt aber auch eine Reihe von Nachteilen. Der wohl entscheidenste Nachteil ist, dass diese Verknüpfungsmethode als Ergebnis einen sogenannten Snapshot und kein Dynaset liefert. Ein Snapshot ist eine sogenannte Menge, das heißt, Datensätze aus einem Snapshot können nicht verändert werden. Dies kann natürlich bei Abfragen, die nur der Information oder dem Ausdruck dienen, von Vorteil sein, da der Umgang mit Snapshots erheblich schneller ist als der mit Dynasets. Ein Dynaset ist eine dynamische Menge, was bedeutet, dass alle Daten auch geändert werden können. I N N E R J O I N . Wie wir oben gesehen haben, lässt sich mit einem Natural Join nur ein Snapshot erzeugen, um ein Dynaset zu erzeugen, muss ein expliziter JOIN-Befehl verwendet werden. Die häufigste Verknüpfungsmethode von zwei oder mehr Tabellen ist der sogenannte INNER JOIN. Mit diesem wird nach Möglichkeit ein Dynaset erstellt. Einen INNER JOIN erzeugen Sie folgendermaßen.

SELECT cd_titel, song_name, song_laenge FROM cd.relation INNER JOIN song.relation ON cd_relation.cd_id = song_relation.cd_id;

Beim INNER JOIN werden nur Tupel ausgewählt, bei denen die im ON-Teil angegebenen Attribute übereinstimmende Werte aufweisen. In unserem Beispiel also werden alle Songs aufgelistet und mit dem zugehörigen CD-Titel angegeben. Im folgenden Beispiel stellen wir alle CD-Titel mit zugehörigen Liedern da, die von der Sängerin "Carla Bruni" aufgenommen wurden.

SELECT kuenstler_name, cd_titel, cd_ersch_jahr, song_name FROM kuenstler.relation INNER JOIN cd.relation ON kuenstler.relation.kuenstler_id = cd_relation.kuenstler_id INNER JOIN song_relation ON cd_relation.cd_id = song_relation.cd_id WHERE kuenstler_name = 'Caria Bruni';

Wie Sie sehen, benötigen wir bei dieser Abfrage einen zweifachen INNER JOIN, da an der Abfrage insgesamt drei Relationen beteiligt sind. Die Ausgabe können Sie in der zugehörigen Abbildung betrachten. O U T E R J O I N . Neben dem INNER JOIN gibt es noch die Möglichkeit der äußeren Verknüpfung (OUTER JOIN). Der Unterschied zur inneren Verknüpfung liegt darin, dass bei der äußeren Verknüpfung alle Datensätze der einen Relation angezeigt werden und nur die Datensätze der zweiten Relation angezeigt werden, bei denen das Verknüpfungsattribut übereinstimmt. Beim OUTER JOIN lassen sich zwei Spielarten unterscheiden.

92

7 Datenbank-Programmierung mit Perl/DBI helmuffi nb Miiiel." Date!

gearbeiten

Ansicht

lerminal

£ehe zu

Hilfe

mysql> use CD_DATA Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> SELECT kuenstler_name, cdLtitel, cd_ersch_Jahr, song_name -> FROM kuenstler_relation INNER JOIN -> cd_relation ON kuenstler_relation.kuenstler_id = -> cd_relation.kuenstler._id INNER JOIN -> song_relation ON ccLrelation.ccLid = -> song_relation.cd_id -> WHERE kuenstler_name = 'Carla Bruni'; kuenstler_name

cd_titel

Carla Carla Carla Carla Carla

quelqu quelqu quelqu quelqu quelqu

Bruni Bruni Bruni Bruni Bruni

un un un un un

ccLersch_jahr m m M m m

a a a a a

dit dit dit dit dit

2003 2003 2003 2003 2003

song_nane Quelqu un n a dit Raphael Tout le monde La noy ee Le toi du noi

5 rows in set (0.01 sec) |TCYsql>

d

Abb. 7.4•' Alle Titel der Sängerin Carla Bruni

LEFT JOIN: Es werden alle Datensätze der Tabelle links vom Befehlswort LEFT JOIN ermittelt und zusätzlich aus der rechten Tabelle nur die, die eine Entsprechung auf der linken Seite haben. SELECT cd_titel, song_name, song_laenge FROM cd.relation LEFT JOIN song.relation ON cd_relation.cd_id = song_relation.cd_id;

RIGHT JOIN: Hier werden alle Zeilen der rechten Tabelle und nur die verknüpften der linken Tabelle angezeigt. RIGHT JOIN ist also der Gegenbefehl zu LEFT JOIN.

7.2.7

Funktionen

Standard-SQL kennt fünf eingebaute Standardfunktionen, die als Aggregatfunktionen bezeichnet werden: Mittelwert (AVG20), Anzahl (COUNT), Summe (SUM), Maximalwert (MAX) und Minimalwert (MIN). Um beispielsweise die Anzahl aller CD-Titel, die in der Datenbank gespeichert sind, zu erhalten, geben Sie die folgende SELECT-Klausel ein. SELECT COUNT(cd.titel) AS AnzahlTitel FROM cd_relation; °Im Englischen bedeutet average soviel wie Durchschnitt oder Mittelwert.

93

7.2 Die Abfragesprache SQL

Im obigen Beispiel wurde als Parameter der Funktion ein Spaltennamen verwendet. Sie können aber als Parameter auch einen beliebigen Ausdruck verwenden 21 . Nehmen wir an, Sie möchten den Wert Ihres Lagerbestandes an CDs ermitteln. In der cd relation haben Sie im Feld ek_preis den Netto-Einkaufspreis (ohne Mehrwertsteuer) eingepflegt, ausgegeben werden soll aber die Summe der Preise inklusive Mehrwertsteuer. Führen Sie hierzu den folgenden SQL-Befehl aus. SELECT SUM(ek_preis * FROM

1.16)

cd_relation;

Mit Hilfe von Funktionen ist es auch möglich, neue Spalten zu erzeugen. Diese Spalten existieren nur in der Abfrage und haben keine Rückwirkung auf die zugrundeliegende Datenbank. Wenn Sie beispielsweise aus einer Relation die Mitarbeiterdaten speichert, eine Liste erzeugen möchten, in der die wichtigsten Mitarbeiterdaten sowie das Jahreseinkommen ausgegeben werden, wobei das Jahreseinkommen aus 12 Monatsgehältern und einem Weihnachtsgeld in Höhe von 500 Euro errechnet wird. In der Tabelle selbst ist allerdings lediglich das Monatsgehalt eingepflegt. Eine Lösung bietet der folgende SQL-Befehl.

SELECT persnr, name, FROM

12 * g e h a l t + 5 0 0 A S

Jahreseinkommen

personal;

7.2.8

Datenbanken mit SQL-Befehlen pflegen

Bisher haben wir SQL-Befehle lediglich dazu verwendet, Daten aus Datenbanken abzufragen. Mit Hilfe von SQL ist es aber auch möglich, Datenbanken und Relationen zu erstellen oder zu löschen, Datensätze einzugeben, zu ändern und zu löschen. Diese Möglichkeiten sollen im Folgenden kurz beschrieben werden. Sofern Sie mit DatenbankManagement-Systemen wie MySQL oder PostgreSQL arbeiten, sind Sie sogar auf die im Folgenden beschriebenen Befehle angewiesen 22 . Datenbanken erstellen und löschen. Um eine Datenbank erstellen zu können, ist es notwendig, dass der Datenbankserver-Prozess aktiv ist. Da die Syntax zur Erstellung von Datenbanken von einem Datenbank-Management-System zum anderen leicht unterschiedlich sein kann, beziehen sich alle folgenden Beschreibungen auf das DatenbankSystem MySQL. 21

solange er syntaktisch korrekt ist Oder Sie verwenden Tools wie das kostenlose phpMyAdmin, das auf der Webseite http://www.phpmyadmin.net heruntergeladen werden kann. Auf Windows-Systemen kann der Versuch, diese Anwendung zu installieren, leicht zu einem Alptraum werden, da es notwendig ist, eine funktionierende php-Installation auf Ihrem System zu besitzen. Diese wiederum setzt einen aktiven Web-Server wie Apache voraus ... 22

94

7 Datenbank-Programmierung mit Perl/DBI

Um den Datenbankserver zu starten geben Sie in der Regel in einer Befehlskonsole (bei Windows ein MS-Dos-Fenster, bei LINUX ein Terminal-Fenster) den folgenden Befehl ein. mysqld Nachdem der Server aktiv ist, können Sie in einem zweiten Konsolen-Fenster mit einer Datenbank kommunizieren. Bei MySQL arbeiten Sie hierzu mit dem Programm mysql. Um eine neue Datenbank anzulegen, geben Sie den folgenden Befehl ein. mysql CREATE l i t e r a t u r Um eine Datenbank zu löschen verwenden Sie den Befehl, mysql DROP l i t e r a t u r Natürlich wird die Datenbank nicht einfach gelöscht, zunächst wird eine Warnung ausgegeben, aber dann ...! Um mit MySQL zu arbeiten, benötigen Sie übrigens die entsprechenden Rechte. Das bedeutet, nicht jeder x-beliebige Anwender hat die Möglichkeit, Datenbanken anzulegen oder zu löschen 23 . Damit ein Benutzer "heinz" auf einem LINUX-System das Recht erhält, Datenbanken anzulegen und wieder zu löschen, geben Sie in einem Root-Terminal-Fenster in den mysql-Monitor die folgende Anweisung ein.

GRANT ALL on * . * TO heinz;

Die meisten Datenbank-Management-Systeme besitzen übrigens ein ausgeklügeltes Sicherheitssystem, mit dem genau festgelegt werden kann, wer was an einer Datenbank ändern darf. Das gilt übrigens auch für das Anlegen und Ändern von Relationen und Daten. Tabellen erstellen und löschen. Zum Erstellen von Datenbanktabellen wird der SQL-Befehl CREATE TABLE verwendet. Voraussetzung hierfür ist bei Client-Server-Datenbanken, dass der Datenbankserver läuft, und dass Sie die entsprechenden User-Rechte auf die Datenbank besitzen. Um in einer Datenbank eine Tabelle anzulegen, müssen Sie zunächst die Datenbank öffnen. Dies geschieht wiederum in einer Befehlskonsole. Bei MySQL geben Sie hierzu den folgenden Befehl ein. mysql CD.DATA Nach dem Betätigen der Eingabe-Taste erscheint der MySQL-Eingabe-Prompt des Befehlsmonitors. mysql > 23

Dies gilt allerdings nur für Anwender von LINUX- und UNIX-Systemen. Nutzer von WindowsSystemen besitzen in der Regel Administrator-Rechte, das heißt, hier hat tatsächlich jeder x-beliebige User das Recht, Datenbanken zu löschen!

7.2 Die Abfragesprache SQL

95

Nun können Sie die Befehle zum Erstellen von Tabellen eingeben. Um in der Datenbank CD_DATA die Tabelle song_relation anzulegen, geben Sie die folgende Zeichenfolge in den Befehlsmonitor ein. CREATE TABLE song_relation song.id INT NOT NULL, cd_id INT, song.name VARCHAR(IOO), song.laenge VARCHAR(20), PRIMARY KEY(song.id) );

Mit dem obenstehenden Befehl haben Sie eine neue Datentabelle in Ihre CD-Datenbank eingefügt. Die Tabelle hat vier Attribute: song_id (als Primärschlüssel), cd_id, song_name u n d song_laenge.

Falls Sie die soeben angelegte Tabelle wieder löschen möchten, verwenden Sie den Befehl DROP TABLE. DROP TABLE song.relation;

Mit Hilfe dieses Befehls wird die Tabelle unwiderruflich aus der Datenbank gelöscht! Werte in Tabellen eintragen und löschen. Wenn Sie in die soeben erstellte Tabelle Daten eingeben möchten (ich hoffe Sie haben die Tabelle nicht wirklich gelöscht), dann geschieht auch dies mit Hilfe von SQL-Anweisungen. Mit dem folgenden Befehl fügen Sie einen Datensatz in die Tabelle song_relation ein. INSERT INTO song.relation (song_id, cd_id, song_name, song_laenge) VALUES (1, 4, 'Mama', '2:35');

Nachdem Sie die obenstehende Befehlsfolge in den MySQL-Monitor eingegeben haben, wird der neue Datensatz in die Datenbank aufgenommen. Um sich davon zu überzeugen, geben sie den folgenden "herkömmlichen" SQL-Befehl in den Monitor ein. SELECT * FROM song_relation

Am Bildschirm wird der soeben eingetragene und einzige Datensatz unserer Tabelle angezeigt. Um den frisch angelegten Datensatz wieder aus der Datenbank zu entfernen, verwenden Sie den Befehl DELETE. DELETE FROM song.relation WHERE song_name = 'Mama';

7 Datenbank-Programmierung mit Perl/DBI

96

Achtung! Wie Ihnen sicher gleich aufgefallen ist, werden mit diesem Befehl alle Datensätze gelöscht, bei denen der Name des Songs "Mama" heißt. In unserem Beispiel ist dies allerdings unerheblich, da nur ein Datensatz dieses Titels vorhanden ist. In der Realität sollten Sie allerdings bei Löschabfragen erheblich vorsichtiger zu Werke gehen. Ein Datenbankprofi würde zunächst eine 'normale' SQL-Abfrage durchführen, um die Wirkung der WHERE-Klausel zu testen. Erst nachdem man sicher ist, dass wirklich nur die gewünschten Datensätze gelöscht werden, wird die eigentliche DELETE- Abfrage durchgeführt. Im Übrigen sei darauf hingewiesen, dass in einer gut programmierten Datenbank eigentlich ohnehin keine Datensätze gelöscht werden sollten! Datensätze ändern. Es kann durchaus vorkommen, dass Sie einen Datensatz abändern müssen ohne ihn dabei zu löschen. Beispielsweise, wenn Sie feststellen, dass der Heintje-Klassiker nicht einfach "Mama", sondern "Mama, du sollst doch nicht um deinen Jungen weinen" heißt. Verwenden Sie hierzu das folgende SQL-Statement. UPDATE song.relation SET song_name = ' Mama, du sollst doch nicht um deinen Jungen weinen' WHERE song_name = 'Mama';

Wie Sie sehen, verwenden wir zum Ändern von Datensätzen die beiden SQL-Befehle UPDATE u n d SET.

Nun haben wir Ihnen die Grundlagen der Abfragesprache erläutert, die notwendig sind, um mit Hilfe von Perl und DBI auf Datenbanken zugreifen zu können. In den folgenden Abschnitten können wir uns nun auf die Programmierung von Datenbanken konzentrieren.

7.3

Die Programmierung der Datenbank-Schnittstelle Perl/DBI

Wie bereits im Vorwort zu unserem Datenbank-Kapitel gesagt, verfügt Perl über eine sehr schnelle, komfortable und gut abstrahierte Datenbankschnittstelle, die Perl/DBI genannt wird. Die Abkürzung DBI steht für den englischen Begriff DataBase Interface. Die DBI-Schnittstelle wurde von Tim Bunce geschrieben und der Perl-Gemeinschaft unter den gleichen Bedingungen wie Perl selbst zur Verfügung gestellt. Neben der Fähigkeit von Perl, besonders gut mit CGI24 zusammenarbeiten zu können, ist sicherlich DBI einer der Gründe, warum Perl mitunter als 11 der Klebstoff" bezeichnet wird, " der das Internet zusammenhält".

7.3.1

Das Konzept von Perl/DBI

Die meisten Datenbank-Systeme, die sich derzeit auf dem Markt befinden, sind Relationale Datenbanksysteme und bedienen sich der Abfragesprache SQL zur Abfrage und 24

Englisch für Common

Gateway

Interface.

7.3 Die Programmierung der Datenbank-Schnittstelle Perl/DBI

97

Pflege der Daten. Da SQL, wie wir bereits gesehen haben, standardisiert ist, scheint es auf den ersten Blick eine leicht zu lösenden Aufgabe zu sein, eine allgemeingültige Datenbankschnittstelle zu entwickeln, um mit Hilfe von Perl auf Datenbanken zugreifen zu können. Doch dies scheint eben wirklich nur auf den ersten Blick so zu sein. Bei genauer Betrachtung tun sich vor dem Entwickler "wahre Gräben" auf. So ist, wie wir weiter oben gesehen haben, SQL mehrfach standardisiert worden und kaum ein Datenbank-System unterstützt den gesamten SQL-Standard. Fast jeder Hersteller lockt zudem mögliche Kunden mit speziellen Erweiterungen zu diesem Standard. An anderen Stellen weichen Hersteller vom Standard ab. So wird beispielsweise der Standard-Wildcard-Operator'/. in MS-Access durch den Operator * ersetzt. Als würden sich durch diese babylonische Sprachverwirrung nicht schon genügend Probleme auftun, hat sich Tim Bunce auch noch zum Ziel gesetzt, eine Möglichkeit zu schaffen, mit Hilfe von Perl und SQL auf "Datenbank-Systeme" zugreifen zu können, die weder relational organisiert, noch in der Lage sind, SQL-Anweisungen zu verstehen. Ein Beispiel hierfür sind die sogenannten CS K 25 -Dateien. Bei CSV-Dateien handelt es sich um reine Text-Files, bei denen die einzelnen Attributswerte durch ein bestimmtes Textzeichen, wie beispielsweise ein Komma oder Semikolon, voneinander getrennt sind. Perl erreicht diese Flexibilität dadurch, dass eine vierschichtige Architektur eingesetzt wird, bei der zwischen die Schnittstelle zur Programmiersprache Perl (DBI) und das Datenbank-Management-System eine Treiberschicht eingeschoben wird. Diese Treiber werden DBD genannt, was für den englischen Begriff DataBase Driver (zu Deutsch Datenbank Treiber) steht. Die Architektur stellt sich wie in der Abbildung 7.5 dar.

Abb.

7.5:

Die Datenbankarchitektur

von

Perl/DBI

Diese Treiber übernehmen die Aufgabe, die SQL-Anweisungen, die von Perl/DBI an die Datenbank abgesendet werden, in eine für das jeweilige System verständliche Form zu übersetzen, wie dies in der Abbildung 7.6 dargestellt ist. Bei "Datenbank-Systemen", wie beispielsweise CSV-Dateien, die nicht in der Lage sind, SQL-Anweisungen zu verstehen, werden diese vom DBD-Modul in für das entsprechende System verständliche Anweisungen übersetzt. 25 D i e englische Abkürzung CSV Kommata getrennte Werte.

steht für Comma

Separated

Values,

zu deutsch ungefähr durch

98

7 Datenbank-Programmierung mit Perl/DBI

Abb.

7.6: Das Treibersystem

von

Perl/DBI

Möchten Sie das Datenbank-System, das von Ihrer Anwendung angesprochen werden soll, wechseln, sind hierfür lediglich zwei Schritte notwendig: • Den DBD-Treiber für das Datenbank-System aus dem CPAN herunterladen und installieren. • Im Programmcode die Zeile ändern, in welcher der Datenbank-Treiber angegeben wird oder, falls Sie eine Initialisierungsdatei verwenden, die entsprechende Zeile in dieser Datei ändern. Und schon arbeitet Ihre Anwendung mit einem anderen Datenbank-Server 26 .

7.4

Arbeiten mit DBI/DBD

Wir möchten nun, nachdem wir Ihnen einiges über Datenbanken und die Perl-Datenbankschnittstelle erklärt haben, Perl/DBI einsetzen. Wir gehen davon aus, dass Sie das Modul DBI sowie die von Ihnen benötigten DBD-Treiber auf Ihrem System eingerichtet haben. Zudem muss auf Ihrem System ein Datenbankserver wie MySQL oder PostgreSQL gestartet sein. Ist eine der Bedingungen nicht erfüllt, lesen Sie bitte den entsprechenden Abschnitt, weiter oben in diesem Kapitel. Beachten Sie zudem, dass auf Ihrem System die vorgestellte CD-Datenbank CD DATA eingerichtet sein muss. In der Datenbank sollten selbstverständlich auch einige Tupel angelegt sein.

7.4.1

Vorhandene Datenbank-Treiber abfragen

Um mit Perl/DBI auf eine Datenbank zugreifen zu können, müssen Sie zunächst das DBI-Modul, wie im Kapitel Module in Perl beschrieben, in Ihre Anwendung einbinden.

26

Eine andere Sache ist natürlich der Aufwand, den Sie betreiben müssen, um die Datenbank selber von einem in das andere System zu übertragen. Aber auch hier hilft Ihnen Perl mit dem Modul: SQL::Translator, das genau diese Aufgabe für Sie übernimmt.

7.4 Arbeiten mit DBI/DBD

99

#!/usr/bin/perl -w # Einbinden des DBI-Moduls use DBI;

Wir können nun abfragen, welche Perl-Datenbank-Treiber auf Ihrem System vorhanden sind. #!/usr/bin/perl -w # dbd_treiber.pl # Ermitteln der auf dem System vorhandenen DBD-Triber-Module # Einbinden des DBI-Moduls use DBI; # installierte Datenbank-Treiber ermitteln ... m y Streiber = DBI->available_drivers(); # ... u n d ausgeben print "Auf Ihren System sind die folgenden DBD-Module foreach my $treiber(@treiber){ print "$treiber\n";

installierten";

> Nachdem Sie das Skript abgetippt und ausgeführt haben, wird einen Liste der auf Ihrem System installierten DBD-Treiber-Module ausgegeben.

7.4.2

Verbindung zu einer Datenbank aufbauen

Nachdem wir nun wissen, dass der für den Zugriff auf das gewünschte Datenbanksystem benötigte Treiber (hoffentlich) installiert ist, die gewünschte Datenbank vorhanden und mit den notwendigen Zugriffsrechten versehen ist, können wir die Verbindung zur Datenbank aufbauen. Hierfür benötigen wir ein Connection-Objekt27. Dieses erstellen wir, indem wir die Methode connectO der DBI-Klasse aufrufen und die benötigten Parameter übergeben. Dies sind im Einzelnen: • Der zu verwendende Treiber. • Der Name der zu öffnenden Datenbank. 27

Sie haben sicher bereits gemerkt, dass es sich bei dem Perl-DBI-Modul um ein Objektorientiertes Modul handelt!

7 Datenbank-Programmierung mit P e r l / D B I

100

helrnul-' nb sti.-Jfci: •,perl. bu< h/dbi examples ßatel

Bearbeiten

Ansicht

Terminal

Gehe zu

hyffe

[helnutQnb-seidel helnut]$ c d perl, büch [helmutCnb-seidel perl_buch]$ cd dbi/examples/ [helmutönb-seide1 exaniples]$ perl dbd_treiber.pl Auf Ihren System sind die folgenden DBD-Module installiert: ExampleP Pg Proxy niysql [helnutCnb-seidel exaniples]S

Abb. 7 . 7 : Ausgabe des Skriptes

|

dbd_treiber.pl

• Der Name des Datenbank-Users, der die notwendigen Zugriffsrechte auf die Datenbank besitzt. Dies muss nicht der Name des am System angemeldeten Users sein (dies ist vor allem für Linux-Anwender von Bedeutung). • Das Passwort des Anwenders, der die Datenbank öffnen möchte. Aus diesen Angaben wird der sogenannte Connection-String erstellt, welci dem Datenbank-Treiber übergeben wird. Das folgende Skript baut eine Verbindung zu unserer CD-Datenbank auf.

#!/usr/bin/perl -w #connect.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Parameter fuer Datenbankverbindung my $treiber = 'mysql'; my $datenbank = 'CD.DATA'; my $anwender = 'helmut'; my $passwort = ''; # Verbindung erstellen oder Programm beenden

7.4 Arbeiten mit DBI/DBD

101

my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $pas swort) or die ("Datenbankverbindung kann nicht erstellt werden: $!"); print "Datenbankverbindung zu $datenbank erfolgreich erstellt!\n"; # . . .

Wir haben in unserem Skript zunächst die Daten, die den Connection-String bilden, in lokalen Variablen hinterlegt. Dies sollten Sie, obwohl es etwas weitschweifig erscheint, stets tun. Der größte Vorteil dieses Vorgehens liegt darin, dass die Anwendung leicht zu pflegen ist. Soll beispielsweise der Datenbank-Treiber ausgewechselt werden, ist lediglich der Wert der entsprechenden Variablen zu ändern. Um Datenbank-Anwendungen vom verwendeten System gänzlich unabhängig zu machen und um zu verhindern, dass bei jeder derartigen Änderung der Programmcode angepasst werden muss 28 , ist es sinnvoll, die genannten Verbindungsdaten aus einer externen Text-Datei einzulesen. Der Anwender muss nun lediglich in dieser Datei die entsprechenden Änderungen vornehmen; die Programmdatei selbst bleibt unangetastet. Ändern Sie also den Programmcode in der folgenden Weise ab. #!/usr/bin/perl -w # connect2.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort) or die ("Datenbankverbindung kann nicht erstellt werden: $!"); print "Datenbankverbindung zu $datenbank erfolgreich erstellt!\n";

Diese Programmversion setzt voraus, dass sich im selben Verzeichnis, in dem das Skript liegt, auch die Initialisierungsdatei "verbdat.txt" befindet. Diese muss folgendes Aussehen besitzen. 28

W a s erhebliche Risiken für die Integrität der Anwendung in sich birgt!

7 Datenbank-Programmierung mit Perl/DBI

102

mysql CD.DATA helmut

Nach dem Eintrag des User-Namens sollte noch eine Leerzeile für das Passwort vorhanden sein, falls Ihre Datenbank kein Passwort benötigt. Es ist zudem eine gute Idee, noch einige "Kommentarzeilen" anzufügen, um dem Anwender die Handhabung dieser Datei zu erklären. Besondere Beachtung verdient zudem der Parameter $datenbank. So reicht es bei MySQL-Datenbanken aus, den Connection-String in der folgenden Form zu übergeben. my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort);

Bei einigen DBD-Modulen, wie zum Beispiel dem DBI :PG-Treiber, der zur Verbindung mit PostgreSQL-Datenbanken benötigt wird, muss allerdings die folgende allgemeine Form verwendet werden. my $dbh = DBI->connect("DBI:$treiber:database=$datenbank", $anwender, $passwort);

Um diesem Umstand gerecht zu werden, sind zwei Lösungsansätze möglich: • Sie können in der Initialisierungsdatei jeweils den gesamten Ausdruck angeben, also bei Verwendung einer MySQL-Datenbank

mysql CD.DATA helmut

beziehungsweise bei Verwendung einer PostgreSQL-Datenbank

Pg database=CD_DATA helmut

• Dieser Ansatz kann allerdings, besonders dann, wenn "Nicht-Programmierer" mit der Pflege der Initialisierungsdateien befasst sind, zu Irritationen führen. Daher ist es besser, prinzipiell die zweite Form zu wählen, also auch bei Verwendung von MySQL-Datenbanken. Sie ersparen sich zudem die Mühe, bei jedem DatenbankTreiber in der Dokumentation die korrekte Syntax nachschlagen zu müssen.

7.4 Arbeiten mit DBI/DBD

103

Es hat sich eingebürgert, für das Connection-Objekt den Namen $dbh zu verwenden, der die Abkürzung des englischen Begriffes DataBase-Handle darstellt. Sie sollten, sofern nicht irgendwelche widrigen Umstände dem entgegenstehen, von dieser Tradition nicht abweichen. Sie erleichtern anderen damit erheblich die Arbeit mit Ihrem Code. Die Methode connect der Klasse DBI gibt bei Erfolg den booleschen Wert true zurück bzw. f alse bei Misserfolg. Dies gilt auch für alle im Weiteren zu besprechenden DBI-Methoden. Sie sollten sich dies, wie in unserem Beispiel gezeigt, stets zu Nutze machen und auf Fehler entsprechend reagieren. Alle Datenbank-Aktionen sollten stets vom Erfolg der vorhergehenden abhängig sein, wie im folgenden Beispiel.

#!/usr/bin/perl -w # connect3.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren if ($dbh) {$success = 1} if ($success) { print "Datenbankverbindung zu $datenbank erfolgreich erstellt!\n"; # Weitere Datenbankaktionen # . . . } eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren

> #

...

104

7 Datenbank-Programmierung mit Perl/DBI

Für das Finden von Fehlern ist die Variable $DBI: : e r r s t r der DBI-Klasse von besonderer Bedeutung, denn sie enthält jeweils eine kurze Beschreibung des aufgetretenen Fehlers. Ihr Inhalt sollte daher in jedem Falle mit der Fehlermeldung ausgegeben werden 29 . Zudem sollte die von Ihnen formulierte Fehlermeldung eindeutig die Stelle im Programmcode bezeichnen, an welcher der Fehler auftrat 3 0 . Zum Abschluss dieses Abschnittes möchten wir Ihnen noch eine Syntax-Variante der connect-Anweisung vorstellen, mit der es möglich ist, auf Datenbanken zuzugreifen, die sich auf einem entfernten Rechner befinden. # ... my $dbh = BBI->connect("DBI:$treiber:database=$datenbaiik: host=$host:port=$port); # .. .

Es werden also zwei zusätzliche Parameter, nämlich der Hostname des entfernten Rechners sowie die Portadresse, über welche die Verbindung erstellt wird, angegeben.

7.4.3

Die Datenbankverbindung schließen

Einige Datenbank-Server erlauben nur eine begrenzte Anzahl von gleichzeitigen Zugriffen. Sie sollten daher stets, wenn Sie nicht mehr auf eine Datenbank zugreifen müssen, das Connection-Objekt freigeben, das heißt, die Verbindung zur Datenbank schließen. Hierzu verwenden Sie die Methode disconnectO der DBI-Klasse in der folgenden Weise. # ... Ihre Anwendung ... # Datenbank-Handle schliessen $dbh->disconnect(); # ... der Rest Ihrer Anwendung ...

Auf keinen Fall sollten Sie sich darauf verlassen, dass Perl das Connection-Objekt automatisch löscht, wenn die Skript-Ausführung beendet wird. Dies stimmt nämlich nur bedingt: Das Objekt wird zwar gelöscht, die Datenbankverbindung muss allerdings bei einigen Datenbank-Systemen explizit "gekappt" werden. Und dies ist nicht immer der Fall, wenn das Objekt einfach gelöscht wird. Mit anderen Worten: es kann durchaus vorkommen, dass die Datenbank-Verbindung nach Beenden Ihres Perl-Skriptes bestehen 29 Der Wert dieser Fehlerbeschreibungen ist allerdings nicht immer besonders hoch, zumeist bieten sie nur einen Anhaltspunkt für das Auftreten des Fehlers. 30 Manche Programmierer neigen dazu, Fehlermeldungen sehr allgemein zu formulieren, etwa "es trat ein Fehler auf!" - Aussagen wie diese können im Ernstfall stundenlanges Suchen verursachen (und in der Folge einen Nervenzusammenbruch!).

7.4 Arbeiten mit DBI/DBD

105

bleibt, wenn die disconnect ()-Methode nicht aufgerufen wird. Ein besonderes Augenmerk sollten Sie in diesem Zusammenhang auf Ihre Fehlerbehandlungs-Routinen legen: auch wenn Ihre Anwendung "unplanmäßig" terminiert 31 wird, sollten alle bestehenden Datenbank-Verbindungen geschlossen werden.

7.4.4

Ausführen einfacher SELECT-Abfragen

Nachdem wir, wie im vorangegangenen Abschnitt beschrieben, erfolgreich eine DatenbankVerbindung aufgebaut haben, können wir das so erstellte Datenbank-Handle verwenden, um die Daten abzufragen oder zu manipulieren. Zur Abfrage und Manipulation der Daten benötigen wir ein sogenanntes StatementHandle. Wir erstellen dieses in zwei Schritten: • Im ersten Schritt wird die Abfrage vorbereitet, indem die prepare-Methode des Datenbank-Handle-Objektes mit einer SQL-Anweisung als Argument aufgerufen wird. • War das Vorbereiten der Abfrage erfolgreich, kann diese mit Hilfe der executeMethode des Statement-Handies ausgeführt werden. Betrachten wir hierzu das folgende Beispiel. #!/usr/bin/perl -w # selectl.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); 31

terminiert: neudeutsch für beenden

106

7 Datenbank-Programmierung mit Perl/DBI

# Erfolg abfragen und entsprechend reagieren if ($dbh){ print "Datenbankverbindung zu $datenbank erfolgreich erstellt!\n"; # SQL-Befehl deklarieren my $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren if ($sth) { print "Statement-Handle erfolgreich erstellt!\n"; $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { print "Abfrage erfolgreich ausgefuehrt!\n"; # ... Daten extrahieren ... } eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";

>

} eise { # Fehler beim Vorbereiten der Abfrage print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";

>

} eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren

> # ... weiteres Programm ...

In unserem Beispiel sehen Sie, wie die Anwendungsstruktur geschachtelt ist, so dass jeder Schritt vom Erfolg des vorhergehenden abhängig ist. In einer "wirklichen" Anwendung müssten Sie natürlich, wie im letzten Abschnitt besprochen, nicht mehr benötigte Datenbank-Handies wieder schließen.

7.4.5

Datensätze aus der Abfrage lesen

Wurde die Abfrage erfolgreich ausgeführt, können die Datensätze mit Hilfe des StatementObjektes gelesen werden. Hierfür gibt es zwei grundsätzliche Möglichkeiten. Die Datensätze können einerseits einzeln abgefragt und bearbeitet werden, oder andererseits werden alle Tupeln des Ergebnisses auf einmal mit Hilfe einer Referenz eingelesen. Zu beachten ist dabei, dass Perl/DBI lediglich einen sogenannten F0RWARD_0NLY Cursor bietet. Das bedeutet, Sie können nicht, wie dies bei anderen Datenbank-Systemen möglich ist, im Resultset vor- und zurückblättern. Jeder Datensatz kann folglich nur ein einziges Mal abgefragt werden.

7.4 Arbeiten mit DBI/DBD

107

Welche der beiden genannten Möglichkeiten Sie verwenden hängt zum einen davon ab, auf welche Weise Sie die Datensätze weiterverarbeiten müssen. Zum anderen ist aber auch die Datenmenge, die das Resultset liefert, ein wichtiger Faktor. Es ist nicht immer möglich, mehrere tausend Datensätze in einem Array im Arbeitsspeicher zu halten. Dies wäre notwendig, wenn Sie alle Daten auf einmal über eine Referenz abfragten. Wir schauen uns daher zunächst an, wie die Tupeln einzeln abgefragt werden. Dies wird die Methode sein, die Sie in der Regel verwenden.

7.4.6

Datensätze des Resultsets einzeln übernehmen

Datensätze eines Resultsets werden, wenn Sie einzeln gelesen werden, in Form eines Arrays, einer Referenz auf ein Array oder eines Hashes zurückgegeben. Der Zugriff auf die Daten erfolgt zumeist innerhalb einer Schleife. In einem Array oder einer Array-Referenz werden die Attribute in der Reihenfolge abgelegt, in der sie im SELECT-Statement angegeben wurden. Haben Sie im SELECT-Statement den Wildcard-Operator * verwendet, entspricht die Reihenfolge der Attribute im Resultset der, wie sie in der DatenbankTabelle definiert ist. Es ist daher stets sinnvoll, die gewünschte Ausgabe-Reihenfolge im SELECT-Statement festzulegen. Haben Sie dies nicht getan, kommen Sie in der Regel nicht drum herum, bei der Ausgabe mit einem Haufen Indizes zu jonglieren. Als Alternative können Sie, wie wir weiter unten zeigen, Hashes für die Rückgabe verwenden. Im folgenden Beispiel geben wir der Reihe nach alle Zeilen des Resultsets unformatiert aus. Möchten Sie die einzelnen Attribute formatieren, müsste dies in einer inneren Schleife geschehen 32 . #!/usr/bin/perl - w # select2.pl # Verbindung z u einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # boolsche Variable success mit false m y $success = 0;

initialisieren

32 Zur Formatierung der Ausgabe eines Resultsets sollten Sie auch die folgenden Kapitel lesen: Formate (Band 1), CGI-Programmierung mit Perl und Programmieren mit Perl/Tk.

108

7 Datenbank-Programmierung mit P e r l / D B I

# Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren if ($dbh){ # SQL-Befehl deklarieren my $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren if ($sth) { $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { # in einer Schleife ResultSet ausgeben while (my Ozeile = $sth->fetchrow_array()) { print "@zeile\n";

} # Anwender ueber Ende der Ausgabe informieren print "Alle Datensaetze des Resultsets wurden ausgegeben!\n"; } eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";

> } eise { # Fehler beim Vorbereiten der Abfrage print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";

> } eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren

> #

...

Die vorstehende Ausgabe, bei welcher das Array, das den Datensatz darstellt, als Ganzes ausgegeben wird, ist nicht besonders schön, allenfalls ist es für eine Testausgabe zu gebrauchen. Wie bereits angedeutet, werden Sie die einzelnen Attribute wohl eher formatieren. Dies kann, wie im Folgenden gezeigt wird, mit Hilfe einer inneren f oreachSchleife geschehen.

#!/usr/bin/perl -w # select2.pl # Verbindung zu einer Datenbank aufbauen

7.4 Arbeiten mit DBI/DBD

109

# Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren if ($dbh){ # SQL-Befehl deklarieren my $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren if ($sth) { $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { # in einer Schleife ResultSet ausgeben while (my Szeile = $sth->fetchrow_array()) { # oder alternativ foreach-Schleife: foreach $attr (Qzeile) { print "$attr ";

> print "\n";

>

# Anwender ueber Ende der Ausgabe informieren print "Alle Datensaetze des Resultsets wurden ausgegeben!\n"; } eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";

} } eise { # Fehler beim Vorbereiten der Abfrage

110

7 Datenbank-Programmierung mit Perl/DBI

>

print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";

} eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren

>

# ...

Ein weiteres Beispiel soll demonstrieren, wie es möglich, ist einzelne Attribute eines Datensatzes zu extrahieren und auszugeben. #!/usr/bin/perl -w # select3.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # Array zum Speichern von cd_id und cd_titel initialisieren ®cd_titel = (); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren if ($dbh){ # SQL-Befehl deklarieren my $sql = "SELECT cd_id, cd_titel, cd_ersch_jahr "; $sql .= "FROM cd_relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren

7.4 Arbeiten mit DBI/DBD

111

if ($sth) { $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { # in einer Schleife ResultSet ausgeben while (my Gzeile = $sth->fetchrow_array()) { # Attribute Zwischenspeichern my ($id, $titel, $ersch_jahr) = Ozeile;

# Titel und Erscheinungsjahr ausgeben print "$titel\t$ersch_jahr\n"; # id und titel zur spaeteren Bearbeitung Zwischenspeichen my $id_titel = join /;/, $id, $titel; push(@cd_titel, $id_titel);

} # Anwender ueber Ende der Ausgabe informieren print "Alle Datensaetze des Resultsets wurden ausgegeben!\n"; }• eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";

> > eise { # Fehler beim Vorbereiten der Abfrage print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";

> } eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren

> # . . .

Anstelle eines "normalen" Arrays ist es auch möglich, eine Referenz auf ein Array zurückgeben zu lassen, indem die Methode fetchrow_arrayref () verwendet wird. Sie müssen dann die einzelnen Attribute der Rückgabe entsprechend der Regeln, die im ersten Band dieses Buches vorgestellt wurden, dereferenzieren.

# Datenbankverbindung erstellen ... # Statementhandle erstellen und Abfrage durchfuehren # . . . # in einer Schleife ResultSet ausgeben while (my $ref_zeile = $sth->fetchrow_arrayref()) {

7 Datenbank-Programmierung mit Perl/DBI

112 # Zeile dereferenzieren my Szeile = ®$ref_zeile; # ... weitere Bearbeitung ...

#

...

Beachten Sie, dass wir im vorherigen Beispiel nur den relevanten Teil der while-Schleife dargestellt haben. Ändern Sie also den Code der Anwendung s e l e c t 3 . p l entsprechend ab und führen Sie anschließend den Code aus. Besonders dann, wenn die Datensätze des Resultsets an eine Unterroutine weitergereicht werden müssen, können Sie die beschriebene Rückgabemethode sinnvoll einsetzen. Weit interessanter als die Abfrage einer Array-Referenz ist die Möglichkeit, eine Referenz auf einen Hash zurückgeben zu lassen. Sie brauchen nun nicht mehr mit nicht aussagekräftigen Indizes zu hantieren, um auf die einzelnen Attribute zuzugreifen. Sie können bequem über den Attributsnamen auf die einzelnen Elemente zugreifen. Betrachten Sie hierzu folgendes Beispiel. #!/usr/bin/perl -w # select4.pl # Verbindung zu einer Datenbank aufbauen # Datenbank-Treiber einbinden use DBI; # Verbindungsdaten aus Datei verbdat.txt einlesen open (IN, "< verbdat.txt") or die ("Verbindungsdaten koennen nicht gelesen werden: $!\n"); chomp(my $treiber = ); chomp(my $datenbank = ); chomp(my $anwender = ); chomp(my $passwort = ); close(IN); # Array zum Speichern von cd_id und cd_titel initialisieren ®cd_titel = (); # boolsche Variable success mit false initialisieren my $success = 0; # Verbindung erstellen oder Programm beenden my $dbh = DBI->connect("DBI:$treiber:$datenbank", $anwender, $passwort); # Erfolg abfragen und entsprechend reagieren

7.4 Arbeiten mit DBI/DBD

113

if ($dbh){ # SQL-Befehl deklarieren my $sql = "SELECT cd_id, cd_titel, cd_ersch_jahr "; $sql .= "FROM cd.relation"; # Abfrage vorbereiten my $sth=$dbh->prepare($sql); # Bei Erfolg Abfrage ausfuehren if ($sth) { $sth->execute(); # bei Erfolg koennen die Daten extrahiert werden if ($sth) { # in einer Schleife ResultSet ausgeben while (my $ref_zeile = $sth->fetchrow_hashref()) { # Titel und Erscheinungsjahr ausgeben print $ref_zeile->{'cd_titel'} . "\t"; print $ref_zeile->{'cd_ersch_jahr'}- . "\n"; # id und titel zur spaeteren Bearbeitung Zwischenspeichen my $id_titel = join /;/, $ref_zeile->{'cd_id'}, $ref_zeile->{'cd_titel'>; push(@cd_titel, $id_titel);

> # Anwender ueber Ende der Ausgabe informieren print "Alle Datensaetze des Resultsets wurden ausgegebenen"; } eise { # Fehler beim Ausfuehren der Abfrage print "Beim Ausfuehren der Abfrage trat ein Fehler auf: $!"; print $sth->errstr . "\n";

} } eise { # Fehler beim Vorbereiten der Abfrage print "Beim Vorbereiten der Abfrage trat ein Fehler auf: $!"; print $dbh->errstr . "\n";

> } eise { print "Bei der Datenbank-Verbindung trat ein Fehler auf!"; # Auf Fehler reagieren

> # . . .

Diese Variante unseres Programmes ist nicht nur kompakter als die weiter oben vorgestellte Lösung, sondern auch für andere besser lesbar. Abschließend sei nochmals darauf hingewiesen, dass jedes Element des Resultsets nur einmal abgerufen werden kann. Wenn Sie also mehrfach auf ein Element zugreifen möchten, müssen Sie das Resultset beispielsweise in einem Array sichern, wie wir das in unserem letzten Beispielskript getan haben.

114

7.4.7

7 Datenbank-Programmierung mit Perl/DBI

Metadaten zum Resultset ermitteln

Nachdem Sie die execute ()-Methode des Statement-Handies ausgeführt haben, können Sie mit Hilfe der verschiedenen Eigenschaften des Handies Informationen über das Resultset abfragen. Die Anzahl der Spalten, die das Resultset umfasst, kann mit Hilfe der Eigenschaft NUM_OF_FIELDS abgefragt werden. # numOfFields.pl use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # Statement-Handle vorbereiten und ausfuehren my $sth = $dbh->prepare("SELECT * FROM cd_relation"); $sth->execute(); # Anzahl der Attribute im Resultset ermitteln und ausgeben my $zahl_Felder = $sth->{NUM_OF_FIELDS>; print "Anzahl der Felder im Resultset: $zahl_Felder\n"; # ... Programmcode ...

Falls die Abfrage, die Sie ausgeführt haben, kein SELECT-Statement war, ergibt die Eigenschaft NUM_0F_FIELDS den Wert 0. Dies ist vor allem dann von Bedeutung, wenn Sie Abfragen in Funktionen bearbeiten und dabei ermitteln müssen, ob die Abfrage ein Resultset geliefert hat. Eine weitere wichtige Eigenschaft des Statement-Handies ist NAME, die eine Referenz auf ein Array zurückgibt, das die Namen aller Felder des Resultsets enthält. Betrachten Sie hierzu das folgende Beispiel.

7.4 Arbeiten mit DBI/DBD

115

# nameOfFields.pl use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # Statement-Handle vorbereiten und ausfuehren my $sth = $dbh->prepare("SELECT * FROM cd.relation"); $sth->execute(); # Anzahl der Attribute im Resultset ermitteln und ausgeben my $zahl_Felder = $sth->{NUM_OF_FIELDS>; print "Anzahl der Felder im Resultset: $zahl_Felder\n"; # In Schleife Feldnamen im Resultset ausgeben foreach $feldname (@{$sth->{NAME») { print "$feldname\n";

> # ... Programmcode ...

Im Beispiel haben wir in einer f oreach-Schleife die Namen der einzelnen Attribute des Resultsets ausgegeben. Beachten Sie hierbei die Art, in der die Referenz auf das Array mit den Feldnamen im Parameter der foreach-Funktion dereferenziert wurde. Die Eigenschaft NAME ist vor allem dann einsetzbar, wenn mit Hilfe von Formaten oder CGI-Anwendungen die Daten eines Resultsets ausgegeben werden sollen. Mit Hilfe des NAME-Arrays ist es möglich, Spaltenüberschriften auszugeben. Besonders interessant ist dabei, dass es mit Hilfe der Eigenschaften NAME_uc möglich ist, die Spaltennamen in Großbuchstaben und mit NAME_lc in Kleinbuchstaben auszugeben. Mit der Eigenschaft TYPE ist es möglich, den Datentyp der Attribute eines Resultsets abzufragen. Dabei gibt TYPE eine Referenz auf ein Array von Integer-Werten zurück, welche die in der folgenden Tabelle vorgestellten Bedeutungen haben.

116 Wert 1 2 3 4 5 6 7 8 9 10 11 12 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10

7 Datenbank-Programmierung mit Perl/DBI Bedeutung SQL CHAR SQL NUMERIC SQL DECIMAL SQL INTEGER SQL SMALLINT SQL FLOAT SQL REAL SQL DOUBLE SQL DATE SQL TIME SQL TIMESTAMP SQL VARCHAR SQL LONGVARCHAR SQL BINARY SQL VARBINARY SQL LONGVARBINARY SQL BIGINT SQL TINYINT SQL BIT SQL WCHAR SQL WVARCHAR SQL WLONGVARCHAR

Eine weitere interessante Eigenschaft des Statement-Handies ist NULLABLE, die eine Referenz auf ein Array mit Integerwerten zurückgibt, welche darüber Auskunft geben, ob das entsprechende Attribut Null werte enthalten darf oder nicht. Dabei haben die Werte die in nachfolgender Tabelle dargestellte Bedeutung.

Wert 0 1 2

Bedeutung Im Attribut darf kein Nullwert enthalten sein Die Spalte kann Nullwerte enthalten Es sind keine Informationen zu dieser Spalte vorhanden

Schließlich ist es möglich, mit Hilfe der Eigenschaft NUM_GF_PARAMS zu ermitteln, wieviele Parameter im SQL-Begriff der Abfrage definiert sind 33 . Die meisten der vorgestellten Eigenschaften sind dann für Sie von Interesse, wenn Ihre Datenbankanwendung einen sehr hohen Abstraktionsgrad besitzt. Das heißt, dann, wenn Sie Abfragen mit Hilfe von Unterroutinen bearbeiten, die sehr stark verallgemei33

L e s e n Sie hierzu bitte den Abschnitt Abfragen

optimieren

weiter unten in diesem Kapitel.

117

7.4 Arbeiten mit DBI/DBD

nert sind. Beispiele für den Einsatz dieser Eigenschaften finden Sie weiter unten im Abschnitt, der sich mit der CGI-Programmierung unter Perl befasst. Zum Abschluss dieses Abschnittes möchten wir noch auf eine Eigenschaft des StatementHandies aufmerksam machen, die zwar in fast allen Büchern zu Perl/DBI beschrieben wird, allerdings nicht bei allen Treibern tatsächlich so funktioniert wie erwartet. Es handelt sich um die Eigenschaft rows des Statement-Handies, welche die Anzahl der im Resultset enthaltenen Zeilen zurückliefern soll. Arbeiten Sie mit einer MySQL-Datenbank, gibt diese Funktion, wie wir im folgenden Beispiel sehen, die Anzahl der im Resultset enthaltenen Datensätze zurück. #!/usr/bin/perl -w # rows.pl use DBI;

# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen - alle Indizes abfragen my $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten u n d ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); # Versuchen die Anzahl der Datensaetze im Resultset zu ermitteln print $sth->rows() . "\n"; # Zum Vergleich alle Datensaetze anzeigen while (my Stupel = $sth->fetchrow_array()) print "@tupel\n";

{

> Bei einigen Datenbank-Treibern 34 gibt allerdings der Versuch, die Anzahl der Datensätze auf diese Weise zu ermitteln, den Wert - 1 zurück. Dieser Wert bedeutet, dass es nicht möglich war, die Anzahl der Datensätze zu ermitteln. Verlassen kann man sich ^Beispielsweise der ODBC-Treiber der ActiveState Distribution.

118

7 Datenbank-Programmierung mit P e r l / D B I

auf den zurückgegebenen Wert prinzipiell nur im Zusammenhang mit SQL-Statements wie UPDATE oder DELETE. Auf SELECT-Statements wie im vorhergehenden Beispiel sollte man die Eigenschaft niemals anwenden 3 5 . U m die Anzahl der Datensätze in einem Resultset zu ermitteln, sollte man daher sicherheitshalber eine SELECT C0UNT(*)-Abfrage wie im folgenden Beispiel durchführen. #!/usr/bin/perl -w # rows2.pl use DBI;

# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen - alle Indizes abfragen my $sql = "SELECT COUNT(cd_id) FROM cd.relation"; # selectrow_array - Methode ausfuehren my Qresult = $dbh->selectrow_array($sql); # Anzahl der Datensaetze ausgeben print "Datensaetze: $result[0]\n"; # Erneut abfragen und ausgeben $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten und ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); # Zum Vergleich alle Datensaetze anzeigen while (my Qtupel = $sth->fetchrow_array()) { print "Otupel" . "\n";

> Die vorgestellte Lösung ist zwar deutlich länger und nicht so elegant wie der erste Algorithmus, hat aber dafür den Vorteil, dass sie immer funktioniert. Sie ist sicher ein Kandidat für eine Funktion in einer allgemeinen Bibliothek mit Datenbank-Routinen. 35 D e r Autor hat diese Lektion auch erst dann gelernt, als es auf Grund der Fehlfunktion bei einem Kunden zu einem massiven Problemen kam!

7.4 Arbeiten mit D B I / D B D

119

Die Funktion s e l e c t r o w _ a r r a y ( ) stellen wir weiter unten im Detail vor.

7.4.8

Den ersten Datensatz abfragen

Mitunter kommt es vor, dass ein Resultset nur einen einzigen Datensatz enthält, beispielsweise dann, wenn man mit Hilfe der SQL-Funktion MAX die letzte (hier: höchste) ID-Nummer einer Tabelle ermitteln möchte. In diesem Falle können Sie die Methode s e l e c t r o w _ a r r a y ( ) des Datenbank-Handies aufrufen. Der Vorteil dieser Methode liegt darin, dass Sie die p r e p a r e O - , e x e c u t e O sowie die f e t c h r o w _ * ( ) - M e t h o d e n des Datenbank- und des Statement-Handies in sich vereint. Betrachten Sie hierzu den folgenden Code-Ausschnitt, mit dem wir den höchsten Index der c d _ r e l a t i o n ermitteln und um 1 inkrementieren.

# selectrow_array.pl use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen my $sql = "SELECT MAX(cd_id) FROM cd_relation"; # selectrow_array - Methode ausfuehren my Qresult = $dbh->selectrow_array($sql); # alte Id zur Kontrolle ausgeben print "Alte Id: " . $result[0] . "\n"; # id inkrementieren und ausgeben my $new_id = ++$result [0]; print "Neue Id: $new_id\n"; # Datenbank-Handle schliessen $dbh->disconnect(); # ... weiterer Programmcode . . .

120

7 Datenbank-Programmierung mit Perl/DBI

Die gezeigte Methode lässt sich stets dann sinnvoll einsetzen, wenn man sicher ist, dass im Resultset lediglich ein Datensatz enthalten ist. In anderen Situationen ist der Wert dieser Methode eher zweifelhaft. Ein Einsatz kann in diesem Fall erhebliche Risiken für Ihre Programmlogik bergen. Beachten Sie zudem, dass es über diese Methode nicht möglich ist, Metadaten des Resultsets wie etwa Feldnamen zu ermitteln. Zum Ermitteln von Indizes leistet diese Methode allerdings unschätzbare Werte und bewahrt den Programmierer vor einer vorzeitigen Sehnenscheidenentzündung auf Grund übermäßiger Tipparbeit.

7.4.9

Einzelne Spalten abfragen

Wie wir bereits erläutert haben, besitzt Perl/DBI keinen beweglichen Cursor, das heißt, es ist nicht möglich, sich in einem Resultset hin und her zu bewegen. Für manche Anwendungsbereiche ist aber gerade eine solche "Blätter-Funktion" sinnvoll. Perl/DBI bietet mit der Funktion selectcol_arrayref () die Möglichkeit, einzelne Spalten eines Resultsets in eine Array-Referenz einzulesen. Mit Hilfe dieser Funktion ist es recht leicht, einen sogenannten beweglichen Datenbank-Cursor zu erstellen, indem wir die Indizes aller Datensätze in ein Array einlesen. Im folgenden Beispiel stellen wir Ihnen hierzu die Grundversion vor, die wir dann weiter unten optimieren 36 . # selectcol_array.pl use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; m y $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->conn.ect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen - alle Indizes abfragen my $sql = "SELECT cd_id FROM cd.relation"; # alle indizes in Array index einlesen my $index_ref = $dbh->selectcol_arrayref($sql); 36

Um diese Punktion in Datenbank-Anwendungen einsetzen zu können, müssen Sie darauf achten, dass die Indizes regelmäßig neu eingelesen werden müssen, damit Änderungen an der Datenstruktur entsprechend gespiegelt werden.

7.4 Arbeiten mit D B I / D B D my Sindex = @$index_ref; # Ersten Datensatz anzeigen Datensatz_zeigen(0index[0] ); # Zeiger auf aktuellen Datensatz my $cur = 0; # Endlosschleife zur Datennavigation while () { print "Eingabe vor , zurueck , beenden : "; chomp(my $nav = ); if ($nav eq 'e') { last; } elsif ($nav eq '+') { # Naechsten Index holen if ($cur < $#index) { # auf naechsten Index zeigen ++$cur; # Aktuellen Datensatz anzeigen Datensatz_zeigen(@index[$cur]); } eise { # Meldung ausgeben print "letzter Datensatz erreicht!\n";

>

> elsif ($nav eq '-') { # vorhergehenden Index holen if ($cur > 0) { # auf vorhergehenden Index zeigen --$cur; # Aktuellen Datensatz anzeigen Datensatz_zeigen(9index[$cur]); } eise { # Meldung ausgeben print "erster Datensatz erreicht!\n";

>

} eise { # Meldung ausgeben print "Falsche Eingabe!\n";

>

>

# Datenbank-Handle schliessen $dbh->disconnect(); # Routine zum Anzeigen eines Datensatzes sub Datensatz_zeigen { # Datensatz-Index uebernehmen

121

7 Datenbank-Programmierung mit Perl/DBI

122 my $index = $_[0];

# Bildschirm loeschen (nur bei LINUX) # unter Windows muss in der Datei Config.sys die # folgende Zeile stehen: # DEVICE=ANSI.SYS # Nicht alle Windows-Varianten werden unterstuetzt print "\033[2J"; # SQL-Statement zur Abfrage der einzelnen Datensaetze vorbereiten $sql = "SELECT * FROM cd.relation WHERE cd_id = $index"; # Statement-Handle vorbereiten my $sth = $dbh->prepare($sql); # Abfrage ausfuehren $sth->execute(); # Datensatz ausgeben my $ref_zeile = $sth->fetchrow_hashref(); # Titel und Erscheinungsjahr ausgeben print "CD-Titel: " . $ref_zeile->{'cd_titel'} . "\t"; print "Erscheinungsjahr: " . $ref_zeile->{'cd_ersch_jahr'} . "\n";

> # ... weiterer Programmcode ...

Beachten Sie beim voranstehenden Code, dass die vorgestellte Escape-Sequenz zum Löschen des Bildschirmes nicht auf allen Windows-Varianten lauffähig ist 37 . In jedem Falle müssen Sie die ANSI-Treiber in der Conf ig.Sys-Datei einfügen. DEVICE=ANSI.SYS

In der vorgestellten Anwendung werden zunächst mit Hilfe der Funktion selectcol_arrayref () des Datenbank-Handies alle Index-Felder aus der Tabelle cd_relation in ein Array eingelesen. Innerhalb der Endlos-Schleife werden vom Benutzer "NavigationsZeichen" abgefragt. Je nachdem, ob der Anwender auf die Taste + oder die Taste gedrückt hat, wird der nächste bzw. vorherige Index aus dem Array gelesen. Sollten Sie dabei das Ende des Arrays erreichen, erhalten Sie eine entsprechende Meldung. Der gefundene Index wird der Unterroutine Datensatz_zeigen() übergeben. Innerhalb dieser Routine wird zunächst der Bildschirm gelöscht. Anschließend erstellen wir mit Hilfe des übergebenen Indexes einen SQL-Begriff, um den entsprechenden Datensatz abzufragen. In der gezeigten Version wäre es auch möglich gewesen, die im vorangegangenen Abschnitt besprochene Methode selectrow_array() zu nutzen. Für unser Beispiel fanden wir allerdings die Übernahme als Hash übersichtlicher. Schließlich geben wir das Ergebnis aus. 37

Soweit der Autor dies als "Nicht-Fachmann" in Windows-Fragen beurteilen kann!

7.4 Arbeiten mit DBI/DBD

123

Natürlich schreit dieses Programm geradezu nach einer grafischen Benutzerschnittstelle. Hier bitten wir Sie noch um ein wenig Geduld - wir werden dieses Thema weiter unten ausführlich behandeln. Im übernächsten Abschnitt, wenn wir uns damit beschäftigen, wie Abfragen mit Perl/DBI optimiert werden können, kommen wir nochmals auf diese Anwendung zurück und stellen Ihnen dort eine optimierte Version vor.

7.4.10

Das Resultset als Ganzes übernehmen

Bisher haben wir stets einzelne Datensätze aus dem Resultset gelesen. Besonders dann, wenn die Ergebnisse einer Abfrage an eine Punktion weitergereicht werden sollen, kann es aber sinnvoll sein, alle Ergebnisse auf einmal einzulesen und in einer Referenz auf ein Array oder einen Hash zu hinterlegen. Mit Hilfe der Funktion f etchall_arrayref () ist dies möglich. Dabei liefert diese Funktion eine Referenz auf ein Array zurück, das seinerseits, je nach angegebenen Parametern, Referenzen auf Arrays oder Hashes enthält, die wiederum auf die einzelnen Datensätze zeigen. Die Datenstruktur wird in der folgenden Abbildung dargestellt. Standardmäßig gibt die Funktion f etchall_arrayref () dabei eine Referenz auf ein Array mit Referenzen auf ein Array zurück. Betrachten Sie hierzu das folgende Beispiel. #!/usr/bin/perl -w # fetchall_arrayref.pl use DBI;

# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten und ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); $array_ref_alle = $sth->fetchall_arrayref(); # Alle Zeilen des Resultsets durchlaufen foreach $array_ref_zeile (0$array_ref_alle) {

124

7 Datenbank-Programmierung mit P e r l / D B I

# alle Spalten ausgeben print "®$array_ref_zeile" . "\n";

> Das Beispiel gibt alle Felder aller Tupel der Tabelle cd_relation aus. Als Argument können Sie der Funktion f etchall_arrayref () zudem in eckigen Klammern die Indizes der Spalten übergeben, die im Resultset enthalten sein sollen. Im folgenden Beispiel übernehmen wir die Spalten cd_id, cd_titel und cd_ersch_jahr, also die drei ersten Spalten der Tabelle.

#!/usr/bin/perl -w # fetchall_arrayref2.pl use DBI;

# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD.DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen $sql = "SELECT * FROM cd.relation"; # Abfrage vorbereiten und ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); $array_ref_alle = $sth->fetchall_arrayref([0,1,2]); # Alle Zeilen des Eesultsets durchlaufen foreach $array_ref_zeile (Q$array_ref_alle) { # alle Spalten ausgeben print "Q$array_ref_zeile" . "\n";

> Alternativ zur oben vorgestellten Syntax ist es auch möglich, den Bereichsoperator in der folgenden Weise zu verwenden.

#

7.4 Arbeiten mit DBI/DBD

125

$array_ref_alle = $sth->fetchall_arrayref([0..2]); # . . .

Wie bereits angedeutet, können die Datenfelder auch als Referenz auf einen Hash übergeben werden. Hierzu geben Sie der Funktion als Argument ein Paar geschweifter leerer Klammern mit, um alle Felder abzufragen, oder Sie geben innerhalb der geschweiften Klammern die Spaltennamen an und weisen diesen jeweils den Wert 1 zu. Betrachten Sie hierzu die folgende Anwendung, die wiederum die Spalten cd_id, c d _ t i t e l und cd_ersch_jahr ausgibt. # ! / u s r / b i n / p e r l -w # fetchall_eurrayref3.pl use DBI; # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD.DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # SQL-Begriff erstellen $sql = "SELECT * FROM cd_relation"; # Abfrage vorbereiten und ausfuehren my $sth = $dbh->prepare($sql); $sth->execute(); $array_ref_alle = $sth->fetchall_arrayref({ cd_id => 1, cd_titel => 1, cd_ersch_jahr => 1>); # Alle Zeilen des Resultsets durchlaufen foreach $array_ref_zeile (®$array_ref_alle) { # Spalten ausgeben print $$array_ref_zeile{'cd_id'} . " " ; print $$array_ref_zeile{'cd_titel'} . " "; print $$array_ref_zeile{'cd_ersch_jahr'} . "\n";

>

7 Datenbank-Programmierung mit Perl/DBI

126

Der zuletzt gezeigte Code ist zwar deutlich länger als der Code der bereits vorgestellten Lösungen, erscheint aber wesentlich übersichtlicher. Dies gilt vor allem dann, wenn Ihre Programme umfangreicher werden. Sie erkennen jeweils auf den ersten Blick, was ausgegeben wird. Andererseits eignet sich die Übernahme als Hash nicht zur Generalisierung, da die einzelnen Feldnamen stets hardcoded38 im Programm angegeben werden müssen. Eine spätere Änderung an der Datenstruktur kann in diesem Falle zu nicht unerheblichen Änderungen am Skript führen und birgt eine Menge von Risiken in sich.

7.4.11

Abfragen optimieren

Sicher haben Sie sich bereits gefragt, warum es mit Perl/DBI so "umständlich" ist, eine Abfrage zu erstellen. Warum also, bevor eine Abfrage mit der executeO-Methode ausgeführt werden kann, diese mit Hilfe der prepareO-Methode vorbereitet werden muss? Dies erscheint zunächst etwas redundant und ist in der Tat nicht besonders schnell 39 . Tatsächlich aber steckt hinter dieser Trennung eine Idee. Betrachten Sie nochmals die Anwendung selectcol_array.plim vorletzten Abschnitt. In der Funktion Datensatz_zeigen() ist Ihnen vielleicht aufgefallen, dass im Prinzip die gleiche Abfrage ausgeführt wird. Lediglich der Parameter, der die gesuchte cd_id angibt, wechselt. Perl/DBI bietet in diesem Fall die Möglichkeit an, die Abfrage zu parametrisieren, was letztlich nicht anderes bedeutet, als dass wir das Statement-Handle nur ein einziges Mal vorbereiten. Der executeO-Methode wird dann jeweils nur der gewünschte Parameter übergeben. Dieses Vorgehen ist nicht nur viel übersichtlicher sondern auch, wie der Autor in Tests mit umfangreichen Datenbanken (mit mehreren 10.000 Datensätzen) festgestellt hat, auch doppelt so schnell wie das früher vorgestellte Verfahren. Betrachten wir zur Demonstration des Gesagten das folgende Skript.

#

selectcol_array_2.pl

use DBI; # ... Programmcode ... # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD.DATA"; my $user = "helmut"; my $dbh = 38

DBI->connect("$treiber:database=$datenbank",

Der Terminus hardcoded bezeichnet den Umstand, dass Parameter, die eigentlich variabel sein sollten, im Programmcode hinterlegt sind. Beispielsweise haben wir im vorgestellten Skript die DatenbankVerbindungsparameter der Bequemlichkeit halber hardgecoded. Weiter oben haben wir Ihnen gezeigt, wie Sie dies mit Hilfe von sogenannten irai-Dateien umgehen können. Generell ist es in großen Projekten sinnvoll, möglichst wenige Parameter hardcoded zu verwenden. 39 Wobei die Aussage "nicht besonders schnell" schon fast einen Euphemismus darstellt!

7.4 Arbeiten mit DBI/DBD $user, ""); # SQL-Begriff erstellen - alle Indizes abfragen my $sql = "SELECT cd_id FROM cd_relation"; # alle indizes in Array index einlesen # direkt dereferenzieren my Oindex = @{$dbh->selectcol_arrayref($sql)}; # Funktion zum Vorbereiten des Statement-Handies aufrufen my $sth = handle_erstellen($dbi); # Ersten Datensatz anzeigen datensatz_zeigen($sth, @index[0]); # Zeiger auf aktuellen Datensatz my $cur = 0; # Endlosschleife zur Datennavigation while () { print "Eingabe vor , zurueck , beenden : "; chomp(my $nav = ); if ($nav eq 'e') { last; > elsif ($nav eq '+') { # naechsten Index holen if ($cur < $#index) { # auf naechsten Index zeigen ++$cur; # Aktuellen Datensatz anzeigen datensatz_zeigen($sth, Qindex[$cur]); } eise { # Meldung ausgeben print "letzter Datensatz erreicht!\n";

>

> elsif ($nav eq '-') { # vorhergehenden Index holen if ($cur > 0) { # auf vorhergehenden Index zeigen --$cur; # Aktuellen Datensatz anzeigen datensatz_zeigen($sth, Oindex[$cur]); } eise { # Meldung ausgeben print "erster Datensatz erreicht!\n";

} } eise {

127

128

7 Datenbank-Programmierung mit Perl/DBI # Meldung ausgeben print "Falsche Eingabe!\n";

>

>

# Routine zum Erstellen des Statement-Handies sub handle_erstellen { # Datenbank-Handle uebernehmen my $dbi = $_ [0]; # SQL-Statement zur Abfrage der einzelnen Datensaetze vorbereiten $sql = "SELECT * FROM cd.relation WHERE cd_id = ?"; # Statement-Handle vorbereiten my $sth = $dbh->prepare($sql); # Statement-Handle zurueckgeben return $sth;

} # Routine zum Anzeigen eines Datensatzes sub datensatz_zeigen { # Statement-Handle uebernehmen my $sth = $_ [0]; # Datensatz-Index uebernehmen my $index = $_[1]; # Bildschirm loeschen (nur bei LINUX) # unter Windows muss in der Datei Config.sys die # folgende Zeile stehen: # DEVICE=ANSI.SYS # Nicht alle Windows-Varianten werden unterstuezt print "\033 [2J"; # Abfrage ausfuehren $sth->execute($index); # Datensatz ausgeben my $ref_zeile = $sth->fetchrow_hashref(); # Titel und Erscheinungsjahr ausgeben print "CD-Titel: " . $ref J .zeile->{'cd_titel'} . "\t"; print "Erscheinungsjahr: " . $ref_zeile->{'cd_ersch_jahr'} . "\n";

> # ... weiterer Programmcode

7.4 Arbeiten mit DBI/DBD

129

Wie Sie sehen, haben wir in der vorstehenden Anwendung das Statement-Handle nur einmal in der Funktion h a n d l e _ e r s t e l l e n O vorbereitet. Innerhalb des SQL-Strings werden alle variablen Werte der WHERE-Klausel durch Fragezeichen ersetzt. Dabei ist es durchaus möglich und üblich, dass mehr als ein Parameter variabel ist. Die Routine gibt als Ergebnis das soeben erstellte Statement-Handle zurück. Wir haben "nebenbei" in dieser zweiten Version auch alle globalen Variablen entfernt, um die Anwendung übersichtlicher und vor allem sicherer zu gestalten. In der Unterroutine d a t e n s a t z _ z e i g e n ( ) können wir uns nun darauf beschränken, den gewünschten Index zu extrahieren und an die executeO-Methode des StatementHandies zu übergeben. Diese zweite Version unserer Anwendung ist nicht nur erheblich schneller und sicherer als die zuvor vorgestellte, sondern auch erheblich übersichtlicher. Dies liegt nicht zuletzt daran, dass wir in dieser Version keine globalen Variablen eingesetzt haben. Allerdings möchten wir darauf hinweisen, dass wir in der vorliegenden Version auf das Abfangen von Fehlern verzichtet haben! Im richtigen Leben müssten Sie diese noch ergänzen.

7.4.12

Datenbanken mit SQL-Statements pflegen

Im Abschnitt Die Abfrage-Sprache SQL haben wir gelernt, dass auch die Datenbankpflege mit Hilfe von SQL-Statements vonstatten geht. Natürlich ist es möglich, diese Anweisungen von Perl/DBI aus zu verwenden. Sie können dies mit den bisher bekannten Methoden p r e p a r e O und executeO bewerkstelligen, was immer dann sinnvoll ist, wenn Sie eine große Zahl von gleichartigen Aktionen in einer Datenbank vornehmen müssen. Der Vorteil dieser Vorgehensweise liegt darin, dass Sie, wie im Abschnitt Abfragen optimieren gezeigt, Parameter-Abfragen verwenden können, was die Effektivität der Anwendung erheblich steigert. Das folgende Beispiel nutzt diese Möglichkeit, um in die c d _ r e l a t i o n eine Reihe von Datensätzen einzufügen, die aus der Textdatei newcd.txt gelesen werden. # ! / u s r / b i n / p e r l -w # db_pflegen.pl use DBI; # Datenbank-Handle e r s t e l l e n my $ t r e i b e r = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, " " ) ; # Statement-Handle v o r b e r e i t e n my $sql = "INSERT INTO c d _ r e l a t i o n ( c d _ i d , c d . t i t e l ,

";

130

7 Datenbank-Programmierung mit Perl/DBI

$sql .= "cd_ersch_jahr, kuenstler_id)"; $sql .= "VALUES(?, ?, ?, ?)"; my $stli = $dbh->prepare($sql); # Daten aus Textdatei lesen und in Datenbank schreiben open (IN, 'newcd.txt'); while () { # Zeile zerlegen chomp; my ($id, $titel, $ersch_jahr, $kuenstler) = split /;/, $_; $sth->execute($id, $titel, $ersch_jahr, $kuenstler);

> # Dateihandle und Datenbankverbindung schliessen close IN; $dbh->disconnect();

Die gezeigte Anwendung setzt voraus, dass sich im Anwendungsverzeichnis die Textdatei newcd.txt befindet, die zeilenweise die anzufügenden Datensätze enthält. Dabei ist wichtig, dass die einzelnen Zeilen jeweils den gleichen Aufbau haben und die einzelnen Einträge durch Semikola voneinander getrennt sind. Leere Felder müssen durch die entsprechende Anzahl von Semikola dargestellt werden. Eine solche Textdatei könnte wie folgt aussehen. 4;Mein Leben mit Mama;1969;4 5;99 Luftballons;1983;1 6;Street Legal;; 7;We believe in God Inc.;1979;5

Beachten Sie, dass im dritten gezeigten Datensatz die Angaben für das Erscheinungsjahr und die Künstler-Id fehlen. Mit dem gezeigten Vorgehen können sehr schnell und effektiv große Datenmengen in eine Datenbank geschrieben werden. Beispielsweise geben viele Behörden Datenbestände in Form solcher Flat-File-Databases40 oder CSV-Dateien weiter. Das gezeigte Vorgehen ist aber nur dann sinnvoll, wenn wirklich eine große Anzahl gleichartiger Änderungen an der Datenbank vorgenommen werden soll. Möchten Sie hingegen nur einzelne Datensätze verändern bzw. einzelne Aktionen ausführen, wie etwa das Löschen eines Datensatzes, so ist es sinnvoller, die do()-Methode des DatenbankHandies zu verwenden. 40 Flat-File-Database oder CSV-Dateien ( C o m m a Separated Values) bezeichnen solche Textdateien, wie wir sie oben vorgestellt haben.

7.4 Arbeiten mit D B I / D B D

131

#!/usr/bin/perl -w # db_pflegen2.pl use DBI; # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank",$user,

"");

# Datensatz 'Street Legal' loeschen my $sql = "DELETE FROM cd.relation WHERE cd_titel = 'Street Legal'"; unless ($dbh->do($sql)) { print "Ich konnte den Datensatz nicht loeschen!\n";

} # Datenbankverbindung schliessen $dbh->disconnect();

Das letzte Beispiel zeigt, wie mit Hilfe der do()-Methode ein Datensatz aus der cd_relation gelöscht wird. Dabei gibt die Methode den Wert 1 zurück, wenn die Ausführung erfolgreich war oder 0, falls nicht. Die Methode do () eignet sich also hervorragend zur Administration einer Datenbank, also beispielsweise um Tabellen anzulegen oder wieder zu löschen. Bei einigen DatenbankSystemen ist es sogar möglich, mit Hilfe dieser Methode neue Datenbanken anzulegen oder zu löschen, wie das folgende Beispiel demonstriert 4 1 .

#!/usr/bin/perl -w # db_pflegen3.pl use DBI; # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", 41 Wir geben zu, dass diese Anwendung nicht sehr sinnvoll ist, normalerweise werden Sie sich für das eine oder andere entscheiden!

132

7 Datenbank-Programmierung mit Perl/DBI $user, "");

# Neue Datenbank anlegen unless ($dbh->do("CREATE DATABASE mein.test")) { print "Ich konnte die Datenbank nicht anlegen!\n";

> # ... und wieder loeschen unless ($dbh->do("DR0P DATABASE mein.test")) { print "Ich konnte die Datenbank nicht loeschen!\n";

> # Datenbankverbindung schliessen $dbh->disconnect();

7.4.13

Metadaten zur Datenbank ermitteln

Weiter oben haben wir gesehen, wie Sie Informationen über ein Recordset erhalten. Diese werden stets über ein Statement-Handle abgerufen. Bevor wir nun das Thema Perl/DBI vorläufig verlassen, möchten wir Ihnen noch erläutern, wie es möglich ist, Metadaten auf der Ebene des Datenbank-Handies abzufragen. Leider verwenden fast sämtliche Datenbank-Management-Systeme unterschiedliche Mechanismen, um diese Daten zu speichern. Perl/DBI verfügt daher nur über zwei Methoden, die mit allen Datenbank-Systemen zusammen arbeiten. Mit Hilfe der Methode tables() kann eine Liste aller in einer Datenbank entbotenen Tabellen abgefragt werden. Betrachten Sie hierzu folgendes Beispiel. #!/usr/bin/perl -w # metadaten.pl use DBI; # Datenbankverbindung herstellen # Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD_DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # Alle Tabellen der Datenbank abfragen

7.4 Arbeiten mit DBI/DBD

133

my Qtabellen = $dbh->tables(); # Header der Ausgabe print "\nln der Datenbank $datenbank sind folgende Tabellen definiert:\n"; print " \n\n" ; # ... und ausgeben foreach my $tabelle (Qtabellen) { print "Tabelle: $tabelle\n";

> print "\n"; $dbh->disconnect();

Die andere Methode, die mit allen Datenbank-Systemen zusammen arbeitet, ist table_i n f o ( ) . Sie gibt ein Statement-Handle zurück, dass in jedem Falle die folgenden Felder enthält: • TABLE_QUAL IFIER: In den allermeisten Fällen wird dieses Feld den Wert undef bzw. NULL enthalten.

• TABLE_OWNER: Dieses Feld gibt den Namen des Besitzers der Tabelle zurück. Bei den meisten Datenbank-Systemen ist der Besitzer einer Tabelle die Person, die diese angelegt hat und alle Rechte auf die Tabelle besitzt. • TABLE_NAME: Wie der Name schon sagt, ist in diesem Feld der Name der Datentabelle hinterlegt. • TABLE_TYPE: Mit diesem Feld können Sie den Typ der Tabelle ermitteln. Mögliche Werte sind unter anderen: TABLE, VIEW, SYSTEM TABLE. • REMARKS: Bei vielen Datenbank-Management-Systemen ist es möglich, zu den Datentabellen Bemerkungen zu hinterlegen, die den Inhalt oder den Zweck der Tabelle beschreiben. Dieses Feld gibt diese Beschreibung aus; in den meisten Fällen wird dies undef oder NULL sein. Auch zu dieser Methode möchten wir Ihnen ein Beispiel vorstellen. #!/usr/bin/perl -w # metadaten2.pl use DBI; # Datenbankverbindung herstellen

134

7 Datenbank-Programmierung mit Perl/DBI

# Datenbank-Handle erstellen my $treiber = "DBI:mysql"; my $datenbank = "CD.DATA"; my $user = "helmut"; my $dbh = DBI->connect("$treiber:database=$datenbank", $user, ""); # Alle Tabellen der Datenbank abfragen my (Stabellen = $dbh->tables() ; # Header der Ausgabe print "\nln der Datenbank $datenbank sind folgende Tabellen definiert:\n"; print " \n\n"; # ... und ausgeben foreach my $tabelle (®tabellen) { print "Tabelle: $tabelle\n";

> # Tabellen-Informationen ermitteln my $sth = $dbh->table_info(); # exemplarisch einige Informationen ausgeben while (my $table_ref = $sth->fetchrow_hashref()) { print "Tabellenname: " . $table_ref->{'TABLE_NAME'} . "\n"; print "\tBesitzer : " . $table_ref->{'TABLE.OWNER'} . "\n"; print "\tTyp : " . $table_ref->{'TABLE_TYPE'} . "\n"; print "YtBemerkung: " . $table_ref->{'REMARKS'} . "\n\n";

} print "\n"; $dbh->disconnect();

Die Ausgabe dieses Skriptes hängt vom verwendeten Datenbank-System sowie den hinterlegten Daten ab, das heißt, es müssen nicht in allen Feldern Werte enthalten sein. Selbstverständlich ist es auch möglich, die Tabellen-Informationen mit Hilfe der Methode fetchrow_array abzufragen. Vor allem dann, wenn Sie nicht alle verfügbaren Informationen benötigen, erscheint die vorgestellte Lösung übersichtlicher. Im vorliegenden Kapitel haben wir uns ausführlich mit Datenbanken und Perls Datenbankschnittstelle beschäftigt. Damit ist das Thema Datenbanken für dieses Buch allerdings noch nicht abgeschlossen. Vielmehr werden wir an verschiedenen Stellen auf das vorgestellte Wissen zurückgreifen. Denn wie gesagt, eine Anwendung, die nicht auf die eine oder andere Art mit Datenbanken zusammen arbeiten muss, ist heutzutage kaum vorstellbar.

8

CGI-Programmierung mit Perl

Wenn Programmierer über Perl sprechen, meinen Sie in der Regel die Verbindung von Perl und CGI. Auch wenn Perl, wie wir Ihnen in diesem Buch zeigen, viel mehr kann als CGI, so ist die leichte Verknüpfbarkeit dieser beiden Komponenten sicherlich einer der Gründe für die weite Verbreitung von Perl. Eine der bekanntesten Aussagen, die dieses Verhältnis illustrieren ist die, dass "Perl das Klebeband sei, welches das Internet zusammenhält". In diesem Kapitel werden wir uns nun ausführlich mit der Webserver-Programmierung mit Perl und CGI beschäftigen. Dabei werden wir auch randliche Themen streifen, so dass Sie nach der Lektüre dieses Kapitels in der Lage sind, leistungsfähige Webapplikationen zu erstellen. Im ersten Abschnitt dieses Kapitels erläutern wir, wie Sie Ihren Apache-Webserver Linux- (Unix-) und Windows-Systemen installieren und konfigurieren.

auf

Wir werden uns dann mit der Frage beschäftigen, was CGI ist und wie es funktioniert. Da CGI-Skripten ohne HTML 1 -Kenntnisse kaum sinnvoll einzusetzen sind, folgt ein kurzer HTML-Exkurs, der Sie mit den Grundlagen dieser Sprache vertraut macht. Anschließend stellen wir CGI anhand eines einfachen Einführungsbeispiels, quasi als Appetithappen, kurz vor. Im folgenden Abschnitt stellen wir Ihnen ein etwas umfangreicheres Beispiel vor, das einige fortgeschrittene Techniken darstellt, mit denen Sie die Arbeit mit Perl/CGI vereinfachen können. Vor diesem Hintergrund können wir uns intensiv mit dem Einsatz und den Möglichkeiten von Perl/CGI befassen. Zunächst stellen wir dar, wie Sie mit Hilfe von Perl/CGI und HTML Eingabeformulare erstellen und auf Eingaben des Benutzers reagieren können. Daran anschließend erklären wir, wie Sie Ihre Anwendungen mit Hilfe von Templates übersichtlicher und vor allem modularer gestalten können. Die Vorteile von Templates werden Sie vor allem bei größeren Projekten und bei der Arbeit in Teams zu schätzen lernen. Mit Hilfe von Perl und CGI können Sie dynamisch Grafiken in Ihre Webanwendung einbetten und Diagramme erzeugen. Wie dies funktioniert, erklären wir im letzten Teil dieses Kapitels. Natürlich werden reichlich Gebrauch von der im vorangegangenen Kapitel besprochenen Datenbankschnittstelle machen, da Webanwendungen ohne eine Datenbank-Verbindung der einen oder anderen Art kaum mehr vorstellbar sind. 1

Hyper- Text-Markup-Language

- eine Seitenbeschreibungssprache zur Erstellung von Internetseiten

136

8.1

8 CGI-Programmierung mit Perl

Einrichten und Konfigurieren des Apache-Webservers

In unseren Beispielen setzen wir voraus, dass auf Ihrem System ein Webserver installiert und aktiv ist. Wir verwenden den Apache-Webserver aus mehreren Gründen. Zunächst ist Apache mittlerweile der am meisten eingesetzte Webserver weltweit. Der ApacheWebserver liegt in verschiedenen Versionen für die wichtigsten Betriebssysteme vor. Wir werden uns hier allerdings auf die Linux- und die Windows-Version beschränken. Das beste an Apache ist allerdings, dass Sie die Software kostenlos auf der folgenden Website beziehen können: http://www.apache.org/dist/httpd/binaries. Auf der Homepage http://www.apache.org finden Sie auch umfangreiche Dokumentationen und Informationen zu dieser Anwendung. Leider ist die Installation dieser Software auf Ihrem Rechner nicht ganz so einfach, wie die eines Textverarbeitungs-Programmes. Daher beschreiben wir kurz diesen Prozess.

8.1.1

Den Apache-Webserver auf einem Linux-System installieren

Linux-Anwender können sich glücklich schätzen 2 , da der Apache Webserver auf diesem Betriebssystem in der Regel automatisch installiert und konfiguriert wird, so dass nur eine kleine Anzahl von Einstellungen für den Anwender übrig bleiben. Das Starten des Webservers kann von Linux-Distribution zu Distribution leicht abweichend sein. Auf meinem RedHat 9.0-System lautet der entsprechende Befehl 3 /etc/init.d/httpd Start

Je nach Distribution kann der benötigte Befehl auch /etc/init.d/apache Start

lauten. Alle Linux-Distributionen besitzen zudem Tools, mit denen es möglich ist, den Webserver gleichzeitig mit dem Betriebssystem zu starten. Bei RedHat 9.0 finden Sie diesen Punkt im Menü Systemeinstellungen | Servereinstellungen I Dienste. Bei einem SuSE-System nehmen Sie die entsprechende Änderung mit Yast2 im RunlevelEditor vor, den Sie im Bereich System finden. Starten Sie hier den Apache-Webserver im Runlevel 5. Auf einem Debian-System können Sie das SysVinit-Tool verwenden. Auch hier sollte Apache im Runlevel 5 gestartet werden.

2 Nach Meinung des Autors natürlich nicht nur aus diesem Grund, aber das ist natürlich die persönliche Ansicht des Autors. 3 Beachten Sie, dass Sie zum Starten und Konfigurieren des Webservers Administrator-, also rootRechte benötigen! Wenden Sie sich also im Zweifelsfall an Ihren Systemadministrator.

8.1 Einrichten und Konfigurieren des Apache-Webservers

8.1.2

137

Den Apache-Webserver auf einem Windows-System installieren

Zunächst müssen Sie sich von der oben angegebenen Adresse das aktuelle Release des Apache-Webservers herunterladen. Sie finden dort die Versionen 1.3.x und 2.0.x 4 . Auf diversen Internetseiten und bei Informationsdiensten liest man hier und da, die Version 2.x habe noch Sicherheitslücken, daher wird oft zur Installation der Version 1.3.x geraten. Mittlerweile scheinen diese aber weitgehend beseitigt zu sein 5 . Nachdem Sie das Installationspaket aus dem Internet geladen haben, können Sie den Webserver, wie jede andere Windows-Anwendung auch, auf Ihrem Rechner installieren. Während des Installationsvorganges, bei dem Sie weitgehend die Vorgaben übernehmen können, erscheint ein Bildschirm, der drei Angaben von Ihnen fordert 6 : • N e t w o r k D o m a i n : Sofern Sie auf einem Einzelplatzrechner arbeiten, was zum Nachvollziehen dieses Stoffes auf jeden Fall sinnvoll ist, geben Sie hier l o c a l h o s t ein. • S e r v e r N a m e : Auch hier tragen Sie bei einem Einzelplatzrechner l o c a l h o s t ein. • A d m i n i s t r a t o r ^ E m a i l - A d d r e s s : In diesem Feld tragen Sie Ihre E-Mail-Adresse ein 7 . Nach ein paar Klicksen auf die Ok-Taste sollte der Apache-Webserver auf Ihrem System installiert sein. Um den Webserver auf einem Windows-System zu starten, wählen Sie im Start-Menü in der Rubrik Programme, Apache den Eintrag Start Apache aus.

8.1.3

Test Ihrer Apache-Installation

Um zu testen, ob Apache auf Ihrem System korrekt installiert wurde und aktiv ist, starten Sie Ihren Lieblingsbrowser und geben die folgende U R L ein. http://localhost Sollte der Apache Webserver gestartet sein, werden Sie mit einem Begrüßungsbildschirm belohnt. Sollten Sie stattdessen eine Fehlermeldung erhalten, gehen Sie die vorhergehenden Schritte nochmals durch. Unter Linux sollten Sie eventuell das Werkzeug zum Einrichten von Software starten und überprüfen, ob die Option Webserver oder bei einem Debian-System Apache aktiviert ist. Sollte dies nicht der Fall sein, holen Sie dies jetzt nach und versuchen es erneut. 4Zum

Zeitpunkt des Schreibens, Frühjahr 2003. Nur damit Sie gewarnt sind. 6 Dieser Bildschirm hat den Autor in seiner Apache-Anfangszeit schier zum Wahnsinn getrieben! Niemand schien es für Wert zu erachten, den Einsteiger darüber aufzuklären, welche Angaben hier gemacht werden müssen. 7 Gell, darauf wären Sie nie gekommen? 5

138

8 CGI-Programmierung mit Perl

ßatei IjMiWtcn ansieht Sehe Lesezeichen loots Feriste» fcWfe Z # HTML-Footer ausgeben print ""; print "";

sub argumenteExtrahieren { # Die an die Anwendung uebergebenen Argumente extrahieren # und als Hash zurueck-liefern

my $aufruf ; my '/»argumente; # Den Aufruf-String in die locale Variable aufruf einlesen read(STDIN, $aufruf, $ENV{'CONTENT.LENGTH'» ; # Alle Parameter-Wert-Paare in Array Zwischenspeichern my 0argumente = split(/&/, $aufruf); # und in Hash schreiben foreach $argument (®argumente) { # zerlegen my ($schluessel, $wert) = split(/=/, $argument);

149

150

8 CGI-Programmierung mit Perl # u n d anhaengen $argumente{$sch.luessel} = $wert;

> # Hash zurueck-liefern return '/,argumente ;

>

Nachdem Sie das Skript abgetippt haben, speichern Sie es in einem Verzeichnis, auf das ein ScriptAlias verweist. Nun müssen Sie die Ausführungsrechte der Datei ändern, indem Sie die Anweisung. chmod +x einfuehrung.cgi

Q*la

Bnwtenen

Zu^ck " -üSianseitc

inîidrt fiehe l^srackhen

Nii^Ltl

'

Irais

£etist« w-tc

.t MI[I ". . t^.v i.i

-

I_c,tir?i(fxn ¿ R e d Hit Nemo* _jSjohc" J ' j r ^ t i J p i o l u d s Jjlraimrg

l infüliruii^sî>fispi print "$schluessel"; unless ($ENV{$schluessel> eq "") { print "$ENV{$schluessel}"; } else { print " "; > print ""; > # Footer ausgeben print ODBC
CINPUT TYPE="RADIO" NAME="dbTreiber" VALUE="MySQL">MySQLcBR> CINPUT TYPE="RADIO" NAME="dbTreiber" VALUE="PostgreSQL">PostgreSQLcBR>

Da Radiobuttons keine Beschriftungs- oder Labeleigenschaft besitzen, muss diese als HTML-Text außerhalb des Input-Tags angegeben werden. Dabei besteht keine Verbindung zwischen dem jeweiligen Radiobutton und der Beschriftung. Es genügt daher nicht, wie bei anderen Oberflächen-Toolkits, auf die Beschriftung zu klicken, um den Zustand des Buttons zu ändern. Checkboxen. Checkboxen sind den Radiobuttons verwandt, allerdings ermöglichen Sie es dem Benutzer, mehrere Werte aus einer Liste auszuwählen. Checkboxen haben üblicherweise unterschiedliche Namen. CINPUT TYPE="CHECKBOX" NAME="notnull" VALUE="notnull"> Kein NullwertCBR> CINPUT TYPE="CHECKBOX" NAME="primaer" VALUE="primaer"> PrimärschlüsselcBR> CINPUT TYPE="CHECKBOX" NAME="index" VALUE="index">IndexCBR> 26

U m beispielsweise eine Datenbank-Applikation zu erstellen.

158

8 CGI-Programmierung mit Perl

Diese Gruppe von Checkboxen könnte Bestandteil einer Routine zum Erstellen von Datenbank-Tabellen sein. Ein Primärschlüssel-Attribut einer Tabelle würden Sie erzeugen, indem Sie die Checkboxen mit den Namen n o t n u l l und primaer ankreuzen. Da Checkboxen genau wie Radiobuttons keine Beschriftungseigenschaft besitzen, müssen wir auch hier den Beschreibungstext als reinen HTML-Text angeben. Hier besteht ebenfalls keine Verbindung zwischen der Beschreibung und der Checkbox. Eine nicht empfehlenswerte Möglichkeit zur Arbeit mit Checkboxen besteht darin, für alle Felder einer Gruppe den gleichen Namen zu verwenden. Die Daten werden dann in der folgenden Weise übertragen. tableattribut=Null-Wert+nicht+erlaubt&tableattribut=primaer-Schluessel

Diese Art der Datenübermittlung ist vor allem dann sinnvoll einzusetzen, wenn die Parameter in ein Array eingelesen werden sollen. Bei Radiobuttons und Checkboxen können Sie über das Attribut angeben, ob die entsprechende Box beim Anzeigen im Browser aktiviert ist oder nicht. Im folgenden Beispiel legen wir ODBC als den Vorgabe-Datenbanktreiber fest. CINPUT TYPE="RADIO" NAME="dbTreiber" VALUE="ODBC" CHECKED>ODBC
CINPUT TYPE="RADIO" NAME="dbTreiber" VALUE="MySQL">MySQL
PostgreSQL


Wie Sie sehen, wird der Option CHECKED kein Wert zugewiesen. Das folgende Beispiel zeigt die Anwendung von Radiobuttons und Checkboxen.: #!/usr/bin/perl -w # radio.cgi use CGI::Carp qw(fatalsToBrowser); print «HTML.ENDE; Content-type: text/html

Radiobuttons und Checkboxen

8.9 Eingabe-Formulare mit Perl und CGI erstellen

159

Radiobuttons

0DBC Kein Nullwert MySQL CTDXINPUT TYPE= "CHECKBOX" NAME="primaer" VALUE="pr imaer"> Primärschlüssel CTDXINPUT TYPE="RADIO" NAME="dbTreiber" VALUE="PostgreSQL">PostgreSQL Index
Checkboxen


HTML.ENDE Haben Sie das CGI-Skript abgetippt, führen Sie es mit folgender Anweisung aus. http://localhost/cgi-bin/radio.cgi Die Ausgabe des Browsers sehen Sie in Abbildung 8.5. S u b m i t - B u t t o n s . Hat der Benutzer die Arbeit an einem Formular abgeschlossen, werden die Daten aus dem Formular ausgelesen, formatiert und an den im ACTION-Attribut des FORM-Tags festgelegten URL abgesendet. Ein Submit-Button wird folgendermaßen erzeugt: CINPUT TYPE="SUBMIT" NAME="cmdVerbinden" VALUE="Verbindung zur Datenbank"> Dem Attribut VALUE kommt dabei eine besondere Bedeutung zu. Zum einen legt dieses Attribut den Text fest, der auf dem Button angezeigt wird. Zum anderen wird dieser Wert an das aufgerufene Skript übertragen. Über das Auslesen dieses Wertes in einer Auswahlstruktur Ihres Skriptes ist es möglich, die vom Anwender gedrückte Schaltfläche zu ermitteln. Dieser Umstand ist vor allem dann von Bedeutung, wenn Ihr Formular mehrere Schaltflächen aufweist. Leider ist es bei Schaltflächen in HTML nicht möglich, die Breite festzulegen, was gerade bei Formularen, die viele Schaltflächen aufweisen, sehr störend und hässlich wirken kann.

160

8 CGI-Programmierung mit Perl

Abb. 8.5: Radiobuttons und Checkboxen

R e s e t - B u t t o n s . Bei Reset-Buttons handelt es sich um eine besondere Art von Schaltflächen. Sie dienen dazu, ein HTML-Eingabeformular zurückzusetzen, das heißt, die angezeigten oder bereits eingegebenen Werte werden wieder gelöscht. Reset-Buttons werden wie folgt erzeugt.

8.9.3

Mehrzellige Textfelder

In Eingabe-Formularen kann es notwendig sein, mehrzelligen Text einzugeben, beispielsweise dann, wenn Sie ein E-Mail-Formular erstellen oder dem Anwender ermöglicht werden soll, längere Kommentare einzugeben. Hierfür besitzt HTML ein eigenes Steuerelement, das mit Hilfe des Tags erstellt wird. Dieses Tag besitzt im Gegensatz zu allen bisher besprochenen Tags auch ein End-Tag , das in jedem Falle auch angegeben werden muss. Ein mehrzelliges Textfeld wird auf folgende Art und Weise erzeugt.

Mit dieser Anweisung wird ein Textbereich erzeugt, der eine Breite von 60 Zeichen und eine Höhe von 20 Zeilen besitzt. Die Spaltenbreite des Textbereiches ist allerdings nur dann verlässlich, wenn Sie eine Schriftart mit konstanter Breite verwenden. Bei der Übertragung an das Skript werden die Zeilenumbrüche standardmäßig mit versendet. Dieses Verhalten können Sie allerdings mit dem Attribut beeinflussen 27 . Für können drei Werte angegeben werden: 27 Beachten Sie dabei aber, dass dieses Attribut nicht zum HTML-Standard gehört! Es wird allerdings derzeit von allen gängigen Browsern unterstützt.

8.9 Eingabe-Formulare mit Perl und CGI erstellen

161

• VIRTUAL: Der Text wird in der Browseranzeige umgebrochen, allerdings ohne diese Umbrüche versendet. In der Regel sollten Sie diese Variante verwenden. • PHYSICAL: Der Text wird inklusive der Zeilenumbrüche an den Server gesendet. Bei dieser Variante müssen Sie unbedingt beachten, dass nicht alle Betriebssysteme den Zeilenumbruch in der gleichen Art und Weise darstellen. So verwenden UNIX/LINUX-Systeme den Escape-Code \n zur Darstellung des Zeilenumbruchs, Windows-Systeme die Escape-Sequenz \r\n und Mac-Systeme verwenden \n\r, um den Zeilenumbruch darzustellen. Sollten in Ihrer Anwendung die Zeilenumbrüche mit gesendet werden, müssten Sie diesem Punkt Aufmerksamkeit schenken und die verschiedenen Varianten mit Hilfe eines regulären Ausdruckes in eine allgemeine Form umwandeln. • NONE: Diese Variante entspricht dem Fehlen des Attributes und bewirkt, dass alle Zeilenumbrüche weggelassen werden, und der Text als eine Zeile an den Server übermittelt wird.

8.9.4

Das -Tag

Mit Hilfe des -Tags können Sie zwei unterschiedliche Arten von Auswahllisten erzeugen. Zum einen können Sie aufklappbare Listen erstellen, bei denen stets nur der ausgewählte Wert angezeigt wird. Klicken Sie auf die Schaltfläche am rechten Rand dieses Feldes, erscheint die Auswahlliste nach unten ausgeklappt. Zum anderen können Sie scrollbare Auswahllisten erzeugen, die stets mehrere der verfügbaren Optionen anzeigen. Das -Tag besitzt, wie auch das TEXTAREA-Tag, ein Endtag . Der folgende CGI-Code erzeugt eine aufklappbare Liste. #!/usr/bin/perl -w # klappliste.cgi use CGI::Carp qw(fatalsToBrowser); print «HTML.ENDE; Content-type: text/html

Klappliste

Datenbanktreiber: Primäschlüssel Indexfeld

8.9 Eingabe-Formulare mit Perl und CGI erstellen

163

HTML.ENDE

Diese Variante verwendet das Attribut VALUE, um für jeden Eintrag einen kurzen und prägnanten Wert festzulegen, der dem Webserver übergeben wird und in einem CGISkipt einfacher zu testen ist, als die für den Anwender leichter verständlichen "Langversionen". Zusätzlich verwendet das vorstehende Beispiel die Option MULTIPLE, mit deren Hilfe es bei scrollbaren Listen möglich ist, mehr als eine Option auszuwählen. Hierzu muss allerdings das Attribut SIZE auf einen Wert größer als 1 gesetzt werden.

8.9.5

Formulardaten auslesen

Nachdem wir nun die im HTML-Standard verfügbaren Formularfelder besprochen haben, stellt sich uns die Frage, wie die übergebenen Formular-Daten vom CGI-Skript gelesen werden können. Die Antwort daraufhaben wir bereits diskutiert, sie soll hier nur kurz resümiert werden. Die Formulardaten werden der Umgebungsvariablen $ENV' QUERY_STRING' übergeben, sofern die Request-Methode GET verwendet wurde. Dieser String enthält, wie bereits dargestellt, die URL inklusive der übergebenen Parameter in folgender Form.

http://localhost/cgi-bin/buch/einfuehrung.cgi?name=Daisy+Duck &password=pumuckl

Haben Sie hingegen die Request-Methode POST verwendet, können Sie zum Auslesen der Formulardaten die bereits in unserem Einführungsbeispiel verwendete Funktion argumenteExtraliieren verwenden.

8.9.6

Ein SQL-Monitor

Nachdem wir uns nun ausgiebig mit CGI und den Formular-Elementen, die HTML zur Verfügung stellt, beschäftigt haben, ist es an der Zeit, ein etwas ausführlicheres Beispiel vorzustellen, welches das erworbene Wissen anwendet. Üblicherweise sollte an dieser Stelle ein E-Mail-Programm oder ein Gästebuch stehen. Wir haben uns allerdings gegen diese Optionen entschieden, da Sie hierfür im Internet ausreichend Beispiele finden. Stattdessen stellen wir Ihnen einen SQL-Monitor vor, mit dem Sie sich mit beliebigen Datenbanken verbinden und an diese beliebige SQL-Statements absenden können. Dabei kann die Anwendung zwischen Abfragen, die ein Resultset liefern, und AktionsAbfragen, die kein Resultset liefern, unterscheiden. Wir werden also in diesem Beispiel auch das in unserem DBI-Kapitel erworbene Wissen anwenden.

164

8 CGI-Programmierung mit Perl

PERL/CG I-SQL-Monitor

NQt -Statement; ilLILT l i n ^ t l f t . r . i " I I lit cit_er»clt_j«hr xai(_n{aie WOH kunii-LjtL.n ¡WIR .KUX im >i tumil I ' I ,.rri"t irei - td_eele*ian.kuenetleT_id D M : i • sun|j_crliil ion Ä ciLrtldtien.cd_id - - ¡ J -'I II II

Aktiim MIM'C (liefert kein ResiHwO F.ymidaF euiuitselicn

iL ^

/.

' 3 connect("$treiber:database=$datenbank", $user, $passwort); # konnte Datenbankverbindung erstellt werden? if ($dbh) { # Testen, ob Aktionsabfrage if ($action eq 'action') { # Aktionsabfrage ausfuehren my $success = $dbh->do($sql); unless ($success) { # Aktion nicht erfolgreich $platzhalter{'meidung'} = "Bei der Ausführung der Aktionsabfrage " . "trat ein Fehler auf"; vorlageAusgeben('meidung.html', \'/,platzhalter); } eise { # Aktion erfolgreich $platzhalter{'meidung'} = "Das folgende Kommando wurde ausgeführt:
" . "$sql

"; vorlageAusgeben('meidung. html', V/,Platzhalter) ;

> } eise { # Abfrage liefert Resultset # Statement-Handle vorbereiten my $sth = $dbh->prepare($sql); if ($sth) { htmlHeadAusgebenO ; print "$sql"; # Abfrage ausfuehren $sth->execute(); if ($sth->rows() > 0) -C # Tabellenkopf erstellen print ""; # Zeile mit Spaltenbeschriftungen ausgeben print ""; # Spaltennamen ermitteln und ausgeben foreach my $spaltenNamen (®{$sth->{'NAME'») { print ""; # Resultset durchlaufen und Daten ausgeben # Zaehlvariable initialisieren my $lineCount = 0; while (my QresultSet = $sth->fetchrow_array()) { # neue Tabellenzeile einlegen und einfaerben

8.10 Perl/CGI-Anwendungen mit Hilfe von Templates modularisieren $lineCount++; if ($lineCount '/. 2 == 0) { print "\n"; } eise { print "\n";

> # Elemente ausgeben foreach my $wert (SresultSet) { unless ($wert eq '') { print ""; } eise { print "";

>

>

# Zeile beenden print "";

> htmlFootAusgebenO ;

} } eise { # Aktion nicht erfolgreich $platzhalter{'meidung'} = "Das Statement konnte nicht vorbereitet werden!"; vorlageAusgebenOmeldung.html' , \'/,platzhalter) ;

>

>

}• eise { # Datenbank-Verbindung konnte nicht erstellt werden $platzhalter{'meidung'} = "Datenbank-Verbindung konnte nicht erstellt werden!"; vorlageAusgeben( 'meldung.html', \*/,platzhalter) ;

sub htmlHeadAusgeben { print « H E A D . E N D E ;

SQL-Monitor

PERL/CGI-SQL-Monitor

HEAD.ENDE >

181

182

8 CGI-Programmierung mit Perl

sub htmlFootAusgeben { print « F O O T . E N D E ;

FOOT.ENDE } sub vorlageAusgeben { # Vorlagenname und Werte-Hash uebernehmen my ($vorlage, $daten) = # Vorlagen-Datei oeffnen open (IN, $vorlage) or die "Kann Vorlage nicht oeffnen!"; # HTTP-Header ausgeben #print "Content-type: text/html\n\n"; # Vorlage zeilenweise lesen und Platzhalter ersetzen while (my $zeile = ) { # Platzhalter ersetzen $zeile =~ s/\$(\w+)\$/$$dateni$l}/g; print $zeile;

> }

8.11

Perl/CGI und Grafik

Bisher haben wir mit unseren CGI-Skripten ausschließlich dynamisch generierte Textdateien ausgegeben. Die Erfolgsstory des Internets beruht aber vor allem auch auf der Fähigkeit, Grafiken auf Webseiten darzustellen. Es wundert daher kaum, dass Ihnen CGI nicht nur die Möglichkeit bietet, Grafiken auf Ihren Webseiten darzustellen, sondern diese auch dynamisch zu erzeugen. Die Einsatzgebiete von dynamisch erzeugten Grafiken sind schier unüberschaubar. Von besonderer Bedeutung scheint allerdings die Möglichkeit zu sein, dynamisch erzeugte Diagramme in Webseiten einzubetten. Die Fähigkeiten von Perl/CGI gehen natürlich weit darüber hinaus und letztendlich hindert Sie niemand daran, Ihr eigenes webbasiertes Geographisches Informationssystem mit Perl und CGI zu erstellen. In diesem Abschnitt werden wir Ihnen zeigen, wie Sie einerseits "statische" Grafiken, wie Photographien oder Skizzen, und andererseits dynamisch erzeugte Grafiken anzeigen bzw. erzeugen können.

8.11 Perl/CGI und Grafik

8.11.1

183

Das Modul GD

Alle Beispiele, die wir in diesem Abschnitt vorstellen, verwenden das Modul GD. Besorgen Sie sich also bitte, falls noch nicht geschehen, dieses Modul für die von Ihnen verwendete Perl Distribution und installieren dieses wie weiter oben beschrieben. Das Objektorientierte Modul GD stellt Ihnen drei Klassen zur Verfügung: • GD: ¡Image: Methoden zum Erstellen und Anzeigen von Grafiken • GD: :Font: Font-Eigenschaften, die Informationen für das Schrift-Rendering enthalten. • GD: ¡Polygon: Methoden zum Erzeugen von Polygonen 35 .

8.11.2

Grafiken mit Perl/CGI laden und anzeigen

Der einfachste Weg, mit Grafiken zu arbeiten, ist es sicherlich, vorhandene Grafiken auf Ihren Webseiten anzuzeigen. Die im Internet am häufigsten verwendeten Grafikformate sind GIF und JPEG. GIF-Dateien lassen sich vor allem für kleinere Grafiken mit nur wenigen Farben einsetzen. Ein Vorteil von GIF-Grafiken ist, dass mit Hilfe von GIFs einfache Animationen erstellt werden können. Zudem können GIF-Dateien transparent dargestellt werden. JPEG-Grafiken eignen sich vor allem für hochauflösende Darstellungen wie Fotografien. In neuerer Zeit findet auch das /WG-Format zunehmende Verbreitung. PNG wurde als Nachfolger zu GIF entwickelt36 und besitzt eine Reihe von zusätzlichen Eigenschaften. So unterstützt das PNG-Format drei verschiedene Modi für Grafiken mit einer begrenzten Farbpalette (bis zu 256 Farben), 16-Bit-Graustufenbilder und 48-Bit-Echtfarbenbilder. Das PNG-Format unterstützt auch die Transparenz. Um mit Hilfe von Perl und CGI Grafiken darstellen zu können, muss ein neuer Content-type, nämlich image/jpeg für JPEG-Dateien oder image/png angegeben werden. Im folgenden Beispiel laden wir eine PNG-Datei und zeigen diese im Browser-Fenster an. #!/usr/bin/perl -w # grafikl.cgi # Scalar zum Einlesen der Grafik my $segment = ""; # Segmentgroesse festlegen m y $segmentGroesse = 4_096; # Bilddatei-Namen setzen - Pfad muss absolut eingegeben w e r d e n m y $bild = "/var/www/cgi-bin/buch/pictures/lillyl.png"; 35 36

Dieses schöne aus dem Griechischen stammende Wort bedeutet Vieleck. Vor allem deswegen, weil das GIF-Format nicht mehr frei ist.

184

8 CGI-Programmierung mit Perl

# Header ausgeben print "Content-Type: image/png\n\n"; # Binaerer Modus binmode STDOUT; # Bilddatei oeffnen open (IN, $bild); # Ueber read-funktion ausgeben while(read(IN, $segment, $segmentGroesse)) { print $segment; > close IN;

Die Grafikdatei in unserem Beispiel wird segmentweise eingelesen und mit Hilfe der Funktion read direkt an den Browser gesendet. Beachten Sie in unserem Beispiel, dass der Pfad der Bilddatei absolut angegeben wird. Eine Pfadangabe relativ zum cgi-binVerzeichnis führt zu einer Fehlermeldung des Browsers. In der Regel möchten Sie allerdings nicht Grafiken in der gezeigten Form anzeigen, sondern die Bilder in das umgebende HTML-Dokument einbinden. Dies ist mit Hilfe von CGI in der gleichen Weise möglich, die wir weiter oben im Rahmen unseres HTMLExkurses gesehen haben. Betrachten Sie hierzu das folgende Beispiel. #!/usr/bin/perl -w # grafik2.cgi print "Content-Type: text/html\n\n"; print « H T M L . E N D E ;

Grafik ausgeben

Grafik ausgeben

Das war die Grafik

185

8.11 Perl/CGI und Grafik

HTML_ENDE

Beachten Sie bei diesem Skript, dass wir im -Tag nicht den Pfad zur Bilddatei angeben, sondern wir rufen innerhalb dieses Tags unsere CGI-Anwendung aus dem ersten Beispiel auf, um die Grafik anzuzeigen. Wir haben zudem innerhalb dieses Tags dafür gesorgt, dass unsere Grafik nicht übermäßig groß erscheint. Hierzu haben wir die Breite über das Attribut und die Höhe über das Attribut auf absolute Werte gesetzt. Nach Ausführen des Skriptes graf ik2. cgi erhalten wir die in Abbildung 8.9 dargestellte Ausgabe. BaM» a&aibcttin

z,ik

Jn sieht Gehe l_ese«Hchcn loch

Ew«'« tjWc

*

Nei/taden "X,, * W^ta««*^*»** c9

'^Staffierte

,tLfsei»ch« ¿ R t d Hat N»worti ^Support _j5hop .^Products _

3&S

Grafik ausgeben

Das war die Grafik

¿- LS

Abb.

Fertig

8.9: Eine Grafik in ein HTML-Dokument

eingebettet

ausgeben.

Im vorigen Beispiel haben wir das Foto unserer Hündin Lilly auf eine darstellbare Größe gebracht. Die vorgestellte Lösung dieses Problems ist allerdings nicht ideal, da wir in der Regel nicht vorher wissen, wie breit und hoch die anzuzeigende Grafik tatsächlich ist, und in welchem Verhältnis Breite und Höhe zueinander stehen. Möchten Sie beispielsweise ein Fotoalbum erstellen, benötigen Sie eine flexiblere Lösung, um die Grafiken auf ein annehmbares Format zu bringen. Um dieses Problem zu lösen, verwenden wir das Modul GD, das wir im folgenden Kapitel ausgiebig nutzen werden und ein wenig Arithmetik. Wir definieren ein feste Breite, die die Grafik in der Anzeige haben soll und berechnen anschließend die Höhe der Grafikanzeige im Verhältnis zur Breite. Um die Originalbreite und Höhe der anzuzeigenden Grafik zu ermitteln, verwenden wir die Methode getBounds des GD-Objektes.

186

8 CGI-Programmierung mit Perl

#!/usr/bin/perl -w # grafik6.cgi use GD; print "Content-Type: text/html\n\n"; # Groesse der Grafik ermitteln my $grafikdatei = "/var/www/cgi-bin/buch/pictures/lillyl.png"; my $grafik = GD::Image->newFromPng($grafikdatei); my ($breite, $hoehe) = $grafik->getBounds(); # Neue Breite und Hoehe berechnen my $breite_neu = 400; my $faktor = $breite / $breite_neu; my $hoehe_neu = int($hoehe / $faktor); print « H T M L _ E N D E ;

Graf ik ausgeben

Grafik ausgeben

Breite : $breite_neu Hoehe: $hoehe_neu


Das war die Grafik

HTML.ENDE

Im letzten Beispiel erzeugen wir zunächst eine neue Instanz der Klasse GD:: Image. Über die Methode g e t B o u n d s O ermitteln wir anschließend die Originalmaße der Grafik. Mit zwei Divisionen berechnen wir dann die neue Höhe der Grafik. Die ermittelten Werte verwenden wir weiter unten im -Tag zur Ausgabe der Grafik. Mit dieser Art von Berechnung können Sie auch leicht sogenannte Thumbnails37 Fotoalbum-Anwendungen und ähnliches erstellen.

für

3 7 D e r deutsche Name für Thumbnails ist Daumennagel-Skizze. Diese bezeichnen in der Grafik extrem verkleinerte grob gezeichnete Skizzen, die den Zweck haben, dem Betrachter die Raumaufteilung und Farbverteilung der späteren Grafik zu verdeutlichen. Daumennagel-Skizzen werden vor allem in der Entwurfsphase eingesetzt.

187

8.11 P e r l / C G I und Grafik

8.11.3

Dynamische Grafiken mit Perl/CGI erstellen

D a s Anzeigen von Grafiken ist zwar eine recht schöne Sache, und Sie können mit den bisher gezeigten Vorgehensweisen Fotoalben oder ähnliches erstellen, aber so richtig "dynamisch" sind die Grafiken eigentlich noch nicht. In diesem Abschnitt werden wir Ihnen erläutern, wie Sie mit Hilfe des Moduls GD wirklich dynamische Grafiken erzeugen können. In unserem ersten Beispiel erstellen wir eine einfache Grafik, die einen Rahmen und einige Linien auf dem Bildschirm ausgibt.

#!/usr/bin/perl -w # Grafik3.pl use GD; # ein neues Grafikobjekt erzeugen # Groesse 400 x 400 Pixel my $grafik = new GD::Image(400, 400); # Einige Farben definieren my $schwarz = $grafik->colorAllocate(0 , 0 ,0); my $blau = $grafik->colorAllocate(0, 0, 255) my $rot = $grafik->colorAllocate(255, 0, 0) my $gruen = $grafik->colorAllocate(0, 255, 0) my $weiss = $grafik->colorAllocate(255, 255, 255); my $gelb = $grafik->colorAllocate(255, 255, 0); my $violett = $grafik->colorAllocate(255, 0, 255); # Einige Linien zeichnen $grafik->line(0, 0, 399, $grafik->line(0, 0, 399, $grafik->line(0, 0, 399, $grafik->line(0, 0, 399, $grafik->line(0, 0, 399,

399, 349, 299, 249, 199,

$weiss); $weiss); $weiss); $weiss); $weiss);

# ein gefuelltes Rechteck $grafik->rectangle(10, 200, 60, 260, $rot); $grafik->fill(ll, 201, $rot); # und ein nicht gefuelltes Rechteck $grafik->rectangle(50, 240, 200, 300, $gruen); # und noch ein Kreis $grafik->arc(100, 200, 90, 90, 0, 360, $gelb);

188

8 CGI-Programmierung mit Perl

# und ein Kreisbogen $grafik->arc(300, 150, 150, 150, 0, 220, $blau); # zum Schluss eine gefuellte Ellipse $grafik->arc(260, 70, 150, 70, 0, 360, $violett); $grafik->fill(260, 70, $violett); # Http-Header ausgeben print "Content-type: image/jpeg\n\n"; # Ausgabe von Binaerdaten binmode STD0UT; # Grafik in der hoechst moeglichen Qualitaet ausgeben print $grafik->jpeg(100);

Nachdem Sie das Skript abgetippt und gestartet haben, erhalten Sie die in Abbildung 8.10 dargestellte Ausgabe. Qatei Bearbeiten Ansicht Zurück " •ft Suitsetie

^

Abb.

8.10:

Gehe lesezetchen lods

Neusen

4 Lesezeichen ¿ R e d Hjt Netwatt

äi m

Eenster fcfitf«

A h"P tfoc^hosVcgMjiiVbuctVg'atlkJ.cgi

R&j

Support _jShop _^Praducn ^Tramtng

Fertig

Eine dynamisch

erstellte

Grafik.

Zunächst erzeugen wir in unserem Skript ein neues GD:: Image-Objekt, welchem wir den Namen $graf ik geben. Alle weiteren Manipulationen an der Grafik, die wir im Folgenden erstellen, geschehen ausschließlich über Methoden dieses Objektes. Wir legen die Größe der Grafik dabei auf eine Breite und Höhe von jeweils 400 Pixeln (Bildpunkten) Im nächsten Schritt definieren wir eine Reihe von Farben für unsere Zeichnungsobjekte. Farben werden mit Hilfe des ÄGß-Formates beschrieben. In diesem Format besteht

8.11 Perl/CGI und Grafik

189

jede Farbe aus einem Rot-, einem Grün- und einem Blau-Anteil. Für jede dieser Farben geben wir den Anteil mit einer Zahl zwischen 0 und 255 an 38 . Wir definieren zunächst die drei Grundfarben, indem wir jeweils einen Faktor auf den Wert 255 setzen. Schwarz und Weiß entstehen, wenn man alle Werte auf 0 bzw. auf 255 setzt. Schließlich erstellen wir noch die beiden Mischfarben Gelb und Violett, indem jeweils zwei Werte auf 255 gesetzt werden. Nun ziehen wir einige Linien in unsere Zeichenfläche. Dabei müssen Sie beachten, dass der Nullpunkt der Zeichnung nicht, wie Sie das aus dem Mathematik-Unterricht gewohnt sind, in der linken unteren, sondern in der linken oberen Ecke liegt! Die Werte der XAchse steigen also nach rechts an und die Werte der Y-Achse nach unten. Um eine Linie zu zeichnen, verwenden wir die Methode l i n e ( ) des Grafik-Objektes. Diese Methode besitzt folgende allgemeine Syntax. $ g r a f i k - > ( x l , y l , x2, y2, $ f a r b e ) ; Dabei bezeichnen xl und yl den Ausgangspunkt der Linie (in unseren Beispielen der Nullpunkt in der linken oberen Ecke der Zeichnung) und x2 und y2 den Endpunkt der Linie. Mit Hilfe der Methode r e c t a n g l e O ist es möglich, Rechtecke zu zeichnen. Auch hierzu werden zwei Punktangaben benötigt, welche zwei sich diagonal gegenüberliegende Ecken des Rechteckes bezeichnen. Ein fünfter Parameter gibt die Zeichenfarbe an. Das erste gezeichnete Rechteck soll gefüllt erscheinen. Hierzu verwenden wir die universal einsetzbare Methode f i l l ( ) , der wir drei Argumente übergeben müssen. Die beiden ersten bezeichnen einen beliebigen Punkt, innerhalb der zu füllenden Fläche, das dritte Argument die Füllfarbe. f i l l ( ) sucht nun, ausgehend von dem angegebenen Punkt nach allen Seiten Pixel (also Bildpunkte) in einer anderen Farbe. Daraus folgt, dass es nur möglich ist, vollständig umschlossene Flächen zu füllen. Wenden Sie die beiden Methoden r e c t a n g l e O und f i l l ( ) in der beschriebenen Art und Weise an, so ist es möglich, die Umrandung in einer anderen Farbe zu zeichnen als die Füllung. Sollen Umrandung und Füllung die gleiche Farbe besitzen, können Sie alternativ auch die Methode f i l l e d R e c t a n g l e O anwenden, welche folgende allgemeine Syntax besitzt. $ g r a f i k - > f i l l e d R e c t a n g l e ( x l , y l , x2, y2, $ f a r b e ) ; Nachdem wir noch ein zweites ungefülltes Rechteck gezeichnet haben, wenden wir uns den Kreisen, Kreissegmenten sowie den Ellipsen zu. Alle drei Formen werden mit Hilfe der Methode a r c ( ) realisiert. Dabei besitzt a r c ( ) die folgende allgemeine Syntax. $ g r a f i k - > a r c ( x , y, b r e i t e , hoehe, S t a r t , ende, $ f a r b e ) ; Diese Parameter bedürfen einer kurzen Erläuterung: Die Argumente x und y bezeichnen die X- und Y-Koordinate des Kreis-Mittelpunkts. Die Argumente b r e i t e und hoehe 38 F ü r die Maler unter Ihnen: Bei der sogenannten additiven Mischung, die beispielsweise bei Computern und Fernsehern angewendet wird, sind die Grundfarben nicht Rot, Gelb, Blau sondern Rot, Grün und Blau. Der Begriff additiv beschreibt dabei, dass mit jeder hinzukommenden Farbe Licht hinzugefügt wird. In der Malerei findet hingegen die subtraktive Mischung Anwendung, bei der mit jeder hinzukommenden Farbe Licht weggenommen wird.

190

8 CGI-Programmierung mit Perl

bezeichnen, wie der Name schon sagt, die Breite und die Hoehe der Figur. Besitzen beide den gleichen Wert, dann erstellen wir einen Kreis oder ein Kreissegment, besitzen sie hingegen unterschiedliche Werte, so erstellen wir eine Ellipse bzw. ein Ellipsensegment. Besondere Bedeutung kommt auch den beiden Argumenten start und ende zu, welche den Startpunkt sowie den Endpunkt des Kreissegmentes bezeichnen. Der mögliche Wertbereich ist jeweils eine Grad-Zahl zwischen 0 und 360 Grad. Dabei liegt der O-Punkt des Winkelkreises oben im Kreis und die Werte steigen im Uhrzeigersinn an. Einen geschlossenen Kreisbogen erhält man, indem man als Startwert 0 und als End wert 360 angibt. Möchten Sie einen Kreis oder eine Ellipse gefüllt darstellen, müssen Sie die Methode f i l l O anwenden. In einem zweiten Beispiel möchten wir Ihnen demonstrieren, wie Sie mit Hilfe des GDModuls Polygone, also Vielecke und Text in Ihre Grafik aufnehmen können. #!/usr/bin/perl -w # Grafik4.pl use GD; # ein neues Grafikobjekt erzeugen # Groesse 400 x 400 Pixel my $grafik = new GD:¡Image(400, 400); # Einige Farben definieren my $schwarz = $grafik->colorAllocate(0 , 0 ,0); my $blau = $grafik->colorAllocate(0, 0, 255) my $rot = $grafik->colorAllocate(255, 0, 0) my $gruen = $grafik->colorAllocate(0, 255, 0): my $weiss = $grafik->colorAllocate(255, 255, 255); my $gelb = $grafik->colorAllocate(255, 255, 0); my $violett = $grafik->colorAllocate(255, 0, 255); # Ein Polygon zeichnen my $polyl = new GD:¡Polygon; $polyl->addPt(50,50); $polyl->addPt(70,90); $polyl->addPt(60, 110); $polyl->addPt(180, 250); $polyl->addPt(150, 260); $polyl->addPt(120, 230); $polyl->addPt(80, 270); $polyl->addPt(10, 130); $polyl->addPt(30, 70); $polyl->addPt(20, 60);

191

8.11 Perl/CGI und Grafik $grafik->polygon($polyl, $weiss); # .. oder regelmaessig u n d gefuellt m y $poly2 = new GD:¡Polygon; $poly2->addPt(250, 100); $poly2->addPt(350, 250); $poly2->addPt(150, 250); $grafik->filledPolygon($poly2, $gelb); # u n d die Beschriftungen $grafik->string(gdLargeFont, 70, 50, "unregelmaessig", $rot); $grafik->string(gdLargeFont, 150, 270, "regelmaessig", $gruen); # Http-Header ausgeben print "Content-type: image/jpeg\n\n"; # Ausgabe v o n Binaerdaten binmode STD0UT; # Grafik in der hoechst m o e g l i c h e n Qualitaet ausgeben print $grafik->jpeg(100);

Qatei Bearbeiten ¿nstcW £ebe LesezeKhen loots ZiTOtk * 'rtSianscite

Abb.

8.11:

Polygone

NeuÜden

E-nsiii yille

i-^Itc/1ocslh«wgt-Nn/buch.'5iillk4.cgl

ÎLesczMchin j R e d Hff Network Jj Support „jShop „jProdutis JTI.

und Text mit Perl

GD.

In unserem Beispiel erzeugen wir zunächst zwei Polygone mit Hilfe der Klasse GD:: Polygon. Hierzu erstellen wir eine neue Instanz der Klasse mit der Anweisung my $polyl = new GD::Polygon;

8 CGI-Programmierung mit Perl

192

Nun können wir die einzelnen Scheitelpunkte des Vieleckes mit Hilfe der Methode addPt() hinzufügen. Die Methode addPtO übernimmt zwei Attribute, nämlich die Xund die Y-Koordinate des zuzufügenden Punktes. Nachdem alle Punkte erstellt sind, können wir das Polygon unter Verwendung der Methode polygon () in die Grafik einfügen. Beachten Sie dabei, dass Sie das Polygon nicht selbst schließen müssen, da das Polygon-Objekt automatisch den letzten angegebenen Punkt mit dem Startpunkt des Polygons verbindet. Die Methode polygon() erhält zwei Parameter, das darzustellende Objekt und die Darstellungsfarbe. Das zweite Vieleck unseres Beispiels erstellen wir mit Hilfe der Methode f i l l e d P o l y g o n ( ) der GD:: Polygon-Klasse. Die Anwendung dieser Methode ist analog der vorher vorgestellten und bedarf daher keiner weiteren Erläuterung. Im Ergebnis erstellt diese Methode ein gefülltes Polygon. Abschließend fügen wir noch Beschriftungen in die Grafik ein. Dies geschieht durch Anwendung der Methode stringO des Grafikobjektes, welche folgende allgemeine Form hat. $grafik->string(Schriftart, x, y, "Beschriftungs Text", $farbe);

Die GD-Klasse bietet die folgenden Schriftarten an: • gdTinyFont • gdSmallFont • gdMediumBoldFont • gdLargeFont • gdGiantFont

Die beiden Argumente x und y geben die linke obere Ecke des einzufügenden Textes an. Innerhalb des Textstrings, der als nächstes Argument folgt, sind keine Zeilenumbrüche möglich.

8.11.4

Speichern von Grafiken

Gerade bei komplexen Grafiken kann es recht zeitraubend sein, die Grafik bei jeder Anzeige komplett neu erzeugen zu müssen. Daher bietet Ihnen Perl die Möglichkeit, die erzeugten JEPEG- oder PNG-Grafiken auf der Festplatte zu speichern. Diesen Vorgang möchten wir Ihnen anhand unseres Polygon-Beispiels demonstrieren. #!/usr/bin/perl -w

8.11 Perl/CGI und Grafik # Grafik5.pl use GD; # ein neues Grafikobjekt erzeugen # Groesse 400 x 400 Pixel my $grafik = new GD::Image(400, 400); # Einige Farben definieren my $schwarz = $grafik->colorAllocate(0 , 0 ,0); my $blau = $grafik->colorAllocate(0, 0, 255) my $rot = $grafik->colorAllocate(255, 0, 0) my $gruen = $grafik->colorAllocate(0, 255, 0): my $weiss = $grafik->colorAllocate(255, 255, 255); my $gelb = $grafik->colorAllocate(255, 255, 0); my $violett = $grafik->colorAllocate(255, 0, 255); # Ein Polygon zeichnen my $polyl = new GD::Polygon; $polyl->addPt(50,50); $polyl->addPt(70,90); $poly1->addPt(60, 110); $polyl->addPt(180, 250); $polyl->addPt(150, 260); $polyl->addPt(120, 230); $polyl->addPt(80, 270); $polyl->addPt(10, 130); $polyl->addPt(30, 70); $polyl->addPt(20, 60); $grafik->polygon($polyl, $weiss); # .. oder regelmaessig und gefuellt my $poly2 = new GD:¡Polygon; $poly2->addPt(250, 100); $poly2->addPt(350, 250); $poly2->addPt(150, 250); $grafik->filledPolygon($poly2, $gelb); # und die Beschriftungen $grafik->string(gdLargeFont, 70, 50, "unregelmaessig", $rot); $grafik->string(gdLargeFont, 150, 270, "regelmaessig", $gruen); # Grafikdaten erzeugen my $grafikdaten = $grafik->jpeg(100); # Grafikdatei zum Schreiben oeffnen open(0UT, '> grafik.jpg')

193

194

8 CGI-Programmierung mit Perl

or die "Ich konnte die Grafikdatei nicht oeffnen!"; # Binaermodus fuer Filehandle setzen binmode OUT; # Grafikdaten schreiben print OUT $grafikdaten; # Filehandle schliessen close (OUT);

Beachten Sie bitte beim vorstehenden Beispiel, dass Sie eine Fehlermeldung des Webservers erhalten, wenn Sie bzw. der Webserver keine Schreibrechte auf das Verzeichnis besitzen, in welches die Grafik gespeichert werden soll. Dieses Skript ist allerdings nicht an CGI gebunden, Sie können es auch "ganz normal" von der Kommandozeile aufrufen. perl grafik5.cgi In diesem Falle sollten Sie allerdings, damit Sie nicht durcheinander geraten, das Programm in g r a f i k 5 . p l umbenennen.

8.11.5

Diagramme mit dem Modul GD: : Graph erstellen

Wenn Sie zu den Menschen gehören, die vor gar nichts zurückschrecken, können Sie mit den bisher besprochenen Methoden beliebige Diagramme erstellen. Sollten Sie aber, wie auch der Autor dieses Buches, zu den Menschen gehören, die lieber den einfachen Weg nehmen, setzen Sie zum Erstellen von Diagrammen besser das Modul GD::Graph ein. Um das Modul GD::Graph verwenden zu können, muss auch das Modul GD: :Text auf Ihrem System installiert sein. Besorgen Sie sich also beide Module in der passenden Version für Ihre Perl-Distribution und installieren Sie diese in der jeweils beschriebenen Art und Weise. Haben Sie die Installation der Module abgeschlossen, können Sie schnell und unkompliziert beliebige Diagramme erzeugen. Im Einzelnen bietet GD:: Graph folgende Module an.

Modul GD :Graph: GD :Graph: GD :Graph: GD :Graph:

Verwendung : area :bars :lines :linepoints

GD :Graph: ¡points GD :Graph: :pie GD :Graph: :mixed

dient zum Erzeugen von Bereichsdiagrammen zum Erzeugen von Balkendiagrammen erstellt Strichdiagramme Erstellen von Mischdiagrammen, die Linien und Punkte verwenden dient der Erzeugung von Punktdiagrammen Erstellen von Tortendiagrammen Mit diesem Modul erzeugt man Kombinationen von beliebigen Diagrammtypen, mit Ausnahme von Tortendiagrammen

8.11 Perl/CGI und Grafik

195

Im folgenden Beispiel erstellen wir ein Balkendiagramm, das die Umsatzentwicklung der Düsentrieb GmbH & Co KG für das Jahr 2002 darstellt, die Ausgabe sehen Sie in Abbildung 8.12. ßaei

ßeaibetten Ansicht Sehe Le»ez

4 .

SiartseRc

»
sub{ exit }

); $schalter->pack(); MainLoopO ;

9.2 Ein einfaches Beispiel

Abb.

9.1: Das

199

'helloTk'-Beispiel

Da alle Perl/Tk-Skripten stets den selben grundlegenden Aufbau aufweisen, möchten wir das soeben erstellte Skript zeilenweise erläutern. Die beiden ersten Kommentarzeilen sollten Ihnen hinlänglich bekannt sein; sie geben den UNIX-Pfad zu Perl sowie den Namen des Skriptes an. Die dritte Programmzeile use Tk;

ist dafür zuständig, Perl mitzuteilen, das Modul Tk.ptn in das Skript einzubinden. Wenn Sie diese Zeile einmal auskommentieren, stellen Sie fest, dass Perl die Anwendung nicht startet und Ihnen statt dessen eine Unzahl von Fehlermeldungen der folgenden Art liefert. Can't locate object method "new" via package "MainWindow" (perhaps you forgot to load "MainWindow"?) at halloTk.pl line 5.

Im nächsten Schritt wird ein Hauptfenster (engl. Toplevel-Window) für unsere Anwendung erstellt. Hauptfenster stellen die unterste Ebene aller grafischen Benutzerschnittstellen dar. Sie sind die Container, in die weitere Elemente wie Schaltflächen, Bezeichnungs- und Textfelder eingefügt werden können. Ein solches Fenster wird mit der folgenden Anweisung in unserem Skript erstellt. my $fenster = MainWindow->new();

In dieser Zeile wird der lokalen Variablen $fenster ein Objekt vom Typ MainWindow (engl. Haupt-Fenster) zugewiesen. Der Zugriff auf dieses Fenster geschieht im weiteren Verlauf der Anwendung stets über diese Objekt-Variable. Ein MainWindow ist ein besonderer Typ eines Toplevel-Windows. Es stellt das Hauptfenster der gesamten GUIAnwendung dar. Die meisten derartigen Anwendungen bestehen allerdings nicht nur aus einem einzigen Fenster, sondern aus einer Vielzahl von Fenstern, wie beispielsweise Dialogfenstern. Das MainWindow steht dabei an der Spitze der Fenster-Hierarchie. Es stellt den Startpunkt der Anwendung dar, und wird es geschlossen, so endet auch die Anwendung. Die folgende Programmzeile weist der Titelleiste der Anwendung einen Text zu. Fehlt diese Zuweisung, nimmt Perl an, Sie möchten den Namen der Skript-Datei in der Titelleiste des Fensters anzeigen, in unserem Beispiel also halloTk.pl. $fenster->title("Hallo Welt!")

Wie Sie sehen, geschieht hier der Zugriff auf die Eigenschaft title des Hauptfensters über die in der vorhergehenden Zeile deklarierten Objektvariable $fenster.

200

9 Grafische Benutzerschnittstellen mit Perl/Tk

Nachdem wir unser Hauptfenster erstellt haben, soll in diesem ein weiteres Steuerelement, nämlich eine Schaltfläche, platziert werden. In der Perl-Terminologie spricht man übrigens nicht wie in der Windows-Welt üblich von Controls sondern von Widgets. (Dies ist die in der UNIX-Welt gebräuchliche Bezeichnung.) Die Schaltfläche erzeugen wir mit folgender Anweisung. my $sch.alter = $fenster-Button( -text=>"Ade d u schn"ode Welt!", - c o m m a n d => sub{ exit }

); Hier wird zunächst eine Objektvariable vom Typ Button (engl. Schaltfläche) im Hauptfenster erzeugt. Dies geschieht in der ersten Zeile des oben dargestellten Blocks. Die öffnende Klammer am Ende dieser Zeile deutet an, dass nun eine Liste von Eigenschaften folgt. In Perl/Tk besitzen alle Widgets eine Vielzahl von Eigenschaften, mit denen beispielsweise das Erscheinungsbild verändert werden kann. Glücklicherweise sind die meisten dieser Eigenschaften mit sinnvollen Werten vorbelegt, so dass wir nur die Eigenschaften in der Argument-Liste angeben müssen, denen wir von den Vorgaben abweichende Werte übergeben wollen. Bei Schaltflächen werden dies in der Regel zumindest die Eigenschaften -text und -command sein.2 Die Eigenschaft -text bezeichnet die Beschriftung, die auf der Schaltfläche erscheint; die Methode -command die Aktion, die ausgeführt werden soll, wenn der Anwender das Steuerelement mit der linken Maustaste angeklickt hat. 3 In unserem Beispiel wird nach einem Mausklick eine Unterroutine ausgeführt, die nur aus der Anweisung exit besteht und die Anwendung beendet. Die Syntax der Zuweisung von Werten an Eigenschaften ist bei allen Widgets von Perl/Tk stets dieselbe. -Eigenschaft => Eigenschaftswert,

Allen Eigenschaften stehen dabei als Liste in Klammern hinter dem Widget-Typ. Zur besseren Lesbarkeit des Quellcodes empfiehlt es sich, die Eigenschaft-Wert-Paare, wie in unserem Beispiel, zeilenweise und eingerückt einzugeben. Bedenken Sie, dass bei der Programmierung von grafischen Benutzerschnittstellen schnell einige hundert oder gar tausend Zeilen zusammenkommen. Sie werden für eine gute Strukturierung des Quellcodes dankbar sein.4 Wir haben das Widget zwar erzeugt, Perl/Tk weiß allerdings noch nicht, was es nun damit anfangen soll. Das Steuerelement muss noch einem sogenannten Geometrie-Manag er 2 Der Bindestrich vor dem Namen der Eigenschaft muss nicht unbedingt gesetzt werden, wir halten dies aber für übersichtlicher und in Anlehnung an Tcl/Tk, wo der Bindestrich erscheinen muss, für konsistenter. 3 Die Begriffe Eigenschaft und Methode werden wir im Kapitel Objektorientiertes Perl ausführlich behandeln. Hier sei nur soviel gesagt: Eigenschaften bestimmen das Erscheinungsbild von Objekten, Methoden hingegen das Verhalten von Objekten. 4 Da es sich bei den Eigenschaft-Wert-Listen um Hashes handelt, ist es auch möglich, diese durch Kommas getrennt anzugeben. Das Zeichen => stellt also lediglich eine Leseerleichterung dar. Ich möchte Ihnen aber dringend davon abraten, dies zu versuchen! Es sei denn, Sie quälen gerne sich und andere!

9.2 Ein einfaches Beispiel

201

übergeben werden, der dafür zuständig ist, das Widget im Fenster zu platzieren und der dafür sorgt, dass jedem Steuerelement sein eigener Platz zugewiesen wird, und sich die einzelnen Steuerelemente nicht gegenseitig ins Gehege kommen. Der wohl gebräuchlichste Geometrie-Manager von Perl/Tk ist pack(). Wir werden ihn in den meisten Beispielen dieses Buches einsetzen. In unserem Beispiel-Skript haben wir es nur mit einem einzigen Widget zu tun, wir können pack() die gesamte Kontrolle überlassen und die Funktion ohne Attribute aufrufen. Dies geschieht mit der folgenden Anweisung. $schalter->pack() ;

Die Benutzerschnittstelle ist nun vollständig erstellt. Würden wir die Anwendung allerdings nun starten, müssten wir feststellen, dass das Programm nach kurzer Zeit, ohne dass etwas geschehen ist, zum Eingabeprompt zurückkehrt. Wir müssen Perl nämlich noch die Anweisung geben: "stelle die Anwendung auf dem Bildschirm dar und achte darauf, ob Ereignisse wie Mausklicks und Tastatureingaben auftreten, die mich interessieren ". Dies alles wird von einer einzigen Anweisung bewirkt. MainLoopO ;

Diese Anweisung startet eine Endlosschleife, die auf Ereignisse wartet, wie sie oben beschrieben wurden. Sie analysiert, welche Widgets von den Ereignissen betroffen sind und sorgt für die Weiterleitung an die entsprechenden Ereignisbehandlungs-Routinen der Anwendung. In unserem Beispiel wird also, wenn Sie auf den Button der Anwendung klicken, der Befehl exit in der Ereignisbehandlungs-Routine der Schaltfläche aufgerufen. Sobald ein Ereignis übermittelt wurde, wartet MainLoopO erneut auf eintretende Ereignisse. Im oben abgedruckten Skript ist dies allerdings nicht der Fall, da mit der Anweisung exit das Programm und damit die Endlosschleife beendet wird. Programmcode, der nach der Anweisung Mainloop () steht, wird folglich erst dann ausgeführt, wenn die grafische Benutzerschnittstelle geschlossen wurde, ohne gleichzeitig die Anwendung zu beenden. Es könnten also an dieser Stelle "Aufräumarbeiten" ausgeführt werden, die beispielsweise das ordnungsgemäße Beenden der Anwendung melden oder das Schließen von geöffneten Datenkanälen bewirken. Selbstverständlich können nach dieser Anweisung auch beliebige Unterroutinen stehen, die aus den Ereignis-Routinen der Benutzerschnittstelle heraus aufgerufen werden. Soweit unser Einführungsbeispiel. Hieraus lassen sich schematisch drei Schritte ableiten, die stets notwendig sind, wenn grafische Benutzerschnittstellen erzeugt werden sollen: 1. Hauptfenster erzeugen, 2. ein oder mehrere Widgets in diesem Hauptfenster platzieren, 3. die Ereignisschleife starten. Nachdem wir unser erstes Perl/Tk-Programm geschrieben und ausgeführt haben, werden wir uns in den folgenden Abschnitten mit den verschiedenen Widgets von Perl/Tk befassen und so Schritt für Schritt lernen, komplexe grafische Benutzerschnittstellen zu erstellen.

202

9.3

9 Grafische Benutzerschnittstellen mit Perl/Tk

Grundlegende Widget-Eigenschaften von Perl/Tk

Die Entwicklung von Perl/Tk verlief in den letzten Jahren mehr als rasant. Ständig wurden neue Widgets hinzugefügt und vorhandene verbessert. Perl/Tk braucht den Vergleich mit anderen Toolkits zur Gestaltung von grafischen Benutzerschnittstellen weder aus der Windows- noch aus der LINUX/UNIX-Welt zu scheuen. Das Tk-Toolkit unterstützt alle fortgeschrittenen Techniken, wie die Darstellung von Tabellen und hierarchischen Listen, die Anzeige von HTML-Dokumenten und den Datenaustausch über die "Zwischenablage" sowie das Drag and Drop. Es würde den Rahmen dieses Buches bei Weitem sprengen, alle Widgets von Perl/Tk mit ihren Eigenschaften und Methoden beschreiben zu wollen. Wir müssen uns daher darauf beschränken, die Standardsteuerelemente mit ihren wichtigsten Eigenschaften und Methoden vorzustellen. Wir möchten Ihnen so ein solides Fundament für Ihre Arbeit geben.

9.3.1

Grundlegende Widget-Optionen

Wie wir in unserem Einführungsbeispiel gesehen haben, besitzen alle Widgets eine Vielzahl von Argumenten, die unter anderem das Erscheinungsbild der Steuerelemente beeinflussen. Daher ist es sinnvoll (auch wenn dies eine recht trockene Angelegenheit ist), zunächst die allgemeinen Widget-Eigenschaften zu beschreiben, die den meisten Steuerelementen gemeinsam sind, bevor wir uns den einzelnen Widgets zuwenden. Sollte es Ihnen zu stark in den Fingern kribbeln, und Sie möchten lieber gleich mit dem Erstellen Ihrer Traumanwendung beginnen, überfliegen Sie diesen Abschnitt nur kurz und schlagen später bei Bedarf hier nach. Hier beschriebene Eigenschaften werden bei der Besprechung der einzelnen Steuerelemente im weiteren Verlauf dieses Kapitels ausgespart. Die allgemeine Syntax zum Erzeugen eines Widgets und zum Setzen von dessen Eigenschaften ist stets dieselbe. $widget = $container -> Widgettyp( [-Eigenschaft => Wert, ...]);

Dabei steht $widget für eine frei wählbare Objekt-Variable, über die das Widget nach seiner Initialisierung angesprochen wird. $container bezeichnet das übergeordnete Steuerelement, in welchem das neu erstellte Widget platziert werden soll. Dabei kann es sich, wie in unserem Einführungsbeispiel, um Toplevel-Fenster handeln oder, wie wir weiter unten sehen werden, um ein Steuerelement vom Typ Frame. Diese Widgets können unter anderem dazu verwendet werden, Steuerelemente in komplexen Benutzerschnittstellen mittels des Geometrie-Managers pack () am Bildschirm anzuordnen. Beachten Sie zu den allgemeinen Widget-Optionen, dass die meisten Eigenschaften mit sinnvollen Standard-Werten versehen sind. Sie müssen also nur dann eine Angabe machen, wenn Sie ein abweichendes Verhalten oder Erscheinungsbild wünschen.

9.3 Grundlegende Widget-Eigenschaften von Perl/Tk

9.3.2

203

Das Erscheinungsbild von Widgets festlegen

-anchor => ' p o s i t i o n ' . Mit dieser Option geben Sie an, wie untergeordnete Widgets innerhalb von Container-Widgets ausgerichtet werden. Die Ausrichtung wird durch ein Kürzel angegeben, das für eine Himmelsrichtung steht. Mögliche Werte für p o s i t i o n sind ' n ' (north - Norden), ' n e ' (northeast - Nordosten), ' e ' (east - Osten), ' s e ' (southeast

- Südosten), ' s ' (south - Süden), ' s w ' (southwest - Südwesten), ' w ' (west

- Westen), 'nw' (northwest - Nordwesten) und ' c e n t e r ' (zentriert). -background => ' f ä r b e ' . Diese Option dient der Festlegung der Hintergrundfarbe eines Steuerelementes. Dabei erfolgt die Angabe über Farbnamen wie ' b l u e ' , ' b l a c k ' , ' g r e e n ' , ' g r e y ' , ' y e l l o w ' , ' r e d ' oder ' w h i t e ' . Es ist aber auch möglich, zusammengesetzte Farbnamen wie ' d a r k g r e y ' , ' l i g h t b l u e ' etc. zu verwenden. Welche Farben von Ihrem System unterstützt werden, hängt nicht zuletzt von der verwendeten Grafikkarte und dem Betriebssystem ab. Sie sollten also vorsichtig mit exotischen Farbnamen wie 'MidnightBlue' umgehen, da das Ergebnis nicht in jedem Fall vorhersehbar ist. Die Option -background kann mit -bg abgekürzt werden. -borderwidth => b r e i t e . Verwenden Sie diese Eigenschaft, um die Randbreite eines Widgets einzustellen. Die Randbreite wird in Bildschirmeinheiten (Pixel) angegeben. Diese Eigenschaft bezieht sich auf den mit Hilfe der Eigenschaft - r e l i e f eingestellten Randtyp des Widgets. $widgetName->conf i g u r e ( - e i g e n s c h a f t => ' W e r t ' ) . Mitunter möchte man den Wert eines Steuerelementes nachträglich ändern. Hierzu verwendet man die Widget-Methode - c o n f i g u r e , die alle Widgets besitzen. Um die Textfarbe eines Button-Widgets auf red zu setzen, verwenden Sie folgende Anweisung in Ihren Programmcode 5 . I b u t t o n -> c o n f i g u r e ( - f o r e g r o u n d => ' r e d ' ) -foreground => ' f ä r b e ' . Diese Eigenschaft dient dazu, die Farbe des Vordergrundes, also beispielsweise der Schriftfarbe eines Label-Widgets, festzulegen. Die Farbangabe erfolgt wiederum über Farbnamen. Diese Eigenschaft kann mit -f g abgekürzt werden. -padx => b r e i t e . Mit dieser und der folgenden Eigenschaft ist es möglich, Widgets optisch voneinander zu trennen, indem ein zusätzlicher freier Raum von b r e i t e Pixeln links und rechts des Widgets geschaffen wird. Diese Eigenschaften werden dem Geometrie-Manager als Argumente übergeben. Beachten Sie hierzu auch den Beispielcode weiter unten. -pady => hoehe. Erzeugt einen zusätzlichen freien Raum von hoehe Bildschirmeinheiten ober- und unterhalb des Widgets und dient wie auch padx zur optischen Trennung von Widgets. # ! / u s r / b i n / p e r l -w # pad.pl 5

Für $button müssen Sie selbstverständlich den Namen des zu ändernden Widgets angeben.

204

9 Grafische Benutzerschnittstellen mit Perl/Tk

use Tk; # Erzeugen des Hauptfensters my $mw = MainWindow->new(); $mw->title(" padx u n d pady" ); # zwei widgets ohne pad-Option my $btnl = $mw->Button( -text => 'Schaltflaeche l')->pack(); my $btn2 = $mw->Button( -text => 'Schaltflaeche 2')->pack(); # zwei widgets mit pad-Option my $btn3 = $mw->Button( -text => 'Schaltflaeche 3')->pack( -padx => 5, - p a d y => 5); my $btn4 = $mw->Button( -text => 'Schaltflaeche 4')->pack( -padx => 5, -pady => 5); # Hauptschleife MainLoopO ;

Die Ausgabe des Beispielskriptes ist in der folgenden Abbildung dargestellt. Beachten Sie aber, dass es scheint, als sei die Option -padx auch bei den beiden oberen Widgets gesetzt worden. Dies liegt daran, dass die Breite des Hauptfensters vom jeweils breitesten Steuerelement bestimmt wird. Es hätte in unserem Beispiel daher ausgereicht, diese Option bei nur einer Schaltfläche zu setzen. V

Schaltflaeche 1 Schaltflaeche Z Schaltflaeche 3 Schaltflaeche 4

L Abb.

9.2: Beispiel für die-padx

und -pady

1 Optionen

-relief => ' art'. Eine Reihe von Widgets, wie Label- oder Textfelder sowie Buttons, werden von einem Rahmen umgeben. Mit Hilfe der Eigenschaft -relief ist es möglich,

9.3 Grundlegende Widget-Eigenschaften von Perl/Tk

205

das Erscheinungsbild dieses Rahmens zu beeinflussen. Für das Argument ' a r t ' können Sie die folgenden Werte einsetzen: ' f l a t ' : der Rahmen erscheint flach, ' g r o o v e ' : der Rahmen erscheint als Einkerbung, ' r a i s e d ' : der Rahmen erscheint dreidimensional hervorgehoben, ' r i d g e ' : der Rahmen erscheint als Kante hervorgehoben und ' sunken', wobei der Rahmen dreidimensional zurückgesetzt erscheint. Sofern Sie für - r e l i e f keine Angabe machen, wird bei Button-Widgets standardmäßig ' r a i s e d ' , bei Text- und Entry-Widgets 'sunken' und bei Label-Steuerelementen ' f l a t ' angenommen. Das folgende Skript erzeugt das unten abgebildete Fenster mit mehreren Button-Steuerelementen, die mit den verschiedenen -relief-Einstellungen dargestellt werden. Um das Skript zu beenden, müssen Sie die mit flat beschriftete Schaltfläche betätigen.

Abb.

9.3: Unterschiedliche

Optionen der Eigenschaß

-relief

#!/usr/bin/perl -w # relief.pl use Tk; # Erzeugen eines Hauptfensters my $Fenster = MainWindow->new(); $Fenster->title('Relief'); # Erzeugen von mehreren Button-Steuerelementen my $Schalterl = $Fenster->Button( -text => 'flat', -relief => 'flat', - command => sub {exit} )->pack(); my $Schalter2 = $Fenster->Button( -text => 'groove', -relief => 'groove' )->pack(); my $Schalter3 = $Fenster->Button(

206

9 Grafische Benutzerschnittstellen mit Perl/Tk - t e x t => ' r a i s e d ' , - r e l i e f => ' r a i s e d ' )->pack();

my $Schalter4 = $Fenster->Button( - t e x t => ' r i d g e ' , - r e l i e f => ' r i d g e ' )->pack(); my $Sch.alter5 = $Fenster->Button( - t e x t => ' s u n k e n ' , - r e l i e f => 'sunken' )->pack(); MainLoopO ;

9.3.3

Callbackfunktionen mit der -command-Methode

Die allgemeine Syntax der -command-Methode ist -command => callback Dabei ist callback ein Zeiger auf eine Punktion, die ausgeführt wird, wenn das Widget, beispielsweise durch einen Mausklick, aktiviert wurde. In unserem Einführungsbeispiel haben wir bereits eine solche callback-Funktion eingesetzt. Mit der Anweisung -command => sub { e x i t } wurde eine Unterroutine definiert, die nur aus einem Befehl besteht. Innerhalb einer solchen Unterroutine kann beliebiger Perl-Code ausgeführt werden. In der Regel wird man diese aber im Sinne einer strukturierten Programmierweise vornehmlich dazu verwenden, um weitere Unterprogramme aufzurufen, -command ist eine der Schlüsselfunktionen bei der Erstellung von ereignisgesteuerten grafischen Benutzerschnittstellen. 6 Da es nicht möglich ist, diese Methode mit einem sinnvollen Wert vorzubelegen, muss bei Widgets, mit denen auf Ereignisse reagiert werden soll, stets eine entsprechende Funktionalität vorgesehen werden.

9.3.4

Bitmaps und Bilder in Widgets anzeigen

Zweifarbige UNIX-Bitmaps anzeigen. Anstelle eines Textes ist es bei einigen Widget-Typen mit Hilfe der Option -bitmap => 'bitmap' 6 Programme mit grafischen Benutzerschnittstellen werden auch als ereignisgesteuert bezeichnet. Der Programmfluss derartiger Anwendungen ist nicht wie bei 'klassischen' Anwendungen, wie wir Sie in Band 1 kennengelernt haben, linear, sondern abhängig von Ereignissen, die an der Schnittstelle auftreten. Der exakte Ablauf einer Anwendung lässt sich dabei nie genau vorhersagen.

207

9.3 Grundlegende Widget-Eigenschaften von Perl/Tk v l i i M W I l i i l -

L Abb.

9.4-' Button mit zweifarbigem

P

x)

J UNIX-Bitmap

möglich, zweifarbige Bitmaps anzuzeigen. 7 Perl/Tk besitzt hierfür einige vordefinierte B i t m a p s ( e r r o r , grayl2, gray59, gray75, hourglass, info, questhead, question u n d

warning). Zudem ist es aber auch möglich, selbsterzeugte Bitmaps anzuzeigen, die Sie beispielsweise mit dem UNIX/LINUX-Programm Xfig erstellen können. Hierzu müssen Sie allerdings vor die Pfadangabe ein @-Symbol stellen, wie in der folgenden Zeile gezeigt - b i t m a p =>

'Q/home/helmut/meinBitmap.bit'

Das folgende Skript erzeugt ein Fenster mit einem Button-Steuerelement, in welchem an Stelle eines Textes ein Fragezeichen dargestellt wird. Nachdem Sie den Button mit der linken Maustaste betätigt haben, wird die Anwendung verlassen.

#!/usr/bin/perl - w # bitmap.pl use Tk; m y $Fenster = MainWindow->new(); $Fenster->title('Button mit Bitmap'); my $Schalter = $Fenster->Button( - b i t m a p => 'question', - c o m m a n d => sub {exit} )->pack(); MainLoopO ;

M e h r f a r b i g e I m a g e s anzeigen. W i e wir bei d e r B e s c h r e i b u n g d e r - b i t m a p - E i g e n -

schaft gesehen haben, ist es möglich, in Widgets zweifarbige Bitmaps anzuzeigen. Wem dies in der Welt der bunten Windows-Anwendungen zu fad ist, der kann über die Eigenschaft -image ein mehrfarbiges Image anzeigen. Derzeit werden die gängigen ImageFormate wie XPM, GIF, BMP, PPM und XBM von Perl/Tk unterstützt. Die Anzeige des Images geschieht in zwei Schritten. 7 Entgegen dem in der Welt von Windows üblichen Sprachgebrauch sind Bitmaps stets zweifarbig. Sie bestehen aus einer Vorder- und einer Hintergrundfarbe.

9 Grafische Benutzerschnittstellen mit Perl/Tk

208

Abb.

9.5: Button

mit mehrfarbigem

Image

Zunächst wird einer Objekt-Variablen über die -photo-Methode ein Verweis auf eine Grafikdatei zugewiesen, dann wird dieser Zeiger der -image-Eigenschaft eines Widgets zugewiesen. Dabei kann ein Zeiger mehrfach von verschiedenen Steuerelementen verwendet werden. Im folgenden Beispiel wird in einem Button das Symbol von DebianGNU/LINUX 8 angezeigt. #!/usr/bin/perl - w # image.pl use Tk; # Erzeugen eines Hauptfensters my $Fenster = MainWindow->new(); $Fenster->title('Button mit Image'); # Erzeugen eines Zeigers auf eine GIF-Datei $debian = $Fenster->Photo(-file => 'gdmDebianLogo.xpm'); my $Schalter = $Fenster->Button( -image => $debian, - c o m m a n d => sub {exit} )->pack(); MainLoopO ;

9.4

Die Standard-Widgets von Perl/Tk

Nachdem wir die allgemeinen Widget-Optionen betrachtet haben, möchten wir Ihnen nun die Standard-Widgets von Perl/Tk mit ihren wichtigsten Optionen vorstellen. 8

Natürlich können Sie die Farben im Buch nicht erkennen. Wenn Sie das Beispiel ausprobieren möchten: das Debian-Logo finden Sie unter Debian-GNU/LINUX im Verzeichnis /usr/share/pixmaps. Selbstverständlich können Sie auch ein beliebiges Image hinterlegen.

9.4 Die Standard-Widgets von Perl/Tk

9.4.1

209

Das Label-Widget

Das Label-Widget dient der Anzeige von Texten, die allerdings nicht editiert werden können. Ihm kommt damit eine bedeutende Rolle bei der Strukturierung von Benutzeroberflächen zu, indem es dem Anwender Hinweise zum Umgang mit der Anwendung gibt oder ihm die Orientierung erleichtert. Besonders häufig werden Label-Widgets zusammen mit E n t r y - oder Text-Widgets verwendet, um deren Inhalte zu erläutern. Der Autor verwendet Label-Widgets zudem gerne in Datenbank-Anwendungen, um Inhalte von Attributen anzuzeigen, die vom Anwender nicht verändert werden sollen. Beispiele für solche Felder sind die sogenannten Primärschlüssel-Felder, die vom Programm bei der Neuanlage eines Datensatzes automatisch inkrementiert oder berechnet werden. Für das Label-Widget sind vor allem drei Eigenschaften von Bedeutung. -text => 'beschreibung'. Diese Eigenschaft legt den Text fest, der im Widget angezeigt wird. Dabei handelt es sich um statischen Text, der nur von der Anwendung, nicht aber vom Anwender verändert werden kann. Hierzu ein kurzes Beispiel.

#!/usr/bin/perl -w # labell.pl use Tk; # Hauptfenster erzeugen my $fenster = MainWindow->new(); # Label-Widget erzeugen und einen Text zuweisen my $label = $fenster->Label( -text => 'Guten Morgen Welt!'); $label->pack(); # Ein Button-Widget erzeugen, das den Text aendert my $button = $fenster->Button( -text => 'Ich aendere das Label•, -command => sub { $label -> configure(-text => 'Ich bin noch so muede!';

»; $button->pack(); MainLoopQ ;

Im Beispiel wird ein Label-Widget erstellt und der -text-Eigenschaft ein Wert zugewiesen. Anschließend erstellen wir ein Button-Widget, dieses ändert in seiner -commandMethode den angezeigten Text des Label-Steuerelementes. Hierzu wird im Beispiel die -configure-Methode des Widgets verwendet.

210

9 Grafische Benutzerschnittstellen mit Perl/Tk

-textvariable => \$variable. Mitunter möchte man allerdings in einem Label variable Werte anzeigen, beispielsweise in einer Datenbank-Anwendung. Hier wäre es mit recht hohem Aufwand verbunden, die -text-Eigenschaft jedesmal mit Hilfe der -conf igure-Methode zu verändern. Perl/Tk bietet uns daher die Eigenschaft -textvariable => \$variable

Mit dieser Eigenschaft ist es möglich, eine im Programmtext deklarierte Variable zu referenzieren. Immer, wenn im Programmtext der Wert dieser Variablen verändert wird, ändert sich automatisch der angezeigte Text des Label-Steuerelementes. Betrachten Sie hierzu die zweite Variante unseres Label-Skriptes. #!/usr/bin/perl -w # label2.pl use Tk; my $text = 'Guten Morgen Welt!'; # Hauptfenster erzeugen my $fenster = MainWindow->new(); # Label-Widget erzeugen und einen Text zuweisen my $label = $fenster->Label( -textvariable => \$text); $label->pack(); # Ein Button-Widget erzeugen, das den Text aendert my $button = $fenster->Button( -text => 'Ich aendere das Label', -command => sub { $text = 'Ich bin noch so muede!';

»; $button->pack(); MainLoopO ;

In dieser Variante wird zu Beginn des Programms die Variable $ t e x t mit einem Wert initialisiert. Das Label-Widget seinerseits referenziert diese Variable. Wird nun an einer beliebigen Stelle der Wert der Variablen geändert, spiegelt sich dies unmittelbar in der Anzeige des Label-Steuerelementes. -justify => 'ausrichtung'. Von Bedeutung für das Label-Widget ist schließlich die Eigenschaft - j u s t i f y => ' a u s r i c h t u n g ' . Sie gibt an, wie der angezeigte Text innerhalb des Widgets ausgerichtet wird. Mögliche Werte für diese Eigenschaft sind: ' l e f t '

9.4 Die Standard-Widgets von Perl/Tk

211

für eine linksbündige Textausrichtung, ' r i g h t ' für eine rechtsbündige Textausrichtung und ' c e n t e r ' für eine zentrierte Textausrichtung. Erfolgt keine Angabe für diese Eigenschaft, so gilt ' c e n t e r ' als Vorgabe-Wert.

9.4.2

Das Button-Widget

Das Button-Widget unterscheidet sich eigentlich nur dadurch von dem Label-Widget, dass es eine -command-Methode besitzt. Mit Hilfe dieser Methode kann festgelegt werden, was geschieht, wenn der Anwender das Button über einen Mausklick oder die Return-Taste aktiviert. Im folgenden Beispiel wird von der -command-Methode eine Unterroutine aufgerufen. Unterroutinen können wie im Beispiel auch hinter der Anweisung MainLoopO stehen. Nach Abarbeitung dieser Routine verzweigt das Skript automatisch wieder in die Ereignisschleife und wartet auf weitere Aktionen des Anwenders.

Sie haben wohl gehofft es ist vorbei! Reingefallen!

Abb. ff.6: Die Anwendung

'button.pl' nach dem, ersten Mausklick auf das

#!/usr/bin/perl -w # button.pl use Tk; # Text fuer Button-Widget festlegen my $text = 'Zum Beenden druecken'; # Hauptfenster erzeugen $fenster = MainWindow->new(); # Label erzeugen $label = $fenster->Label( -text => 'Ich bin aktiv ... (oder?)' )->pack(); # Button erzeugen $button = $fenster->Button( # Die variable '$text' referenzieren -textvariable => \textbackslashO$text, # Callback-Routine deklarieren -command => sub { # Unterroutine aufrufen ButtonEreignis()

Button-Widget

9 Grafische Benutzerschnittstellen mit Perl/Tk

212 })->pack(); MainLoopO ;

sub ButtonEreignis { # hier koennte ganz viel Code stehen if ($text ne 'Reingefallen!') { # Text auf Label aendern $label->configure(-text => 'Sie haben wohl gehofft es ist vorbei!'); # Meldetext fuer Button aendern $text = 'Reingefallen!'; } eise { # Jetzt aber exit(0); }

Auch in diesem Programm verwenden wir die Option -textvaxiable, aber diesmal bei einem Button-Widget. Die Option wird im Programm verwendet, um den Zustand des Buttons abzufragen und in der Unterroutine entsprechend zu reagieren.

9.4.3

Checkbutton- und Radiobutton-Widgets

Bei Checkbuttons und Radiobuttons handelt es sich ebenfalls um Schaltflächen, die über eine -command-Methode verfügen. Beide Widget-Typen bieten Optionslisten an, aus denen der Anwender Elemente auswählen kann. Aus einer Gruppe von Checkbuttons können mehrere Optionen gewählt werden. Radiobuttons hingegen, die ihren Namen von den Stationstasten von Radiogeräten haben, erlauben jeweils nur die Auswahl eines einzelnen Elementes. Sie funktionieren also genauso wie die Tasten der namengebenden Geräte, bei denen auch stets nur ein einzelner Sender eingestellt sein kann. Die wohl wichtigste Eigenschaft dieser Steuerelemente ist die Option -variable => \$variable

Was moechten Sie tun? fc Schwimmen j Schlafen • Schuften Sie moechten: |

Abb.

9.7: Das Checkbutton-Beispiel

Schwimmen, Schuften

|1

mit den Lieblingstätigkeiten

des Autors

9.4 Die Standard-Widgets von Perl/Tk

213

Hierbei stellt $ v a r i a b l e eine Referenz auf eine Variable dar, welche den Zustand des jeweiligen Buttons spiegelt. Bei Checkbuttons ist jedem Button einer Gruppe eine eigene Variable zugeordnet, die den Zustand des Buttons (aktiviert/nicht aktiviert) angibt. Hierzu werden die in Perl üblichen Entsprechungen der booleschen Werte True und False verwendet, wobei False durch 0 dargestellt wird. Im folgenden Beispiel werden Checkbuttons genutzt, um eine "ToDo"-Liste zu erstellen.

#!/usr/bin/perl -w # checkbutton.pl use Tk; # boolesche Variablen fuer Zustand der Buttons # mit 'False' vorbelegen my $chkl = 0; my $chk2 = 0; my $chk3 = 0; # Hauptfenster erzeugen $fenster = MainWindow->new(); # Label erzeugen $labell = $fenster->Label( -text => 'Was moechten Sie tun?'

); # Checkbuttons erzeugen $chkbuttonl = $fenster->Checkbutton( -text => 'Schwimmen', -variable => \$chkl, -command => sub {Auswertung()>

); $chkbutton2 = $fenster->Checkbutton( -text => 'Schlafen', -variable => \$chk2, -command => sub {Auswertung()}

); $chkbutton3 = $fenster->Checkbutton( -text => 'Schuften', -variable => \$chk3, -command => sub { A u s w e r t u n g O }

); # Zwei Label zum Darstellen des Ergebnisses erstellen

214

9 Grafische Benutzerschnittstellen mit Perl/Tk

$label2 = $fenster->Label( -text => 'Sie moechten:'

);

$label3 = $fenster->Label();

# Alle Steuerelemente auf die Oberflaeche 'packen' $labell->pack(); $chkbuttonl->pack(); $chkbutton2->pack(); $ch.kbutton3->pack() ; $label2->pack(); $label3->pack(); # Ereignisschleife starten MainLoopO ; sub Auswertung { # angezeigten Text loeschen my $text = " ; # Zustand der Variablen abfragen if($chkl) { $text = 'Schwimmen, ';

> if($chk2) { $text .= 'Schlafen, ';

} if($chk3) { $text .= 'Schuften';

} # Ergebnis ausgeben $label3->configure( -text => $text);

> Bei den Radiobuttons kann stets nur eine Schaltfläche einer Gruppe aktiviert sein. Dies wird über zwei Eigenschaften realisiert: Alle Buttons einer Gruppe weisen eine Referenz auf dieselbe Variable in der Eigenschaft - v a r i a b l e auf. Zusätzlich besitzen Radiobuttons eine Eigenschaft mit Namen -value, in der jeweils der Wert enthalten ist, welcher in die Referenz geschrieben wird, wenn der entsprechende Button aktiviert ist. Zugegeben, die Erklärung hört sich ein wenig kompliziert an. Sie sollten daher folgendes Beispiel ausprobieren.

9.4 Die Standard-Widgets von Perl/Tk

215

v .!i.n.1x"... • i WBHBBPBjaagWBBBIWWBiPWM« BW Wie moechten Sie zahlen? v

Visa Bar

*

gar nicht

Sie moechten: |

Abb.

9.8:

Das Radiobutton-Beispiel

Finden Sie das gut?

mit der vom Autor

#!/usr/bin/perl -w # radiobutton.pl use Tk; # Initialisierung der Radiobutton-Variablen my $var = ''; # Hauptfenster erzeugen $fenster = MainWindow->new(); # Label erzeugen $labell = $fenster->Label( -text => 'Wie moechten Sie zahlen?'

# Radiobuttons erzeugen $radiobuttonl = $fenster->Radiobutton( -text => 'Visa', -variable => \$var, -value => 'Visa', -command => sub {Auswertung()}

); $radiobutton2 = $fenster->Radiobutton( -text => 'Bar', -variable => \$var, -value => 'Bar', -command => sub {Auswertung()>

); $radiobutton3 = $fenster->Radiobutton( -text => 'gar nicht', -variable => \$var, -value => 'gar nicht',

bevorzugten

Zahlungsweise

216

9 Grafische Benutzerschnittstellen mit Perl/Tk -command => sub {Auswertung()}

); # Label-Steuerelemente fuer Auswertung erstellen $label2 = $fenster->Label( -text => 'Sie moechten:'

); $label3 = $fenster->Label(); # Alle Steuerelemente auf die Oberflaeche 'packen' $labell->pack(); $radiobuttonl->pack(); $radiobutton2->pack(); $radiobutton3->pack(); $label2->pack(); $label3->pack(); # Ereignisschleife starten MainLoopO ; sub Auswertung { # Auswerten, welche Schaltflaeche aktiviert wurde # und Meldung ausgeben if ($var eq 'gar nicht') { $label3->conf igure( -text => 'Finden Sie das gut?'

); } eise { $label3->configure( -text => 'Ihre bevorzugte Zahlungsweise: ' . $var

>

);

}

9.4.4

Das Listbox-Widget

Listbox-Widgets dienen dazu, eine Liste von Elementen anzuzeigen, aus denen es möglich ist, ein oder mehrere Elemente auszuwählen. Listboxen besitzen zwei Vorteile gegenüber Gruppen von Check- oder Radiobuttons. Zum einen ist es möglich, die Liste zur Laufzeit der Anwendung zu ändern, indem Elemente hinzugefügt oder entfernt werden. Zum anderen ist die Anzahl nicht auf die sichtbare Größe der Listbox beschränkt. Besonders in Verbindung mit den im nächsten Abschnitt vorgestellten Scrollbar-Widget

217

9.4 Die Standard-Widgets von Perl/Tk

Deutsch Englisch

Franzoesisch Kisuaheli Italienisch

L Abb.

9.9: Die Ausgabe des

Kisuaheli

J

Listbox-Beispiels

lässt sich eine große Anzahl von Elementen übersichtlich anzeigen.9 Die wichtigsten Methoden und Eigenschaften von Listboxen sind. $ l i s t b o x - > i n s e r t ( i n d e x , element, element, . . . ) . Mit Hilfe dieser Methode ist es möglich, einem Listbox-Widget neue Einträge zuzufügen. Das Argument (index) gibt die Position an, an welcher die neuen Elemente eingefügt werden sollen. Der kleinste mögliche Index ist 0. Um Elemente am Ende der Liste anzufügen, kann als Index auch ' e n d ' angegeben werden. $ l i s t b o x - > d e l e t e ( e r s t e r Index [, l e t z t e r Index]). Um Elemente aus einem Listbox-Steuerelement zu entfernen, können Sie diese Methode verwenden. Um ein einzelnes Element zu löschen, geben Sie nur den Index diese Elementes an. Um mehrere Elemente zu löschen, geben Sie den Index des ersten und des letzten Elementes an, das gelöscht werden soll. Mit der folgenden Anweisung ist es möglich, die gesamte Liste eines ListboxWidgets zu löschen. $listbox->delete(0,

'end');

- s e l e c t m o d e = > m o d u s . Diese Eigenschaft gibt an, wieviele Listeneinträge vom Benutzer ausgewählt werden können. Möglich sind folgende Modi: 'browse', ' S i n g l e ' , ' m u l t i p l e ' und ' e x t e n d e d ' . Die Vorgabe ist 'browse'; 'browse' und ' s i n g l e ' erlauben es dem Benutzer, jeweils nur ein Element aus der Liste auszuwählen. Die beiden anderen Optionen erlauben die Auswahl mehrerer Elemente. $ l i s t b o x - > g e t ( $ l i s t b o x - > c u r s e l e c t i o n ( ) ) . Um das ausgewählte Element der Listbox zu ermitteln, verwenden Sie diese Methode. Das folgende Beispielprogramm stellt eine Liste mit Sprachen dar. Ein Labelfeld gibt das gewählte Element aus. ®Der Autor verwendet Listboxen auch gerne zum Anzeigen von Tabellen, um aus diesen Elemente zu wählen. Hierzu müssen die Listenelemente allerdings entsprechend aufbereitet werden, also beispielsweise durch eine entsprechende Anzahl von Leerzeichen getrennt werden.

218

9 Grafische Benutzerschnittstellen mit Perl/Tk

#!/usr/bin/perl -w # listbox.pl use Tk; # Listenelemente \QListe = qw/Deutsch Englisch Franzoesisch Kisuaheli Italienisch/; # Hauptfenster erzeugen $fenster = MainWindow->new(); # Listbox erzeugen $listbox = $fenster->Listbox(-selectmode => $label = $fenster->Label();

'single');

# Listenelemente einfuegen $listbox->insert('end', OListe); # Das ausgewaehlte Element zurueckgeben $listbox->bind('', sub { $label->configure( -text => $listbox->get($listbox->curselection()))

»; # Steuerelemente packen $listbox->pack(); $label->pack(); # Ereignisschleife MainLoopO ;

Da das Listbox-Widget keine -command-Methode kennt, müssen wir den bind-Mechanismus verwenden, mit dem es möglich ist, beliebige Ereignisse an ein Widget zu binden. Die allgemeine Syntax von bind ist $widget->bind(Ereignis, sub {Eigenschaftsprozedur}); In unserem Beispiel wird auf das Ereignis 'erste Maustaste gedrückt' gewartet. Ein anderes, häufig verwendetes Ereignis ist das Drücken der Return-Taste, welches folgendermaßen angegeben wird. $widget->bind(, sub {Eigenschaftsprozedur}; Um das aktuell gewählte Listenelement zu ermitteln, verwenden wir die Methoden get und c u r s e l e c t i o n in der dargestellten Form. Dabei liest die Methode get den Eintrag an der angegebenen Stelle und gibt diesen zurück. Die allgemeine Syntax lautet $listbox->get(index);

219

9.4 Die Standard-Widgets von Perl/Tk

Deutsch (Englisch Franzoesisch Kîsuaheli Italienisch Kroatisch Ftufìnisch Niederiaendisch Koptisch Lateinisch Africaans | Bayerisch

Abb.

9.10:

Das Scrollbar-Beispiel

mit einer Auswahl



an bedeutenden

Sprachen

Die Methode curselection gibt den Index des aktuell gewählten Listenelementes zurück. Die allgemeine Syntax dieser Methode ist: $listbox->curselection();

9.4.5

Das Scrollbar-Widget

Bei manchen Widget-Typen kann es vorkommen, dass die Widgetgröße nicht ausreicht, um den gesamten Inhalt anzuzeigen. Dies gilt vor allem für die noch zu besprechenden Text- und Canvas-Widgets, aber auch für die im vorhergehenden Abschnitt besprochenen Listbox-Steuerelemente. Um dieses Problem zu lösen, stellt Perl/Tk das Scrollbar-Widget zur Verfügung, welches an beliebige Seiten eines Widgets angedockt werden kann. Im folgenden Beispiel erstellen wir einen vertikalen Scrollbar, den wir am Listbox-Steuerelement aus unserem vorherigen Beispiel andocken. #!/usr/bin/perl -w # scrollbeir.pl use Tk; # Listenelemente QListe = qw/Deutsch Englisch Franzoesisch Kisuaheli Italienisch Kroatisch Rufinisch Niederiaendisch Koptisch Lateinisch Africaans Bayerisch Valonisch Isotonisch Erstaunlich/; # Hauptfenster erzeugen $fenster = MainWindow->new(); # Scrollbar erstellen

9 Grafische Benutzerschnittstellen mit Perl/Tk

220

$scrollbar = $fenster->Scrollbar(); # Listbox erzeugen $listbox = $fenster->Listbox( -selectmode => 'single', -height => 12, -yscrollcommand => ['set' => $scrollbar]); # Srcollbar Ein das Listensteuerelement binden $scrollbar->configure(-command => ['yview' => $listbox]); # Listenelemente einfuegen $listbox->insert('end', OListe); $scrollbar->pack(-side => 'right', -fill => 'y'); $listbox->pack(-side => 'left'); MainLoopO ;

Wie Sie dem obenstehenden Skript entnehmen können, besteht das Erzeugen eines Scrollbars im Wesentlichen aus drei Schritten: 1. Zunächst wird das Scrollbar-Steuerelement in der bekannten Art und Weise mit der Anweisung scrollbar

=container->Scrollbar

erzeugt. 2. Nun müssen Sie vom Steuerelement, an welches der Scrollbar gebunden werden soll, einen Verweis auf das Scrollbar-Widget erstellen. Hierzu dient folgende Zeile. -yscrollcommand => ['set' => $scrollbar]);

Soll statt des vertikalen ein horizontaler Scrollbar erzeugt werden, so ersetzt man das Argument yscrollcommand

durch die Option xscrollcommand.

3. Im letzten Schritt muss nun umgekehrt das Widget, welches gescrollt werden soll, an das Scrollbar-Steuerelement gebunden werden. Dies wird mit folgender Programmzeile erreicht. $scrollbar->configure( -command => ['yview' => $listbox]);

221

9.4 Die Standard-Widgets von Perl/Tk Hier müssen Sie gegebenenfalls die Option 'yview' durch 'xview' ersetzen.

Scrollbar-Widgets sind standardmäßig vertikal ausgerichtet, auch wenn Sie es als horizontalen Scrollbalken verwenden möchten. Um die Ausrichtung des Scrollbars zu verändern, müssen Sie die Eigenschaft - o r i e n t setzen. Mögliche Eigenschaftswerte sind ' h o r i z o n t a l ' und ' v e r t i c a l ' . Wie Sie an unserem Beispiel gesehen haben, braucht der Eigenschaftswert ' v e r t i c a l ' nicht explizit gesetzt werden, da er der Standardwert Interessant sind die Aufrufe der Methode p a c k ( ) , welche in diesem Beispiel mit Attributen verwendet wurden. Die Option - s i d e gibt an, auf welcher Seite des Fensters (oder des umgebenden Containers) ein Widget ausgerichtet wird. Für diese Option gibt es vier mögliche Einstellungen: ' l e f t ' (links), ' r i g h t ' (rechts), ' t o p ' (oben) und ' b o t t o m ' (unten). Der Parameter - f i l l => ' y ' gibt an, dass der Scrollbar den zur Verfügung stehenden Raum in vertikaler Richtung ausfüllen soll. Die Methode p a c k ( ) wird ausführlich in einem späteren Abschnitt behandelt. Dies sollte Sie aber nicht daran hindern, ein wenig zu spielen, indem Sie die Parameter verändern oder ganz weg lassen und das jeweilige Ergebnis betrachten.

9.4.6

Das Entry-Widget

Sicherlich haben Sie sich bereits gefragt, wie es bei Perl/Tk möglich ist, einer Anwendung Ihre Texteingaben mitzuteilen. Nun, eine Möglichkeit ist das Entry-Widget, mit Hilfe dessen Sie einzeilige Eingaben vornehmen können. Eine andere Möglichkeit stellt das weiter unten dargestellte Text-Widget dar. Das Entry-Widget ist in seiner Handhabung recht einfach und bringt gegenüber den bisher besprochenen Steuerelementen wenig Neues. Wir können uns daher direkt einem Beispiel zuwenden. _ B X' Geben Sie etwas ein:

Sie haben eingegeben: Herbert

Abb. 9.11:

Ein Entry-Widget

# ! / u s r / b i n / p e r l -w # entry.pl

in Aktion

J

222

9 Grafische Benutzerschnittstellen mit Perl/Tk

use Tk; # Textvaxiable initialisieren $var = " ; # Hauptfenster erzeugen $fenster = MainWindow->new(); # Label-Widget erstellen $labell = $fenster->Label( -text => 'Geben Sie etwas ein:'

); # Entry-Widget erzeugen $entry = $fenster->Entry( -textvariable => \$var

); # zwei Label-Widgets fuer die Ausgabe erzeugen $label2 = $fenster->Label( -text => 'Sie haben eingegeben:'

); $label3 = $fenster->Label( -textvariable => \$var

); # Die Widgets auf der Oberflaeche packen $labell->pack(); $entry->pack(); $label2->pack(); $label3->pack(); # Die Ereignisschleife starten MainLoopO ;

Wenn Sie das Beispiel betrachten, achten Sie bitte darauf, dass wir diesmal sowohl beim Label- als auch beim Entry-Widget über die Eigenschaft -textvariable eine Referenz auf die gleiche Variable verwenden. Dadurch werden diese beiden Steuerelemente miteinander " verknüpft". Sobald Sie den Inhalt des Entry-Widgets verändern, wird dies unmittelbar vom Label-Widget gespiegelt. Lassen Sie uns nun noch einen Blick auf die wichtigsten Methoden des Entry-Widgets werfen.

9.5 Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk

223

$var = $ e n t r y - > g e t ( ) M i t Hilfe der Methode g e t ( ) ist es möglich, den Inhalt eines Entry-Widgets auszulesen und zurückzugeben. In unserem Beispiel wird der Inhalt in der Variablen $var hinterlegt. $ e n t r y - > i n s e r t ( index, ' z e i c h e n k e t t e ' ) . Wie schon beim Label-Widget, dient auch hier die Funktion i n s e r t O dazu, eine Zeichenkette an der mit index bezeichneten Position in ein Entry-Widget einzufügen. Der kleinste mögliche Index ist 0, der größte 'end\ $ e n t r y - > d e l e t e ( s t a r t l n d e x [, endlndex]). Mit der Methode d e l e t e O können Teile vom Inhalt eines Entry-Widgets gelöscht werden. Um alle Zeichen zu löschen, verwenden Sie folgenden Code. $entry->delete(0,

'end');

Wird die Angabe endlndex fortgelassen, werden alle Zeichen ab s t a r t Index bis zum Textende gelöscht. Auch mit folgender Anweisung können Sie daher den gesamten Inhalt eines Entry-Steuerelementes löschen. $entry->delete(0) ;

9.5

Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk

Bisher haben wir die wichtigsten Standard-Steuerelemente vorgestellt, die bei der Entwicklung von grafischen Benutzerschnittstellen stets eingesetzt werden. Mit Ihnen können Sie bereits recht ansehnliche Anwendungen erstellen. Sehen wir einmal davon ab, dass wir uns bisher noch kaum mit dem Geometrie-Management von Perl/Tk beschäftigt haben, und Sie auch noch keine Möglichkeit kennen, Menü-Strukturen in Ihr Programm zu integrieren. 10 Wir möchten Ihnen aber auch einige exotischere Widgets vorstellen, die dem Autor wichtige Dienste bei der Erstellung und Strukturierung von Anwendungen leisten. Wenn Sie sich ein wenig im Internet umschauen, werden Sie eine Reihe weiterer Steuerelemente finden, die Sie ebenfalls einsetzen können.

9.5.1

Das NoteBook-Widget von Perl/Tk

Das NoteBook-Widget ist eine Art Container-Steuerelement, ähnlich dem im Abschnitt Geometrie-Management mit Perl/Tk beschriebenen Frame-Steuerelement, das wie dieses in der Lage ist, weitere Widgets aufzunehmen. Mit dem NoteBook-Steuerelement ist es möglich, mehrere Fenster innerhalb eines einzigen Fensters anzuordnen. Die einzelnen Fenster liegen ähnlich einem Stapel Karteikarten übereinander, und sichtbar ist das jeweils oben liegende Fenster. Jedes Fenster besitzt einen Reiter, über den es in den Vordergrund gebracht werden kann. Lassen Sie uns zunächst ein Beispiel-Skript betrachten. 10

Beides werden wir in einem späteren Abschnitt ausführlich darstellen.

224

9 Grafische Benutzerschnittstellen mit Perl/Tk « n *' Name

Adresse

Name

ti Abb.

9.12:

Das

NoteBook-Beispiel

#!/usr/bin/perl -w # notebook.pl # Ein Hauptfenster erstellen $mw = MainWindow->new(); $mw->title('Notebook'); use Tk; # NoteBook-Widget einbinden use Tk::NoteBook; # Ein NoteBook-Steuerelement erstellen $notebook = $mw->NoteBook( # die standardmaessig eingestellte Schrift ist riesig -font => 'Helvetica 12 bold') ->pack( # die folgenden Optionen werden spaeter beschrieben -side => 'top', -expand => 1, -fill => 'both'); # zwei Register im NoteBook erzeugen $regName = $notebook->add( 'Name', -label => 'Name', -underline => 0) ; $regAdr = $notebook->add( 'Adresse', -label => 'Adresse', -underline => 0); # Steuerelemente auf den Seiten platzieren $lblName = $regName->Label( -text => 'Name')->pack( -side => 'left'); $txtName = $regName->Entry( -width => 24)->pack(

9.5 Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk

225

- s i d e => ' l e f t ' ) ; $lblAdr = $regAdr->Label( - t e x t => 'Adresse')->pack( - s i d e => ' l e f t ' ) ; $txtAdr = $regAdr->Entry( -width => 24)->pack( - s i d e => ' l e f t ' ) ; # Ereignisschleife starten MainLoopO ; In diesem Beispiel sehen Sie einiges Neues, was wir Ihnen nun kurz erläutern möchten. Da es sich beim NoteBook-Widget nicht um ein Standard-Steuerelement handelt, muss dieses explizit mit der Anweisung use Tk::NoteBook; zusätzlich zur Standard-Bibliothek Tk eingebunden werden. Ein NoteBook-Widget wird auf die üblich Art und Weise erstellt und in das Hauptfenster eingebunden. Leider ist die voreingestellte Schriftart für die Beschriftung der Register-Schaltflächen riesengroß, sodass wir eine etwas kleinere Schriftart auswählen. Dies geschieht mit der folgenden Zeile. - f o n t => ' H e l v e t i c a 12 b o l d ' Innerhalb des NoteBook-Widgets legen wir anschließend zwei Register an. Dies geschieht mit Hilfe der Methode add des NoteBook-Steuerelementes. Als erstes Argument übergeben wir dieser Methode den Namen des Registers, der von der Beschriftung des Registers verschieden sein kann. Erst nachdem der Name übergeben wurde, werden die weiteren Argumente in der bekannten Hash-Notation angegeben. Achten Sie bitte darauf, dass der Name angegeben werden muss! Vergessen Sie dies, wird der erste Hash-Schlüssel als Name gelesen, der erste Wert als Schlüssel der ersten Eigenschaft usw. und Sie erhalten als Quittung eine nur schwer zu interpretierende Fehlermeldung. Über die Eigenschaft - u n d e r l i n e legt man fest, welcher Buchstabe des Labels unterstrichen dargestellt wird. Zur Laufzeit kann der Anwender die entsprechende Register-Seite dann mit der Tasten-Kombination Alt + Unterstrichener Buchstabe aktivieren. Geben Sie diese Eigenschaft nicht an, ist der Defaultwert auf -1, was bewirkt, dass kein Buchstabe unterstrichen erscheint. Auf den Register-Karten legen wir nun einige Steuerelemente in der gewohnten Art an. Beachten Sie hier, dass die Steuerelemente nicht wie bisher im Hauptfenster, sondern in der jeweiligen Register-Karte untergebracht sind. Sie sehen hier erstmals, dass die verschiedenen Steuerelemente einer Anwendung eine Hierarchie bilden können. Auf die etwas komplizierter erscheinenden pack ()-Methoden gehen wir an dieser Stelle nicht ein und verweisen Sie auf den entsprechenden Abschnitt.

226

9 Grafische Benutzerschnittstellen mit Perl/Tk

Abschließend stellen wir Ihnen noch einige wichtige Eigenschaften und Methoden des NoteBook-Widgets vor: -createcmd => callback. Mit dieser Methode kann eine Callback-Funktion angegeben werden, die aufgerufen wird, wenn die entsprechende Register-Karte das erste Mal aktiviert wird. -raisecmd => callback. Diese Methode entspricht der soeben beschriebenen mit dem Unterschied, dass die Callback-Funktion jedesmal aufgerufen wird, wenn die RegisterKarte in den Vordergrund geholt wird. - S t a t e ' s t a t u s ' . Über diese Methode kann festgelegt werden, ob es dem Benutzer erlaubt ist, das entsprechende Register in den Vordergrund zu holen. Mögliche Optionswerte sind 'normal' (der Benutzer kann das Register aktivieren) und ' d i s a b l e d ' (der Benutzer kann dies nicht tun). Der Standard ist 'normal'. $ n o t e b o o k - > d e l e t e ( ' r e g i s t e r n a m e ' ) . Mit Hilfe dieser Methode ist es möglich, die mit registername bezeichnete Register-Seite zu löschen. $ n o t e b o o k - > r a i s e ( ' r e g i s t e r n a m e ' ) . Um eine Registerkarte in den Vordergrund zu holen, kann diese Methode verwendet werden. $var = $notebook->raised. Diese Methode gibt den Namen der sich im Vordergund befindlichen Registerseite zurück.

9.5.2

Das BrowseEntry-Widget von Perl/Tk

Auch das BrowseEntry-Steuerelement gehört nicht zu den Standard-Widgets von Perl/Tk. Es entspricht in seiner Funktion den von MS-Windows-Systemen her bekannten sogenannten Äom&o&oz-Steuerelementen. Sie stellen dem Benutzer ein Textfeld zur Verfügung, auf dessen rechter Seite sich eine Schaltfläche befindet. Klickt der Anwender diese Schaltfläche an, öffnet sich nach unten eine Liste mit Optionen, aus welchen der Anwender ein Element auswählen kann. Die Liste entspricht in Ihrer Funktion dem oben besprochenen Listbox-Widget. Das jeweils ausgewählte Element wird im Textfeld angezeigt. Es ist dem Anwender allerdings auch möglich, im Textfeld manuell einen Wert einzutragen. Dieses Widget eignet sich also besonders als platzsparender Ersatz des Listbox-Steuerelementes. Der Autor verwendet dieses Widget vor allem in seinen Datenbank-Applikationen, um dem Anwender eine Auswahl unter festgelegten Alternativen zu ermöglichen. Leider hat das BrowseEntry-Widget einige Ecken und Kanten, die sich allerdings leicht umschiffen, bzw. beheben lassen. Wir werden Ihnen weiter unten zeigen, wie Sie hierbei vorgehen müssen. Zunächst möchten wir Ihnen ein kurzes Beispielprogramm zum BrowseEntry-Widget vorstellen.

9.5 Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk -

227

B Kl

Anrede

|-ierr"unci Frau

Abb.

9.13:

Das

BrowseEntry-Beispiel

#!/usr/bin/perl -w # browseEntry.pl # Einbinden der Bibliotheken use Tk; use Tk:¡BrowseEntry; # Ein Hauptfenster erstellen my $mw=MainWindow->new(); $mw->title('BrowseEntry'); # Ein Labelsteuer-Element erzeugen # Die pack()-Optionen werden im entsprechenden # Abschnitt beschrieben my $label = $mw->Label( -text => 'Anrede', -anchor => 'w')->pack( -expand => 1, -fill => 'both'); # Das BrowseEntry-Steuerelement erzeugen my $cboAnrede = $mw->BrowseEntry( width => 24)->pack( -expand => 1, -fill => 'both'); # Liste mit Elementen fuer BrowseEntry my \@anreden = ('Frau', 'Herr', 'Herr und Frau', 'Firma', 'Mr', 'Ms'); # Vorsichtshalber evtl. existierende Elemente loeschen $cboAnrede->delete(0, 'end'); # Listen-Elemente einfuegen $cboAnrede->insert('end', \@anreden); # es ist zwar unhoeflich $cboAnrede->insert(2, 'Fr"aulein');

228

9 Grafische Benutzerschnittstellen mit Perl/Tk

# Ereignisschleife MainLoopO ;

Aus dem obenstehenden Beispiel kann man bereits die wichtigsten Eigenschaften und Optionen des BrowseEntry-Steuerelementes entnehmen. Wie das NoteBook-Widget muss auch das BrowseEntry-Steuerelement über ein useStatement eingebunden werden, bevor wir es verwenden können. Das Einfügen und Löschen der Listenelemente geschieht analog einem Listbox-Steuerelement mit den Anweisungen i n s e r t und d e l e t e . Weitere Eigenschaften und Methoden des BrowseEntry-Widgets sind: Wie die meisten bisher besprochenen Widgets besitzt auch das BrowseEntry die Möglichkeit, direkt an eine Variablen-Referenz gebunden zu werden. Hierdurch sind diese Steuerelemente besonders gut in Datenbank-Anwendungen einsetzbar.

-browsecmd => c a l l b a c k . Besonders bedeutsam für Eingabe-Validierungen11 ist die Möglichkeit, an das Treffen einer Auswahl eine Callback-Funktion zu binden. Die Methode -browsecmd übergibt als Argumente den Namen des Widgets und das ausgewählte Listenelement. Das erste Argument werden Sie wahrscheinlich eher selten verwenden, da dies in einer sehr Perl-eigenen Notation übergeben wird. Beachten Sie zudem, dass die Unterroutine erst aufgerufen wird, nachdem der gewählte Wert an die VariablenReferenz weitergegeben wurde. Soll also, im Fall einer Fehleingabe durch den Anwender, der ursprüngliche Wert wiederhergestellt werden, so muss er vor der Auswahl f ächert werden. Dies kann beispielsweise mit der im Folgenden beschriebenen Methode lästernd geschehen. - l i s t c m d => callback. Auch mit Hilfe dieser Methode ist es möglich, eine CallbackRoutine an das Widget zu binden, die stets aufgerufen wird, wenn der Benutzer den Pfeil neben dem Textfeld anklickt, bevor die Liste aufgeklappt wird. Dies wäre somit der geeignete Mechanismus, den aktuell ausgewählten Text zu sichern, bevor der Anwender einen neuen auswählt. Vermutlich haben Sie bereits das "Problem" des BrowseEntry-Widgets erkannt: Wenn Sie die Benutzerschnittstelle betrachten, fällt Ihnen sicherlich sofort auf, dass das BrowseEntry gegenüber dem Label-Widget einen Hauch nach rechts eingerückt erscheint. Dies kann auf komplexen Benutzerschnittstellen recht störend wirken. Zwei Ursachen sind hierfür verantwortlich: zum einen besitzt das BrowseEntry die Möglichkeit, über die Eigenschaft - l a b e l eine Beschriftung des Widgets anzugeben. Auch wenn dies nicht geschieht, beansprucht das Label einen gewissen Raum. Das Label lässt sich allerdings nicht besonders formatieren, so dass man dieses in der Regel selten verwendet. Die 11 Eine Eingabe-Validierung ist eine Routine, die überprüft, ob eine Eingabe, die der Anwender durchgeführt hat, den Anforderungen der Anwendung entspricht.

9.5 Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk

229

zweite Ursache hierfür liegt darin, dass die einzelnen Komponenten im Code des Steuerelementes mit der Option -padx => 1 "gepackt" sind, wodurch ein zusätzlicher Raum rechts und links des Widgets geschaffen wird. 12 Um die Probleme zu beseitigen, möchten wir mit Ihnen gemeinsam ein "eigenes" BrowseEntry-Steuerelement schreiben. Gehen Sie hierzu wie folgt vor: 1. Als erstes kopieren Sie das Original BrowseEntry-Widget in Ihr lokales ProjektVerzeichnis. Auf einem Debian GNU/LINUX-System finden Sie die Datei im Verzeichnis: /usr/lib/perl5/Tk unter dem Namen BrowseEntry.pm. 2. Sie sollten nun den Dateinamen Ihrer Kopie ändern, ich habe meine Version MyBrowseEntry. pm g e n a n n t .

3. Als nächstes öffnen Sie mit Ihrem Editor die Datei MyBrowseEntry zum Bearbeiten. 4. Nun müssen Sie die package-Anweisung zu Beginn der Datei ändern. Ersetzen Sie die Zeile package Tk::BrowseEntry;

durch eine in der folgenden Art package MyBrowseEntry;

5. Ersetzen Sie im gesamten Projekt-File alle Verweise auf BrowseEntry durch solche auf Ihre Version (Also durch MyBrowseEntry). 6. Nun müssen Sie einige Zeilen aus dem Programm-Code des Widgets auskommentieren (indem Sie jeweils ein Kommentarzeichen # voranstellen). Im Einzelnen handelt es sich um die folgenden Zeilen, die Sie alle relativ weit oben im Programmcode finden. my $lpack = delete $args->{-labelPack}; if (not defined $lpack) { $lpack = [-side => 'left', -anchor => 'e'];

> 7. Suchen Sie nun die folgenden Zeilen in Ihrem Programm-Code my $e = $w->LabEntry(-labelPack => $lpack, -label => delete $args->{-label}, -textvaxiable => \$var,); 12 W i r werden die Optionen -padx und -pady im Z u s a m m e n h a n g mit d e m Geometrie-Manager pack() ausführlich besprechen.

230

9 Grafische Benutzerschnittstellen mit Perl/Tk und ändern diese wie folgt ab. my $e = $w->LabEntry(#-labelPack => $lpack, # - l a b e l => d e l e t e $ a r g s - > { - l a b e l } , - t e x t v a r i a b l e => \ $ v a r , ) ;

8. Abschließend müssen Sie noch die folgenden Zeilen suchen $b->pack(-side => ' r i g h t ' , -padx => 1); $e->pack(-side => ' r i g h t ' , - f i l l => ' x ' , -expand => 1, -padx => 1);

und diese wie unten dargestellt ändern, $b->pack(-side => ' r i g h t ' ) ; $e->pack(-side => ' r i g h t ' , - f i l l => ' x ' , -expand => 1);

indem Sie die -padx-Optionen entfernen. Sie sollten nicht vergessen, den geänderten Code zu speichern. In Zukunft können Sie Ihre eigene Version des BrowseEntrys verwenden. Hierzu binden Sie das Modul mit der Anweisung use MyBrowseEntry; in Ihre Anwendung ein. Beachten Sie aber bitte, dass sich die Datei MyBrowseEntry. pm hierzu im selben Verzeichnis mit Ihrer Anwendung befinden muss. 13

9.5.3

Das HList-Widget von Perl/Tk

Mit dem HList-Steuerelement ist es möglich, hierarchische Listen anzuzeigen. Die häufigsten Formen von hierarchischen Listen sind die Verzeichnisstrukturen von ComputerSystemen. Hierbei zweigen von einem Wurzel- oder Root-Verzeichnis weitere Verzeichnisse ab. Jedes dieser Verzeichnisse kann wiederum weitere Verzeichnisse enthalten. Es ergibt sich also eine baumartige Struktur, die in HList-Widgets darstellbar ist. Dabei ist es möglich, die einzelnen Hierarchieebenen mittels eines Doppelklicks ein- oder aus zu blenden. Die Elemente und Ebenen können mit Linien verbunden und mit Icons versehen werden. Im folgenden Beispiel möchten wir Ihnen eine Anwendung zeigen, die von der üblicherweise dargestellten Verzeichnisstruktur abweicht. Wir stellen einen stark verkürzten

9.5 Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk

MB

h•

231

K El Familie Maus —Boodhound Pluto —Minnie Maus — M i c k e y Maus

J

El Familie Duck

1 /

Abb. 9.14: Der Familien-Stammbaum

in einem

HList-Widget

Stammbaum dar. Hiermit möchten wir aufzeigen, dass dies schnell zu recht kompliziertem Code führen kann, wenn die Liste über mehrere Ebenen verschachtelt ist. Wir verwenden daher eine geschachtelte Datenstruktur in Form eines Hashes von Hashes.

# ! / u s r / b i n / p e r l -w # hlistl.pl use Tk; use Tk::HList; # Hashes f u e r Stammbaum-Daten '/.Duck = ( ' D a i s y ' => 'Daisy Duck', 'Donald' => 'Donald Duck', ' T i c k ' => 'Tick Duck'); '/.Maus = ( 'Minnie' => 'Minnie Maus', 'Mickey' => 'Mickey Maus', ' P l u t o ' => 'Bloodhound P l u t o ' ) ; # Hashes zu einem Hash von Hashes zusammenfassen '/,Stammbaum = ( 'Duck' => Y/,Duck, 'Maus' => Y/.Maus); # Hauptfenster e r s t e l l e n my $mw = MainWindow->new(); $mw->title('HList-Beispiel'); 13 Im Kapitel Arbeiten mit Perl-Modulen haben wir beschrieben, wie Sie ein Modul so ablegen können, dass es sich nicht im aktuellen Verzeichnis befinden muss.

232

9 Grafische Benutzerschnittstellen mit Perl/Tk

# Bitmaps einlesen $open = $mw->Photo(-file => ',/plus.xpm'); $closed = $mw->Photo(-file => './minus.xpm'); # HList-Steuerelement erstellen my $hlist = $mw->Scrolled('HList', # Verbindungslinien der Elemente darstellen -drawbranch => 1, # Trennzeichen fuer Pfad auf Bindestrich festlegen -separator => # Einzug auf 20 Pixel setzen -indent => 20, # Callback-Routine fuer Doppelklick -command => \&ausUndEinblenden)->pack(); # Stammbaum einlesen stammbaumAnzeigenO ; MainLoopO ; sub stammbaumAnzeigen { # Alle Schluessel des Hashes durchlaufen foreach $key (keys '/,Stammbaum) { # den Schluessel der path-Eigenschaft zuweisen $path = $key; # Knoten zufuegen $hli st->add($path, # Element kann Text und Icon einzeigen -itemtype => 'imagetext', # Angezeigter Text -text => "Familie $key" , # Steuerelement Icon zuweisen -image => $open

>

);

} sub ausUndEinblenden { # Pfad zu angeklicktem Element auslesen $path = $_ [0]; # Das naechste Element auslesen $naechster = $hlist->info('next', $path); # Testen, ob naechstes Element dem angeklickten Element untergeordnet if (!$naechster II (index ($naechster, "$path-" ) == -1)) { # Nein, die Liste ist zugeklappt; Elemente anzeigen # Icon fuer geoeffnete Liste anzeigen $hlist->entryconfigure($path, -image => $closed);

9.5 Einige 'Nicht-Standard'-Steuerelemente von P e r l / T k

233

# Elemente der Liste anzeigen foreach. $key (keys ,/,{$Staimnbaum{$path}'}-) { # Elemente Liste anzeigen $hlist->add("$path-$key", -text => $Stammbaum{$pathH$key>

>

);

> eise { # Das naechste Element ist dem aktuellen untergeordnet # Schliessen-Bitmap anzeigen $hlist->entryconfigure($path, -image => $open); # Liste durch Loeschen der Elemente zuklappen $hlist->delete('offsprings', $path

>

>

);

Bevor wir die Eigenschaften des HList-Widgets erläutern, möchten wir zum Vergleich die entscheidenden Codefragmente darstellen, ohne verschachtelte Datenstrukturen einzusetzen. Sie können sich vorstellen, wie kompliziert und kryptisch der Code aussehen würde, wäre der S t a m m b a u m auch nur eine Generation tiefer geschachtelt. Verwenden Sie hingegen geschachtelte Strukturen, können Sie beliebig tief in Ihre Familiengeschichte eintauchen, ohne vorher ein Dutzend Sitzungen mit Ihrem Psychiater vereinbaren zu müssen.

sub stammbaumAnzeigen { # Die Knoten fuer die H-Liste zufuegen $path = 'Duck'; $hli st->add($path, # Element kann Text und Icon anzeigen -itemtype => 'imagetext', # Angezeigter Text -text => 'Familie Brand', # Steuerelement Icon zuweisen -image => $open); $path = 'Maus'; $hlist->add($path, -itemtype => 'imagetext', -text => 'Familie Seidel', -image => $open);

> sub ausUndEinblenden { # Pfad zu angeklicktem Element auslesen

234

9 Grafische Benutzerschnittstellen mit P e r l / T k $path = $_ [0]; # Das naechste Element auslesen $naechster = $hlist->info('next', $path); # Testen, ob naechstes Element dem angeklickten Element untergeordnet if (!$naechster II (index ($naechster, "$path-" ) == -1)) { # Nein, die Liste ist zugeklappt; Elemente anzeigen if ($path eq 'Duck') { # Icon fuer geoeffnete Liste anzeigen $hlist->entryconfigure($path, -image => $closed); # Elemente Liste Duck anzeigen $hlist->add('Duck-Daisy', -text => 'Daisy Duck'); $hlist->add('Duck-Donald', -text => 'Donald Duck'); $hlist->add('Duck-Tick', -text => 'Tick Duck');

> if ($path eq 'Maus') { # Das gleiche fuer Liste Maus $hlist->entryconfigure($path, -image => $closed); $hlist->add('Maus-Minnie', -text => 'Minnie Maus'); $hlist->add('Maus-Mickey', -text => 'Mickey Maus'); $hlist->add('Bloodhound-Pluto', -text => 'Pluto Bloodhound');

>

} else { # Das naechste Element ist dem aktuellen untergordnet # Schliessen-Bitmap anzeigen $hlist->entryconfigure($path, -image => $open); # Liste durch Loeschen der Elemente zuklappen $hlist->delete('offsprings', $path);

}

>

Falls Sie mit den verschachtelten Datenstrukturen Probleme hatten, schlagen Sie im entsprechenden Kapitel des ersten Teils nach. Wir möchten Ihnen nun die wichtigsten Eigenschaften des HList-Widgets kurz vorstellen. Das HList-Steuerelement muss, wie die bisher besprochenen Nicht-Standard-Widgets, über eine use-Anweisung eingebunden werden. use Tk::HList;

9.5 Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk

235

Die einzelnen Einträge im HList-Widget werden über ihren Pfad festgelegt. Dabei weist der Name dieser Eigenschaft auf den Hauptverwendungszweck dieses Steuerelementes hin, nämlich der Darstellung von Verzeichnisbäumen. Natürlich können, wie wir gerade gesehen haben, auch andere hierarchische Daten dargestellt werden. Die Eigenschaft Path legt dann die hierarchische Beziehung der Elemente untereinander fest. Die Pfadeigenschaft hat daher stets, wie oben gesehen, die folgende Struktur. Hauptelement/Unterelement/UnterUnterelement/etc. Im Folgenden geben wir Ihnen einen Überblick über die wichtigsten Eigenschaften und Methoden des HList-Steuerelementes: - d a t a => ' e i n t r a g ' . Mitunter soll einem Eintrag ein vom angezeigten Text abweichender Wert zugeordnet werden. Wenn Sie beispielsweise mit Datenbanken arbeiten, kann es sein, dass Sie einen Text anzeigen (wie etwa eine Anrede), in der Datenbank selbst aber einen Zahlenwert eintragen möchten. Um dies zu erreichen, hinterlegen Sie unter der Eigenschaft - d a t a des Eintrages den gewünschten Wert. Beachten Sie, dass hier eine Zeichenkette hinterlegt werden muss! 14 -drawbranch => Oll. Diese Eigenschaft legt fest, ob die einzelnen Elemente mit Linien verbunden werden sollen (-drawbranch => 1) oder nicht (-drawbranch => 0). - i t e m t y p e => ' i m a g e t e x t ' | ' t e x t ' . Diese Eigenschaft dient dazu, anzugeben, ob links vom jeweiligen Eintrag zusätzlich ein Icon dargestellt werden soll ( - i t e m t y p e => ' imaget e x t ' ) oder nicht ( - i t e m t y p e => ' t e x t ' ) . Zumeist geben Sie diese Eigenschaft bei der ->add-Methode oder der ->entryconf igure-Methode an. -image => ' b i t m a p ' . Um einem Eintrag ein Image zuzuweisen, verwenden Sie diese Methode. Es ist nur möglich, einem Eintrag ein Icon zuzuordnen, bei dem Sie vorher die Eigenschaft -imagetype auf ' i m a g e t e x t ' gesetzt haben. i n d e n t => p i x e l . Mit Hilfe dieser Eigenschaft legen Sie fest, um wieviel Pixel ein untergeordnetes Element gegenüber seinem übergeordneten Element eingezogen werden soll. - S e p a r a t o r => ' Z e i c h e n ' . Sie können mit dieser Eigenschaft das Trennzeichen zwischen den einzelnen Ebenen der Liste festlegen. Wenn Sie eine Verzeichnis-Struktur darstellen möchten wird dies auf UNIX-Systemen das /-Zeichen, auf Win32-Systemen hingegen das \-Zeichen sein. Wie oben gesehen, kann allerdings jedes beliebige Zeichen verwendet werden. - t e x t => ' a n g e z e i g t e r T e x t ' . Der im HList zu einem Eintrag dargestellte Text muss nicht mit der Angabe zur Eigenschaft p a t h übereinstimmen. Vielmehr kann über die Eigenschaft - t e x t zu jedem Element ein eigener Label-Text festgelegt werden, der im Steuerelement angezeigt wird. 14

Was angesichts der Typenfreiheit von Perl in keinem Falle zu Problemen führen sollte.

236

9 Grafische Benutzerschnittstellen mit Perl/Tk

-command => ' c a l l b a c k ' . Das -command-Ereignis des HList-Widgets ist, anders als bei den Button-Steuerelementen, an das Doppelklick-Ereignis des Steuerelementes gebunden. Wenn Sie unser Beispiel genau betrachten, sehen Sie noch etwas Neues: Haben wir bisher bei dieser Methode eine Unterroutine definiert 15 , hinterlegen wir diesmal eine Referenz auf eine Unterroutine. Dies erscheint in den meisten Fällen sinnvoller als eine Subroutine zu erstellen, die lediglich eine andere Unterroutine aufruft. $ h l i s t - > e n t r y c o n f i g u r e ( $ p a t h , - e i g e n s c h a f t => ' w e r t ' ) . Um im Code auf ein HList-Widget Bezug zu nehmen, verwenden Sie diese Methode. Sie funktioniert analog zur Methode $ w i d g e t - > c o n f i g u r e ( - e i g e n s c h a f t => ' w e r t ' ) , die wir weiter oben beschrieben haben. Allerdings müssen wir angeben, auf welchen Eintrag sich der Zugriff beziehen soll, daher geben wir zusätzlich zu den Eigenschaft-Wert-Paaren auch den Pfad zum gewünschten Element an. $ h l i s t - > a d d ( $ p a t h , - t e x t => ' a n g e z e i g t e r T e x t ' , [ - e i g e n s c h a f t => 'Wert' . . . ] . Mit der add-Methode erzeugen Sie neue Listeneinträge. Notwendig ist hierbei lediglich die Angabe des Pfades, alle anderen Eigenschaften sind optional. In der Regel werden Sie aber zumindest einen Anzeigetext mit der Eigenschaft - t e x t zuweisen. Das HList-Widget verfügt über eine ganze Reihe von Methoden zum Löschen von Einträgen: $hlist->deleteEntry($path). Um einen einzelnen Eintrag zu löschen, verwenden Sie diese Methode. $ h l i s t - > d e l e t e O f f s p r i n g s ( $ p a t h ) . Mit dieser Methode werden alle Einträge gelöscht, die dem Element $path untergeordnet sind. Wie Sie im oben stehenden Beispiel erkennen, können Sie dies auch mit der Anweisung $ h l i s t - > d e l e t e ( ' o f f s p r i n g s ' , $path) bewerkstelligen. $ h l i s t - > d e l e t e A l l . Schließlich ist es auch möglich, alle Listeneinträge auf einmal zu löschen, indem Sie diese Methode verwenden.

9.5.4

Tabellarische Daten mit dem TixGrid-Widget anzeigen

Als letztes Widget in diesem Abschnitt möchten wir Ihnen das TixGrid-Widget 16 von Perl/Tk vorstellen. Mit Hilfe dieses Widgets können Sie Daten und Abbildungen in einer 15

Wir haben dies immer mit Hilfe der Eigenschaft sub getan. Wenn Sie den Schalter -w anwenden, werden Sie feststellen, dass das TixGrid-Widget als deprecated (engl, ablehnen) gekennzeichnet ist. Dies bedeutet, die Macher von Perl/Tk sind der Meinung, das Widget sei veraltet. Wir bevorzugen allerdings dennoch dieses Steuerelement, da es sehr leicht anzuwenden und zu programmieren ist. 16

237

9.5 Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk

M J i x G n d Beispiel

a

O

X

Duck

Daisy

Erpelweg 4 B

1234

Duesentrieb

Daniel

Einsteingasse 27

1234

Flash

Gordon

In der Donnertjuechse 15 5432

Maus

Mickey

Im Mausfeld 3

1204

L Abb.

9.15:

Eine

'Adress-Datenbank'

im

TixGrid-Widget

zweidimensionalen Matrix, also in einer Tabelle, darstellen und editieren. Das TixGridWidget ist also besonders im Zusammenhang mit Datenbank-Applikationen sinnvoll einzusetzen. Im folgenden Beispiel-Skript verwenden wir dieses Steuerelement, um die Daten einer Adress-"Datenbank" auszugeben. Für die Daten verwenden wir wiederum eine verschachtelte Datenstruktur, diesmal ein Array von Hashes17. # ! / u s r / b i n / p e r l -w # TixGrid.pl use Tk; # TixGrid-Widget einbinden use Tk::TixGrid; # D a t e n s t r u k t u r f u e r Tabellen-Eintraege f e s t l e g e n my Oadressen = ( {'Name' => 'Duck', 'Vorname' => ' D a i s y ' , ' S t r a s s e ' => 'Erpelweg 4 B ' , >PLZ'=> '12345', ' O r t ' => 'Entenhausen'}, {'Name' => ' D u e s e n t r i e b ' , 'Vorname' => ' D a n i e l ' , ' S t r a s s e ' => ' E i n s t e i n g a s s e 2 7 ' , 'PLZ' => ' 1 2 3 4 5 ' , ' O r t ' => 'Entenhausen'}, 17 Denken Sie mal darüber nach, warum in diesem Beispiel ein Array von Hashes günstiger ist als ein Hash von Hashes. Dies ist eine Frage, die Sie sich bei komplexen Anwendungen immer wieder stellen sollten, sonst rennt man allzu leicht in "böse Fallen".

238

9 Grafische Benutzerschnittstellen mit Perl/Tk {'Name' => ' F l a s h ' , 'Vorname' => 'Gordon', ' S t r a s s e ' => ' I n der Donnerbuechse 1 5 ' , 'PLZ' => ' 5 4 3 2 1 ' , ' O r t ' => 'Sim C i t y ' } , {'Name' => 'Maus', 'Vorname' => 'Mickey', ' S t r a s s e ' => 'Im Mausfeld 3 ' , 'PLZ' => ' 1 2 0 4 5 ' , ' O r t ' => ' H a m e l n ' » ;

# Hauptfenster erzeugen my $mw = MainWindow->new(); $mw->title('TixGrid-Beispiel'); # TixGrid-Widget erzeugen my $tixGrid = $mw->Scrolled('TixGrid')->pack(); # Spaltenbreiten festlegen $tixGrid->sizeColumn(0, - s i z e => 100); $tixGrid->sizeColumn(l, - s i z e => 70); $tixGrid->sizeColumn(2, - s i z e => 150); $tixGrid->sizeColumn(3, - s i z e => 30); $tixGrid->sizeColumn(4, - s i z e => 100); # In S c h l e i f e Elemente in Tabellen-Steuerelement anzeigen f o r (my $x = 0; $x s e t ( 0 , $x, # Typ des E i n t r a g e s setzen -itemtype => ' t e x t ' , # A t r r i b u t e der Adresse d e r e f e r e n z i e r e n und ausgeben - t e x t => ${$adressen[$x]M'Name'}) ; $ t i x G r i d - > s e t ( 1 , $x, -itemtype => ' t e x t ' , - t e x t => ${$adressen[$x]}{'Vorname'}); $ t i x G r i d - > s e t ( 2 , $x, -itemtype => ' t e x t ' , - t e x t => $ { $ a d r e s s e n [ $ x ] } { ' S t r a s s e ' } ) ;

9.5 Einige 'Nicht-Standard'-Steuerelemente von Perl/Tk

239

$ t i x G r i d - > s e t ( 3 , $x, -itemtype => ' t e x t ' , - t e x t => ${$adressen[$x]H'PLZ'}) ; $ t i x G r i d - > s e t ( 4 , $x, -itemtype => ' t e x t ' , - t e x t => $ { $ a d r e s s e n [ $ x ] } { ' O r t ' } ) ;

} # Hauptschleife MainLoopO ;

Wie Sie an unserem Beispiel sehen können, ist es recht einfach, tabellarische Daten mit Hilfe des TixGrid-Widgets darzustellen. Wir haben nur wenige Eigenschaften und Methoden benötigt, um das Adress-Buch auszugeben. Die Einbindung und das Erzeugen des Widgets haben wir, wie schon in den vorangegangenen Beispielen, mit Hilfe der Anweisungen use und $container = S c r o l l e d { 'Widget', . . . > bewerkstelligt. $tixGrid->sizeColumn(spalte, - s i z e => b r e i t e ) . Mit dieser Methode legen Sie die Breite einer Tabellenspalte in Pixeln fest. Der Spaltenindex beginnt dabei stets bei 0. $tixGrid->sizeRow(zeile, - s i z e => hoehe). Diese Methode ist analog zur soeben besprochenen, nur dass Sie hierüber die Höhe der Zeile festlegen können. $ t i x G r i d - > s e t ( s p a l t e , z e i l e , -itemtype => t y p , - a t t r i b u t => wert [ . . . ] ) . Um in ein Tabellenfeld einen Wert einzutragen, verwenden Sie die set()-Methode des TixGrid-Widgets. Hierzu müssen Sie zunächst die Spalte und Zeile der Zelle angeben, in der Sie einen Eintrag vornehmen möchten. Anschließend legen Sie den Typ des gewünschten Eintrages mit Hilfe der Eigenschaft -itemtype fest. Für diese Eigenschaft heben Sie die Optionen ' t e x t ' an, wenn Sie Text in die Zelle eintragen möchten, oder ' t e x t i m a g e ' , sofern Sie eine Graphik ausgeben wollen. Im Folgenden stellen wir noch einige weitere Methoden und Eigenschaften des TixGridSteuerelementes kurz vor: -browsecmd => ' c a l l b a c k ' . Die Callback-Routine dieser Methode wird immer dann aufgerufen, wenn eine Tabellenzelle aktiviert worden ist. Der Callback-Routine werden zwei Parameter, der Spalten- und der Zeilen-Index der aktivierten Zelle übergeben. -editdonecmd => ' c a l l b a c k ' . Nachdem eine Zelle editiert wurde, wird die CallbackRoutine dieser Methode aufgerufen. Ubergeben werden wiederum der Spalten- und der Zeilen-Index der geänderten Zelle.

240

9 Grafische Benutzerschnittstellen mit Perl/Tk

$tixGrid->deleteColumn(vonSpalte [, bisSpalte] ). Mit dieser Methode können Sie die Spalten ab der vonSpalte (einschließlich) bis zur bisSpalte löschen. Geben Sie den Parameter bisSpalte nicht an, wird nur die im Parameter vonSpalte angegebene Spalte gelöscht. $tixGrid->deleteRow(vonZeile [, bisZeile]). Diese Methode ist analog zur vorher besprochenen, bezieht sich allerdings auf Zeilen einer Tabelle. $tixGrid->entrycget (spalte, zeile, -eigenschaft). A m häufigsten wird diese Me-

thode dazu eingesetzt, den Text einer Zelle abzufragen. Um beispielsweise den Wert der zweiten Zelle in der dritten Zeile abzufragen, verwenden Sie die folgende Syntax. $tixGrid->->entrycget(2, 3, -text);

Sie können allerdings auch beliebige andere Eigenschaftswerte einer Zelle abfragen. $tixGrid->entryconfigure(spalte, zeile, eigenschaft => wert). Schließlich möch-

ten wir noch die Methode entryconf igure() des TixGrid-Widgets vorstellen, mit Hilfe derer Sie die Attribute einer Zelle ändern können. Um beispielsweise die Adresse des Eintrags von Daisy Duck zu verändern, geben Sie den folgenden Code ein. $tixGrid->entryconfigure(2, 3, -text => 'Falkenweg 4');

9.6

Geometrie-Management mit Perl/Tk

Bisher haben Sie gelernt, welche Standard-Widgets Perl/Tk bietet, und wie Sie einfache grafische Benutzerschnittstellen erzeugen können. Um mit diesen leistungsfähige Oberflächen erstellen zu können, müssen Sie auch in der Lage sein, die Steuerelemente auf dem Bildschirm exakt zu positionieren und auszurichten. Zu diesem Zweck besitzt Perl/Tk, wie auch die meisten anderen Oberflächen-Toolkits, sogenannte GeometrieManager.

In Perl/Tk stehen Ihnen drei dieser Geometrie-Manager zur Verfügung. Der wohl beliebteste, weil einfach anzuwendende Geometrie-Manager ist pack(), den wir bisher in unseren Beispielen ausschließlich angewendet haben. Mit dem Geometrie-Manager g r i d ( ) können Sie Ihre Widgets mit Hilfe einer (gedachten) Tabelle zeilen- und spaltenweise anordnen. Ein ganz ähnliches Verfahren haben wir bereits weiter oben im Beispiel-Programm SQL-Monitor im Kapitel Perl/CGI angewendet, um HTML-Steuerelemente in einem HTML-Dokument anzuordnen. In der Perl/TkProgrammierung wird dieser Manager recht selten angewendet, obwohl er durchaus Vorteile bietet. Wir werden ihn später in einem kurzen Beispiel vorstellen. Der dritte Geometrie-Manager, den Perl/Tk bietet, ist place (), mit dessen Hilfe es möglich ist, Widgets mit absoluten Koordinaten auf dem Bildschirm zu positionieren.

9.6 Geometrie-Management mit Perl/Tk

241

Wir werden uns hier nicht mit place () beschäftigen, da der Aufwand, den Sie betreiben müssten, um mit diesem Manager eine Oberfläche zu erstellen, immens ist. PlaceGeometrie-Manager finden vor allem in sogenannten RAD-Toolsls, wie Visual Basic von Microsoft Verwendung, in denen Benutzerschnittstellen mit der Maus gezeichnet werden. Da solche Tools für Perl/Tk derzeit nicht existieren, spielt der place-Manager folglich kaum eine Rolle. 19

9.6.1

Der Geometrie-Manager Pack

Kommen wir also nun zu pack(). In den vorangegangenen Kapiteln haben wir pack() zumeist ohne jegliche Argumente verwendet. Möglicherweise haben Sie bereits vermutet, dass dies nicht alles ist, wozu packQ imstande ist. Lassen Sie uns also in den folgenden Zeilen einen Blick auf die Möglichkeiten werfen, die uns pack() bietet. W i d g e t - P o s i t i o n i e r u n g m i t -side. pack() g i b t u n s ü b e r d a s A r g u m e n t -side die

Möglichkeit, Widgets an einer Seite des Hauptfensters oder des aktiven Containers 20 auszurichten. Dabei können die folgenden Werte für -side verwendet werden: 'left', 'right', 'top' u n d 'bottom'

Dabei ist 'top'die Standardausrichtung, das heißt, wenn Sie alle Widgets übereinander anordnen möchten, ist es vollkommen gleichwertig, ob Sie

$label = $fenster->Label( -text => 'Geben Sie etwas ein:') ->pack(-side => 'top'); $entry = $fenster->Entry() ->pack(-side => 'top');

schreiben, oder einfach.

$label = $fenster->Label( -text => 'Geben Sie etwas ein:') ->pack(); $entry = $fenster->Entry() ->pack(); 1&

RAD = engl. Rapid Application-Development Mit Hilfe von Glade ist es zwar möglich, in einem zu VisualBasic analogen Verfahren, Benutzerschnittstellen zu erstellen und diese von Perl aus anzusprechen. Glade basiert allerdings auf dem Gnome Toolkit GTK, welches an dieser Stelle nicht behandelt wird. Einen intensiven Blick ist dieses Toolkit durchaus wert! Die Perl Unterstützung für die aktuelle Version 2 des Gnome-Toolkits ist jedoch eher als schwach zu bezeichnen! 20 Was unter diesem Begriff zu verstehen ist, erklären wir Ihnen im nächsten Abschnitt. 19

242

9 Grafische Benutzerschnittstellen mit Perl/Tk

Wenn Sie allerdings im obigen Beispiel-side => 'left'anstelle von-side => 'top' verwenden, werden die Widgets jeweils nach links vom vorhergehenden angeordnet. Nun mag dies vielleicht ganz spannend sein, aber es sieht sicherlich nicht besonders toll aus, wenn in einer größeren Anwendung alle Widgets entweder über- oder nebeneinander ausgerichtet sind. Wie schaffen wir es dann, dass in einer Anwendung die Widgets sowohl neben- als auch untereinander positioniert werden können? Ganz einfach, mit einem kleinen Trick und einem weiteren Widget-Typ, den Frames.

9.6.2

Widget-Positionierung mit Hilfe von Frames

Frames sind, sofern nicht die Eigenschaft -relief gesetzt wurde, unsichtbare ContainerWidgets, sozusagen Widgets, die ihrerseits weitere Widgets aufnehmen können. Mit Frames können die oben beschriebenen Nachteile von pack() vollkommen ausgeglichen werden. Lassen Sie uns zunächst eine etwas komplexere Benutzerschnittstelle aufbauen, mit der wir Ihnen die Verwendung von pack() zusammen mit Frames demonstrieren. Tippen Sie also das folgende Skript ab und führen Sie es aus. #!/usr/bin/perl -w # pack.pl use Tk; # ein Hauptfenster erzeugen $fenster =MainWindow->new(); $fenster->title('Ein tolles Beispiel'); # Frame fuer Adressfeld erzeugen my $fraAdresse = $fenster->Frame() ->pack(-side => 'left'); # Frame fuer Schaltflaechen erzeugen my $fraSchaltf1 = $fenster->Frame() ->pack(-side => 'left'); # Frame und Widgets fuer Namensangaben im Adressrahmen erzeugen my $fraName = $fraAdresse->Frame() ->pack(-side => 'top'); my $lblName = $fraName->Label( -width => 25, -text => 'Name / Vorname') ->pack(-side => 'left'); my $txtName = $fraName->Entry(

9.6 Geometrie-Management mit Perl/Tk -width => 25) ->pack(-side => 'left'); my $txtVorname = $fraName->Entry( -width => 25) ->pack(-side => 'left'); # Frame und Widgets fuer Strasse im Adressrahmen erzeugen my $fraStrasse = $fraAdresse->Frame() ->pack(-side => 'top'); my $lblStrasse = $fraStrasse->Label( -width => 25, -text => 'Strasse') ->pack(-side => 'left'); my $txtStrasse = $fraStrasse->Entry( -width => 25) ->pack(-side => 'left'); # Frame und Widgets fuer Plz und Ort im Adressrahmen erzeugen my $fraOrt = $fraAdresse->Frame() ->pack(-side => 'top'); my $lblOrt = $fraOrt->Label( -width => 25, -text => 'PLZ / Ort') ->pack(-side => 'left'); my $txtPlz = $fraOrt->Entry( -width => 5) ->pack(-side => 'left'); my $txtOrt = $fraOrt->Entry( -width => 25) ->pack(-side => 'left'); # Widgets zum Bearbeiten im Schaltflaechenrahmen erzeugen # Achtung: Zur besseren Uebersicht sind keine Callbacks hinterlegt my $cmdNeu = $fraSchaltfl->Button( -text => 'Neu', -underline => 0, -width => 25) ->pack(-side => 'top'); my $cmdLoeschen = $fraSchaltfl->Button( -text => 'Loeschen',

243

244

9 Grafische Benutzerschnittstellen mit Perl/Tk -underline => 0, -width => 25) ->pack(-side => 'bottom');

# Ereignisschleife starten MainLoopO ;

Wenn Sie die Anwendung ausführen, erhalten Sie die in Abbildung 9.16 dargestellte Ausgabe. Q

E i n tolles Beispiel

:

Name ! Vorname Strasse

Loeschert

PLZ / Ort

Abb.

9.16:

Die noch etwas ungeordnete

O E D

Neu

-3 Benutzerschnittstelle

Im soeben erstellten Beispiel sehen Sie, wie es möglich ist, beliebig komplexe Benutzerschnittstellen mit Hilfe von hierarchisch ineinander geschachtelten Frames zu erstellen. Allerdings haben Sie wohl auch zwei weitere Erkenntnisse gewonnen: 1. Die Benutzerschnittstelle bedarf noch einer gewissen Überarbeitung. 2. Es ist dringend notwendig, sich von komplexen Benutzerschnittstellen vorher eine Zeichnung anzufertigen.

9.6.3

Benutzerschnittstellen optimieren

Wenn Sie die Anwendung "Ein tolles Beispiel" aufmerksam betrachten, bemerken Sie sicher zwei Punkte, die noch optimiert werden müssen. Zum einen sind die Steuerelemente im Adressrahmen nicht sauber ausgerichtet, so dass die gesamte Oberfläche ziemlich chaotisch erscheint. Zum anderen kleben die Widgets förmlich aufeinander. W i d g e t s optisch voneinander trennen. Beginnen wir mit dem zweiten Punkt. Lassen Sie uns die Widgets optisch voneinander trennen. Dies geschieht am besten dadurch, dass wir mit Hilfe der Option -padx links und rechts vom jeweiligen Widget einen zusätzlichen Freiraum schaffen und analog dazu mit -pady einen zusätzlichen Freiraum ober- und unterhalb der Widgets. -padx und -pady sind Argumente der Methode -pack und werden dort angegeben. Die allgemeine Syntax lautet.

-padx => Bildschirmeinheiten -pady => Bildschirmeinheiten

9.6 Geometrie-Management mit Perl/Tk

245

Fügen Sie also bei allen Label-, Entry- und Button-Widgets links und rechts einen Freiraum von 2 und ober- sowie unterhalb einen Freiraum von 3 Einheiten ein. Dies geschieht wie im Folgenden, exemplarisch an der Neu-Schaltfläche dargestellt 21 .

#

...

my $cmdNeu = $fraSchaltfl->Button( -text => 'Neu', -underline => 0, - w i d t h => 25) ->pack(-side => 'top', -padx => 2, -pady => 3); # . ..

Wahrscheinlich haben Sie bei der Änderung des Skripts reichlich Gebrauch von des "Programmierers besten Freunden" Copy & Paste (Kopieren und Einfügen) gemacht! Nein? Nun, dann wäre dies ein guter Zeitpunkt, sich dieser Vorgehensweise zu bedienen, wenn Sie Ihre geistige Gesundheit bewahren möchten. Widget-Positionierung optimieren. Das zweite oben erwähnte Problem lässt sich nicht ganz so einfach lösen wie das gerade beschriebene. Sie müssen hierzu wissen, dass in Perl/Tk jedes Widget nur genau den Raum in der Benutzerschnittstelle beansprucht, den es benötigt, sich selbst anzuzeigen. Dies gilt auch für die Frame-Widgets. Jedes dieser Widgets ist genau in der Größe der enthaltenen Steuerelemente dargestellt. Der Frame mit den Namens-Angaben $fraName ist folglich der größte Frame im Adressrahmen und der Frame $ f r a S t r a s s e der kleinste. Demzufolge müssen wir erreichen, dass alle Frames den gesamten, Ihnen zur Verfügung stehenden, Raum ausnutzen, die Frames $ f r a S t r a s s e und $ f r a 0 r t also gedehnt werden. Dies geschieht, indem wir im p a c k ( ) Geometriemanager das Argument -expand auf den Wert 1 und das Argument - f i l l auf den Wert ' b o t h ' setzen. Unten sehen Sie, wie dies für den Straßen-Rahmen geschieht.

# .. . my $fraStrasse = $fraAdresse->Frame() ->pack(-side => 'top', - e x p a n d => 1, -fill => 'both'); #

... 21

Am Ende dieses Abschnitts drucken wir das komplette und geänderte Skript ab.

246

9 Grafische Benutzerschnittstellen mit Perl/Tk

Das Ergebnis ist nun fast perfekt, die Entry-Widgets sind auf der linken Seite untereinander ausgerichtet, die Button-Widgets sind ebenfalls dort, wo wir sie sehen möchten, nur die Label-Felder sind noch nicht ganz perfekt. Dies liegt daran, dass der angezeigte Text in Label-Widgets standardmäßig zentriert ausgerichtet wird. Es sollte für Sie eigentlich kein Problem sein, dies zu ändern! Fügen Sie einfach zu allen Label-Widgets das Argument -anchor => 'w' hinzu. Das Ergebnis sehen Sie im folgenden, komplett abgedruckten Skript.

#!/usr/bin/perl -w # pack2.pl use Tk; # ein Hauptfenster erzeugen $fenster =MainWindow->new(); $fenster->title('Ein tolles Beispiel'); # Frame fuer Adressfeld erzeugen my $fraAdresse = $fenster->Frame() ->pack(-side => 'left', -expand => 1, -fill => 'both'); # Frame fuer Schaltflaechen erzeugen my $fraSchaltf1 = $fenster->Frame() ->pack(-side => 'left', -expand => 1, -fill => 'both'); # Frame und Widgets fuer Namensangaben im Adressrahmen erzeugen my $fraName = $fraAdresse->Frame() ->pack(-side => 'top', -expand => 1, -fill => 'both'); my $lblName = $fraName->Label( -width => 25, -text => 'Name / Vorname', -anchor => 'w') ->pack(-side => 'left', -padx => 2, -pady => 3); my $txtName = $fraName->Entry( -width => 25) ->pack(-side => 'left',

9.6 Geometrie-Management mit Perl/Tk -padx => 2, -pady => 3); my $txtVorname = $fraName->Entry( -width => 25) ->pack(-side => 'left', -padx => 2, -pady => 3); # Frame und Widgets fuer Strasse im Adressrahmen erzeugen my $fraStrasse = $fraAdresse->Frame() ->pack(-side => 'top', -expand => 1, -fill => 'both'); my $lblStrasse = $fraStrasse->Label( -width => 25, -text => 'Strasse', -anchor => 'w') ->pack(-side => 'left', -padx => 2, -pady => 3); my $txtStrasse = $fraStrasse->Entry( -width => 25) ->pack(-side => 'left', -padx => 2, -pady => 3); # Frame und Widgets fuer Plz und Ort im Adressrahmen erzeugen my $fraOrt = $fraAdresse->Frame() ->pack(-side => 'top', -expand => 1, -fill => 'both'); my $lblOrt = $fraOrt->Label( -width => 25, -text => 'PLZ / Ort', -anchor => 'w') ->pack(-side => 'left', -padx => 2, -pady => 3); my $txtPlz = $fraOrt->Entry( -width => 5) ->pack(-side => 'left', -padx => 2,

247

248

9 Grafische Benutzerschnittstellen mit Perl/Tk - p a d y => 3);

my $txtOrt = $fraOrt->Entry( - w i d t h => 25) ->pack(-side => 'left', -padx => 2, -pady => 3); # Widgets zum B e a r b e i t e n im Schaltflaechenrahmen erzeugen # Achtung: Zur b e s s e r e n Uebersicht sind keine Callbacks hinterlegt my $cmdNeu = $fraSchaltfl->Button( -text => 'Neu', -underline => 0, - w i d t h => 25) ->pack(-side => 'top', -padx => 2, - p a d y => 3); my $cmdLoeschen = $fraSchaltfl->Button( -text => 'Loeschen', -underline => 0, - w i d t h => 25) ->pack(-side => 'bottom', -padx => 2, - p a d y => 3); # Ereignisschleife starten MainLoopO ;

Name I Vorname

Neu

Strasse Loeschen

PLZ / Ort

Abb.

9.17:

Die fast perfekte Benutzer Schnittstelle

Wenn Sie dieses Skript ausführen, sollten Sie eine nahezu perfekte grafische Benutzerschnittstelle vor sich sehen. Allerdings, so mag der ein oder andere Leser einwenden, ist der Aufbau einer solchen Oberfläche mit einer ganzen Menge Tipp-Arbeit verbunden. Hier also noch ein paar Anregungen, die sich bei der Arbeit des Autors bewährt haben: • Machen Sie sich zunächst eine Zeichnung, um sich über die Gestaltung der Oberfläche klar zu werden. Zeichnen Sie dabei alle benötigten Frames ein.

9.6 Geometrie-Management mit Perl/Tk

249

• Verwenden Sie, wo immer möglich, die Funktionen Copy & Paste. Die meisten Steuerelemente sind stets gleich aufgebaut, so dass Sie jeweils nur einige Parameter ändern müssen. • Fügen Sie reichlich Kommentare ein, damit Sie sich auch später in Ihrem Skript zurechtfinden. Wenn Sie mit Perl/Tk aufwändigere Benutzerschnittstellen aufbauen, können Sie schnell auf einige tausend Code-Zeilen kommen! Mit keinem anderen Geometrie-Manager können Sie derart leistungsfähige und vor allem plattformunabhängige Benutzerschnittstellen in so kurzer Zeit realisieren wie mit pack (). Der Autor hat übrigens im Kundenauftrag mehrfach komplexe Benutzerschnittstellen mit pack() entwickelt, die zum Teil aus etlichen Formularen und weit mehr als 100 Widgets bestanden.

9.6.4

Ein Beispiel mit dem Grid-Geometrie-Manager

Wie bereits oben versprochen, möchten wir Ihnen nun noch ein Beispiel für die Anwendung des Geometrie-Managers Grid geben. Bedenken Sie aber, bevor Sie größere Projekte mit Hilfe dieses Geometrie-Managers in Angriff nehmen, dass es nur auf den ersten Blick so scheint, als sei die Anwendung einfacher. Der Autor musste schmerzlich feststellen, dass man in einem Projekt sehr schnell in Fallen tappt. Problematisch wird es beispielsweise dann, wenn sehr schmale und sehr breite Widgets untereinander und nebeneinander so gruppiert werden sollen, dass keine großen Lücken entstehen 22 . Doch nun zu unserem Beispiel. Das unten abgedruckte Skript erstellt die gleiche Benutzerschnittstelle wie das p a c k . p l . #!/usr/bin/perl -w # grid.pl use Tk; # ein Hauptfenster erzeugen $fenster =MainWindow->new(); $fenster->title('Ein tolles Beispiel'); # Die oberste Widget-Reihe erzeugen $fenster->Label(-width => 25, -text => 'Name / Vorname', -anchor => 'w') ->grid($fenster->Entry(-width => 25), $fenster->Entry(-width => 25), $fenster->Button(-text => 'Neu'), 22 Auch diese Probleme lassen sich mit Hilfe der Optionen -columnspan => 'Spalten* sowie -rovspan => 'Zeilen' lösen, Sie benötigen in diesem Fall eine sehr genaue Zeichnung, in der alle Spalten und Zeilen eingetragen sind.

250

9 Grafische Benutzerschnittstellen mit Perl/Tk - s t i c k y => ' e w ' ) ;

# Die zweite Widget-Reihe erzeugen $fenster->Label(-width => 25, - t e x t => ' S t r a s s e ' , -anchor => 'w') ->grid($fenster->Entry(-width => 2 5 ) ) ; # Die d r i t t e Widget-Reihe erzeugen $fenster->Label(-width => 25, - t e x t => 'PLZ / O r t ' , -anchor => 'w') ->grid($fenster->Entry(-width => 5 ) , $fenster->Entry(-width => 2 5 ) , $ f e n s t e r - > B u t t o n ( - t e x t => 'Loeschen', -underline => 0 , -width => 2 5 ) , - s t i c k y => ' w ' ) ; # Ereignisschleife starten MainLoopQ ;

Die Anwendung g r i d . p l erzeugt die Ausgabe, die Sie in Abbildung 9.18 sehen. Q

Ein t o l l e s B e i s p i e l

Name / Vorname

!



1

L,oeschen

Strasse • PLZ / Ort

Abb. 9.18:

_

Die mit Hilfe des Geometrie-Managers

gridO

erstellte

Benutzerschnittstelle

Wenn Sie das Skript grid.pl betrachten, fällt Ihnen sicherlich auf, dass es erheblich kürzer ist als die vorher behandelte Anwendung pack.pl. Daher ist es sicherlich sinnvoll, den Geometrie-Manager bei solchen einfachen Programmen einzusetzen. Beachten Sie aber, dass g r i d O in der gezeigten Form mit anonymen Widgets arbeitet, das heißt, ein späterer Zugriff auf ein Widget, beispielsweise mit -configure, ist nicht möglich. Dies Problem wird in der folgenden Version grid2.pl des Programmes beseitigt, dafür verliert die Anwendung allerdings etwas an Übersichtlichkeit. # ! / u s r / b i n / p e r l -w # grid2.pl use Tk;

f

9.6 Geometrie-Management mit Perl/Tk

251

# ein Hauptfenster erzeugen $fenster =MainWindow->new(); $fenster->title('Ein tolles Beispiel'); # Die oberste Widget-Reihe erzeugen my $lblName = $fenster->Label(-width => 25, -text => 'Name / Vorname', -anchor => 'w') ->grid(my $txtName = $fenster->Entry(-width => 25), my $txtVorname = $fenster->Entry(-width => 25), my $cmdNeu = $fenster->Button(-text => 'Neu'), -sticky => 'ew'); # Die zweite Widget-Reihe erzeugen my $lblStrasse = $fenster->Label(-width => 25, -text => 'Strasse', -anchor => 'w') ->grid(my $txtStrasse = $fenster->Entry(-width => 25)); # Die dritte Widget-Reihe erzeugen my $lblOrt = $fenster->Label(-width => 25, -text => 'PLZ / Ort', -anchor => 'w') ->grid(my $txtPlz = $fenster->Entry(-width => 5), my $txtOrt = $fenster->Entry(-width => 25), my $cmdLoeschen = $fenster->Button(-text => 'Loeschen', -underline => 0, -width => 25), -sticky => 'w'); # Ereignisschleife starten MainLoopQ ;

Ein anderes Problem lässt sich allerdings nicht so einfach beseitigen, und dies ist, so meinen wir, erheblich schwerwiegender, wenn auch im vorliegenden Beispiel recht unscheinbar. Wie Sie erkennen können, richtet sich die Breite einer Spalte nach dem breitesten Widget dieser Spalte. Nun ist aber das Entry-Widget zur Eingabe der Postleitzahl erheblich schmäler als das Widget zur Eingabe des Namens. Das Resultat ist ein ziemlich breiter ungenutzter Raum links vom PLZ-Widget. Dies führt zu einem recht zerissenen Erscheinungsbild der Benutzeroberfläche. Zur Lösung dieses Problemes bietet sich die Verwendung des Attributs columnspan => ' Spalten' an. In unserem Beispiel verwenden wir allerdings stattdessen den "Abkürzungs-Operator" " - " , den wir jeweils dem Widget nachstellen. Wir teilen in unserem Beispiel die Widgets $txtName und $txtStrasse auf zwei Spalten auf, die wir miteinander verbinden. Die Felder $txtPlz sowie $txt0rt erhalten so jeweils eine eigene Spalte. Damit allerdings die Schaltfläche

252

9 Grafische Benutzerschnittstellen mit Perl/Tk

$cmdLoeschen wieder auf der ganz rechten Spalte erscheint, müssen wir vorher mit dem Operator "x" angeben, dass eine Spalte ausgelassen wird. Das Skript hat nun folgende Form.

#!/usr/bin/perl -w # grid3.pl use Tk; # ein Hauptfenster erzeugen $fenster =MainWindow->new(); $fenster->title('Ein tolles Beispiel'); # Die oberste Widget-Reihe erzeugen my $lblName = $fenster->Label(-width => 25, -text => 'Name / Vorname', -anchor => 'w') ->grid(my $txtName = $fenster->Entry(-width => 25), ii• iii my $txtVorname = $fenster->Entry(-width => 25), my $cmdNeu = $fenster->Button(-text => 'Neu'), -sticky => 'ew'); # Die zweite Widget-Reihe erzeugen my $lblStrasse = $fenster->Label(-width => 25, -text => 'Strasse', -anchor => 'w') ->grid(my $txtStrasse = $fenster->Entry(-width => 25), n_n >

-sticky => 'ew'); # Die dritte Widget-Reihe erzeugen my $lblOrt = $fenster->Label(-width => 25, -text => 'PLZ / Ort', -anchor => 'w') ->grid(my $txtPlz = $fenster->Entry(-width => 5), my $txtOrt = $fenster->Entry(-width => 25), llAvl!» my $cmdLoeschen = $fenster->Button(-text => 'Loeschen', -underline => 0, -width => 25), -sticky => 'w'); # Ereignisschleife starten MainLoopO;

9.7 Menüs mit Perl/Tk erstellen

253

Ein tolles. BgjsgigL

- O X'!

Name l Vorname Strasse Loeschen

PLZ / Ort

Abb. 9.19: schönt"

Die mit Hilfe des Geometrie-Managers

grid()

erstellte

Benutzerschnittstelle

"ver-

Das fertige grid()-Beispiel zeigen wir in Abbildung 9.19. Zum Abschluss dieses Kapitels noch ein kleiner Tipp: Bei komplizierten Oberflächen hilft es manchmal, wenn die Widgets innerhalb eines Frames von rechts her gepackt oder nur einzelne Widgets von rechts gepackt werden ( - s i d e => r i g h t ) . Damit ist es möglich, Widgets auch rechtsbündig untereinander auszurichten. Mit diesen Worten möchten wir das Kapitel zum Geometrie-Management abschließen. Nachdem Sie nun in der Lage sind, komplexe Formulare zu erstellen, benötigen Sie jetzt noch die Möglichkeit, dem Anwender die Orientierung zu erleichtern. Daher stellen wir Ihnen im nächsten Abschnitt die Arbeit mit Menüs in Perl/Tk vor.

9.7

Menüs mit Perl/Tk erstellen

Sie haben nun einen tiefen Einblick in die Programmierung mit Perl/Tk erhalten und sind jetzt in der Lage, auch komplexe Benutzerschnittstellen zu realisieren. Wenn Ihre Anwendung mit dem Benutzer kommunizieren soll, ist es sinnvoll, dies mit Hilfe von Menüs zu tun, die am oberen Bildschirmrand angeordnet sind. Perl/Tk stellt Ihnen alle Mittel zur Verfügung, die Sie benötigen, um solche Menüstrukturen zu erstellen. Beginnen Sie am besten damit, ein Frame-Steuerelement als Container zu erzeugen. Anschließend werden ein oder mehrere Widgets vom Typ Menubutton in diesem Frame erstellt. Diese Widgets sind für die Anzeige der einzelnen Menü-Punkte verantwortlich. Wenn Sie später einen dieser Buttons mit der Maus anklicken, klappt eine Liste mit den jeweiligen Menüeinträgen nach unten aus 2 3 . Schließlich ordnen Sie den Menüpunkten die entsprechenden Einträge mit den notwendigen Callbackfunktionen Menüeinträge können in Perl/Tk einfache B u t t o n s sein, die eine Aktion auslösen, also beispielsweise einen Dialog anzeigen. Menüeinträge können aber auch in Form von Check- oder Radio-Buttons eingefügt werden, die mit einer referenzierten Variablen interagieren. Um den Aufbau einer Menüleiste zu demonstrieren, tippen Sie bitte das folgende Skript ab, das eine Menüleiste erzeugt, wie sie beispielsweise in einem Edi23 Daher werden diese Art von Menüs auch mit dem englischen Namen Pop-Down-Menüs Pop-Down bedeutet nach unten aufklappen.

genannt.

254

9 Grafische Benutzerschnittstellen mit Perl/Tk

tor verwendet werden könnte. Beachten Sie bitte, dass wir der Übersichtlichkeit halber weitgehend auf Callbackfunktionen verzichtet haben.

#!/usr/bin/perl -w # menu.pl use Tk; # ein Hauptfenster erzeugen my $fenster = MainWindow->new(); $fenster->title('Das koennte ein Editor werden'); # Frame-Widget zur Aufnahme des Menues erzeugen my $fraMenuBar = $fenster->Frame() ->pack(-side => 'top', -expand => 1, -fill => 'both'); # Menue-Punkt 'Datei' erzeugen my $mnuDatei = $fraMenuBar->Menubutton(-text => 'Datei', -underline => 0) ->pack(-side => 'left'); # Menue-Eintrag 'Beenden' im Menue-Punkt 'Datei' erzeugen $mnuDatei->command(-label => 'beenden', -underline => 0, -accelerator => 'Strg + x', -command => sub { exit }

); # Menue-Punkt 'Bearbeiten' erzeugen my $mnuBearbeiten = $fraMenuBar->Menubutton(-text => -underline => 0) ->pack(-side => 'left');

'Bearbeiten',

# Menue-Eintraege im Menue-Punkt 'Bearbeiten' erzeugen $mnuBearbeiten->command(-label => 'kopieren', -underline => 0); $mnuBearbeiten->command(-label => -underline => 0);

'ausschneiden',

$mnuBearbeiten->command(-label => -underline => 0);

'einfuegen',

# Menue-Punkt 'Schrift' erzeugen

9.7 Menüs mit Perl/Tk erstellen my $mnuSchrift = $fraMenuBar->Meimbutton(-text => 'Schrift', -underline => 0) ->pack(-side => 'left'); # Menue-Eintraege im Menue-Punkt 'Schrift' erzeugen $mnuSchrift->radiobutton(-label => 'sehr gross', -value => 'sehrgross', -variable => \$schriftgr); $mnuSchrift->radiobutton(-label => 'gross', -value => 'gross', -variable => \$schriftgr); $mnuSchrift->radiobutton(-label => 'klein', -value => 'klein', -variable => \$schriftgr); $mnuSchrift->radiobutton(-label => 'sehr klein', -value => 'sehrklein', -variable => \$schriftgr); # Einen Trennstrich erzeugen $mnuSchrift->separator(); $mnuSchrift->checkbutton(-label => 'normal', -variable => \$normal); $mnuS ehr if t-> che ckbutt on(-labe1 => 'fett', -variable => \$fett); $mnuSchrift->checkbutton(-label => 'kursiv', -variable => \$kursiv); # Menue-Punkt Hilfe erzeugen und am rechten Fensterrand ausrichten my $mnuHilfe = $fraMenuBar->Menubutton(-text => 'Hilfe', -underline => 0) ->pack(-side => 'right'); # Menue-Eintrag 'Hilfe anzeigen' in Menue 'Hilfe' erzeugen $mnuHilfe->command(-label => 'Hilfe anzeigen', -underline => 0, -accelerator => 'Alt + h'); # Ereignisschleife starten MainLoopO ;

255

256

Abb.

9 Grafische Benutzerschnittstellen mit Perl/Tk

9.20:

Ein Beispiel für eine mit Perl/Tk

erstellte

Menüstruktur

Wie Sie sehen, ist es gar nicht so schwierig, mit Perl/Tk Menüstrukturen zu erstellen. Zu beachten ist allerdings, dass die Eigenschaft - a c c e l e r a t o r lediglich die Anzeige steuert, also keine Aktion bewirkt. Die müssten Sie selbst über -bind dem ToplevelFenster zuordnen. Damit möchten wir die Besprechung der Menü-Erzeugung mit Perl/Tk beenden. Im nächsten Kapitel, welches die Darstellung der Möglichkeiten von Perl/Tk abschließt, stellen wir Ihnen eine Beispielanwendung für das wohl mächtigste Perl/Tk-Widget, das Text-Widget, vor.

9.8

Ein Editor mit Perl/Tk

Das leistungsfähigste Steuerelement von Perl/Tk ist wohl das Text-Widget. Alle Eigenschaften und Methoden dieses Widgets aufzuführen, würde ein ganzes Buch füllen. Wir haben uns daher entschieden, Ihnen dieses Widget anhand eines umfangreichen Beispiels vorstellen. Dieses Beispiel soll zugleich zum Abschluss dieses Kapitels zeigen, wie Sie eine umfangreiche Anwendung mit Perl/Tk erstellen. Zudem demonstrieren wir, wie es möglich ist, mit mehreren Fenstern zu arbeiten. Ohne diese Technik sind sie kaum in der Lage, umfangreiche Programme zu erstellen. Besonders möchten wir Sie auch darauf hinweisen, dass wir darstellen, wie es mit Hilfe des bind-Mechanismus möglich ist, Callback-Routinen zur Laufzeit der Anwendung festzulegen. Eine Technik, die Ihren Anwendungen eine unglaubliche Flexibilität erlaubt. Hier also der Code unserer Anwendung e d i t . p l .

# ! / u s r / b i n / p e r l -w # edit.pl use Tk;

9.8 Ein Editor mit Perl/Tk

# lokale Variablen deklarieren my($Meldung) = $DatName = " ; # Hilfsvariable fuer Suchfunktion $StartPos = '1.0'; # Editor-Fenster erstellen $tlEdit = MainWindow->new(); $tlEdit -> title('Kundengeschichte'); $tlframel = $tlEdit -> Frame(-relief => 'groove', -borderwidth => 1) -> pack(-fill => 'both', -expand => ' 1'); # Menue erzeugen # Menue-Punkt 'Datei' erzeugen my $mnuDatei = $tlframel->Menubutton(-text => 'Datei', -underline => 0) ->pack(-side => 'left'); # Menue-Eintrag 'laden' erzeugen $mnuDatei->command(-label => 'Datei laden', -underline => 6, -command => sub { ladenSpeichern(1);

> ); # Menue-Eintrag 'speichern' erzeugen $mnuDatei->command(-label => 'Datei speichern', -underline => 6, -command => sub { ladenSpeichern(0);

> ); # Trenner erzeugen $mnuDatei->separator(); # Menue-Eintrag 'Beenden' $mnuDatei->coimnand(-label => 'Programm beenden', -underline => 9, -command => sub { exit }

); # Frame fuer Suchfunktion $tlframe2 = $tlEdit -> Frame() -> pack(-fill => 'both', -expand => '1');

257

258

9 Grafische Benutzerschnittstellen mit Perl/Tk

# Label $lblSuchen = $tlframe2 -> Label(-text => -relief => 'ridge', -width => 12, -height => 1);

'Suchbegriff',

# Textfeld fuer Suchfunktion $txtSuchen = $tlframe2 -> Entry(-bg =>'LightYellow', -fg =>'MidnightBlue', -width => 40); # Schaltflaeche zum Starten der Suche $cmdSearch = $tlframe2 -> Button(-text => 'Neu Suchen', -width => 12, -height =>1, -command => sub { # Suchen ab Pos 1.0 $StartPos = '1.0'; # Aufruf Suchfunktion BegriffSuchenO ;

> ); # Schaltflaeche zum Suchen ab aktueller Position $cmdSearch2 = $tlframe2 -> Button(-text => 'Weiter Suchen', -width => 12, -height => 1, -command => sub -[ # Aufruf Suchfunktion BegriffSuchenO

> ); # Widgets packen $lblSuchen -> pack(side => -padx=>6, -pady=>2); $txtSuchen -> pack(side => -padx=>6, -pady=>2); IcmdSearch -> pack(side => -padx=>6, -pady=>2); $cmdSearch2 -> pack(side => -padx=>6, pady=>2);

'left',

'left',

'left',

'right',

# Frame fuer Textfeld erstellen $tlframe3 = $tlEdit -> Frame() -> pack(-fill => 'both', -expand => '!');

9.8 Ein Editor mit Perl/Tk

# Scrollbar erstellen $scrEdit = $tlframe3 ->Scrollbar(-orient => 'vertical'); # Textfeld erstellen $txtEdit = $tlframe3 -> Text( # Vorder- und Hintergrund farbig gestalten -bg =>'LightYellow', -fg =>'MidnightBlue', -height => 34, # Anbindung ein Scrollbar -yscrollcommand =>['set' , $scrEdit], # Wordwrap-Funktion aktivieren -wrap => 'word',

); # Scrollbar ein Textfeld binden $scrEdit->configure(-command => ['yview', $txtEdit]); # Textfeld in Oberflaeche packen $txtEdit -> pack(side => 'left', -padx=>6, -pady=>2, -expand => '1', -fill => 'both'); # Scrollbar packen $scrEdit -> pack(-side => 'right', -fill => 'y>, -padx=>6, -pady=>2); # Frame fuer Meldungen erstellen $lblMeldung = $tlEdit -> Label(-relief => 'ridge', -text => $Meldung); $lblMeldung -> pack(side => 'left', -fill => 'both', -expand => '1', -padx=>6, -pady=>2); MainLoopO ; # Unterroutinen sub ladenSpeichern { # Argument uebernehmen

259

260

9 Grafische Benutzerschnittstellen mit Perl/Tk my $laden = $_ [0]; # 1 wenn laden # Dberflaeche aufbauen my $tlLadenSpeichern = $tlEdit -> T o p l e v e l O ; # Label und Textfeld erzeugen $framel = $tlLadenSpeichern -> Frame ->pack(-fill => 'x', -expand => 'y') ;

$lblDatName = $framel -> Label( -text => 'Dateiname', -relief => 'ridge', -width => 12, -height => 1); # Label und Textfeld erzeugen $txtDatName = $framel -> Entry( -bg =>'LightYellow', -fg =>'MidnightBlue', -width => 50); # Steuerelemente packen $lblDatName -> pack(side => 'left', -padx=>6, -pady=>2); $txtDatName -> pack(side => 'right', -padx=>6, -pady=>2); # Frame fuer Schaltflaechen erzeugen $frame2 = $tlLadenSpeichern -> Frame ->pack(-fill => 'x', -expand => 'y'); # Schaltflaechen erzeugen $cmdAbbrechen = $frame2->Button(-text => 'Abbrechen', -width => 12, -command => sub { $tlLadenSpeichern->destroy();

> )-> pack(side => 'left', -padx=>6, -pady=>2); $ cmdSpe i chern = $frame2->Button(-width => 12) -> pack(side => 'right', -padx=>6, -pady=>2);

9.8 Ein Editor mit Perl/Tk

if ($laden == 1) { # Laden $tlLadenSpeichern -> title('Datei laden'); # Caption der Schaltflaeche Speichern belegen $cmdSpeichern->configure(-text => 'Datei laden'); # Callback-Routine fuer Schaltflaeche Speichern $cmdSpeichern-> bind("", sub{ my $text = " ; # Dateinamen aus Entry lesen $DatName = $txtDatName->get(); # Datei oeffnen unless(open(HIN, $DatName)) { # nicht vorhanden: Neue Datei anlegen $lblMeldung->configure( -text => "$DatName nicht vorhanden!"); } eise { $lblMeldung->configure( -text => "$DatName wurde geladen"); # zur ersten Zeile gehen seek( HIN, 0, 0); # Zeilenweise einlesen und anfuegen while() { $text .= $_;

} # letzten Zeilenumbruch entfernen chomp($text); # Textfeld loeschen $txtEdit->delete('1.0', 'end'); # Text in Textfeld schreiben $txtEdit->insert('end', $text);

> close(HIN); # Eingabefenster schliessen $tlLadenSpeichern->destroy();

»; # Textfeld loeschen $txtDatName->delete(0, 'end'); } else { # Speichern $tlLadenSpeichern -> title('Datei speichern'); # Caption der Schaltflaeche Speichern belegen $cmdSpeichern->configure(-text => 'Datei speichern'); # Textfeld loeschen $txtDatName->delete(0, 'end'); # Dateinamen vorgeben

261

9 Grafische Benutzerschnittstellen mit Perl/Tk

262

$txtDatName->insert(0, $DatName); # Callback-Routine fuer Schaltflaeche Speichern $cmdSpeichern-> bind("", sub{ my $text = " ; # Text aus Editor lesen $text = $txtEdit->get('1.0', 'end'); # letzten Zeilenumbruch enfernen chomp($text); # Dateinamen aus Entry lesen $DatName = $txtDatName->get(); # wenn Dateiname angegeben speichern unless ($DatName eq '') { # Datei schreiben open (HOUT, ">$DatName"); print HOUT $text; close (HOUT); $lblMeldung->configure(-text => "$DatName gespeichert"); } eise { $lblMeldung->configure(-text => "$DatName konnte nicht gespeichert werden!");

> # Eingabefenster schliessen $tlLadenSpeichern->destroy() ;

}

»;

} sub BegriffSuchen { # Markierung aufheben $txtEdit->tagRemove( 11 markiert",'1.0', 'end'); # tag definieren $txtEdit->tagConfigure("markiert", -background => 'MidnightBlue', -foreground => 'LightYellow'); # Wert aus txtSuchen lesen und speichern my($Suchbegriff) = $txtSuchen->get; # Suchfunktion des Text-Widgets aufrufen my($Suchindex) = $txtEdit -> search( -nocase, $Suchbegriff, $StartPos); # Falls gefunden, markieren

263

9.8 Ein Editor mit Perl/Tk unless($Suchindex e q '') { my($Laenge) = length($Suchbegriff); $txtEdit->tagAdd("markiert", "$Suchindex", "$Suchindex + $Laenge chars"); # Neu positionieren $StartPos = "$Suchindex + $Laenge chars"; # Fensterausschnitt anpassen $txtEdit->see("$StartPos");

}

Nachdem Sie das Programm abgetippt 24 und gestartet haben, bietet sich der Editor wie in Abbildung 9.21 dar.

Abb.

9.21:

Der Perl-Editor

in

Aktion.

Bedenken Sie vor dem Einsatz der Anwendung bitte, dass es sich bei unserem BeispielCode wirklich um ein Beispiel und nicht um eine fertige Anwendung handelt. Wir verwenden vor allem globale Variablen,um das Skript nicht unnötig aufzublähen und verzichten zudem weitgehend auf Routinen zum Abfangen von Fehlern. Der Aufbau des Editor-Fensters folgt dem im Abschnitt zum Geometrie-Manager pack () vorgestellten Aufbau und besteht aus einer Reihe von Rahmen, in denen die Steuerelemente angeordnet sind. 24

Oder den Code von der Webseite zum Buch geladen haben.

264

9 Grafische Benutzerschnittstellen mit Perl/Tk

An der Oberkante des Editor-Fensters erstellen wir eine Menü-Leiste, in der wir den Eintrag Datei anzeigen. Das Frame-Steuerelement, welches das Menü aufnimmt, stellen wir sichtbar dar, indem wir dessen Eigenschaft -relief auf den Wert 'groove' setzen. Das Menü Datei erhält drei Menü-Einträge: Datei laden, Datei speichern und Programm beenden. Beachten Sie, dass die Callback-Routinen der beiden Schaltflächen zum Laden und Speichern dieselbe Unterroutine aufrufen. Sie übergeben allerdings jeweils einen Wert, aus dem wir innerhalb der Unterroutine schließen können, von welchem Button der Aufruf stammte. Die aufgerufene Subroutine ladenSpeichernO übernimmt zunächst das Argument in die lokale Variable $laden, anhand derer wir weiter unten feststellen, von welchem Button der Aufruf erfolgte. Sodann wird die Benutzerschnittstelle aufgebaut. Nachdem der allgemeine Teil des Dialogfensters aufgebaut ist, testen wir, ob wir eine Datei laden oder speichern wollen. Entsprechend beschriften wir die Schaltflächen und hinterlegen deren Callback-Routinen mit Hilfe der Methode bind(). Die Funktionalität zum Laden und Speichern bietet nicht viel revolutionäres, so dass wir hier auf eine weitere Beschreibung verzichten 25 . Der zweite Frame unseres Hauptfensters dient der Implementierung einer Suchfunktion. Mit dieser ist es möglich, einzelne Begriffe im Textfeld zu suchen und zu markieren. Wir kommen auf diese Funktion weiter unten zu sprechen. Der dritte Frame unseres Hauptfensters dient der Aufnahme des Text-Widgets. Zunächst erstellen wir ein Scrollbar-Steuerelement, das wir später in der bekannten Weise an das Text-Widget binden. Das Widget selbst erstellen wir wie üblich und belegen einige Eigenschaftswerte. Damit das Widget nicht ganz so trist wirkt, belegen wir die Eigenschaften für die Vorder- und Hintergrundfarbe (-fg und -bg) mit ansprechenden Farben. Die Höhe des Steuerelementes legen wir auf 34 Zeilen fest, die Breite belegen wir nicht, da wir in der Methode pack() für dieses Widget die Eigenschaften -expand und -f i l l setzen, um das Widget auf die maximal zur Verfügung stehende Breite zu dehnen. Wir binden dann den Scrollbar an. Schließlich setzen wir die Eigenschaft -wrap auf den Wert 'word'. Damit erreichen wir, dass innerhalb des Textbereiches Wörter, die über das Zeilenende hinausreichen, in die nächste Zeile umbrechen, wie Sie das von Ihrer Textverarbeitung gewohnt sind. Standardmäßig wird diese Eigenschaft auf den Wert 'char' gesetzt, was bewirkt, dass einfach überstehende Buchstaben in die nächste Zeile umgebrochen werden. Möchten Sie jedes Wrapping am Zeilenende verhindern, setzen Sie diesen Wert auf ' n o n e ' . Beachten Sie in letztem Fall, dass Sie dann einen horizontalen Scrollbalken anlegen müssen. Innerhalb der Unterroutine ladenSpeichern sehen Sie, wie es möglich ist, Text aus einem Text-Widget zu löschen. Hierzu verwenden wir die Methode deleteO, der wir zwei Parameter übergeben. Der erste Parameter ist der Index des ersten zu löschenden Zeichens. Der Index wird als Fließkommazahl in Klammern angegeben. Dabei bezeichnet der Wert vor dem Punkt die Zeile und der Wert hinter dem Punkt die Spalte. Beachten Sie, dass der Zeilenindex nicht bei 0 beginnt, sondern bei 1. Der Spaltenindex hingegen beginnt bei 0. Das zweite Argument bezeichnet das letzte zu löschende Zeichen, wir können hier die, oben im Zusammenhang mit dem Entry-Widget beschriebenen, Anker verwenden. Die Methode deleteO besitzt folgende allgemeine Syntax. 26

Die Methoden zum Lesen und Schreiben des Textfeldes beschreiben wir weiter unten.

265

9.8 Ein Editor mit Perl/Tk $widget->delete('indexStart',

'indexEnde');

Um in ein Text-Widget Text einzufügen, verwenden wir die Methode i n s e r t Q , der wir ebenfalls zwei Argumente übergeben. Zum einen den Index des Zeichens, nach dem der Text eingefügt wird, zum anderen den einzufügenden Text. Die Methode i n s e r t besitzt die folgende allgemeine Syntax. $widget->insert('indexStart',

$textString);

Von besonderem Interesse ist schließlich die Prozedur BegriffSuchenO, in der wir Ihnen zeigen, wie Sie mit sogenannten Text-Tags arbeiten. Text-Tags sind Textbereiche, die Sie beispielsweise mit einer anderen Hinter- und Vordergrund-Farbe belegen können, um Sie hervorzuheben. Nachdem die Prozedur aufgerufen wurde, löschen wir zunächst ein eventuell vorhandenes Tag mit der Bezeichnung markiert. Dies geschieht mit Hilfe der Methode tagRemove, welche folgende allgemeine Form hat. $widget->tagRemove('tagName', ' i n d e x S t a r t ' ,

'indexEnd');

Die beiden Indizes geben den Bereich an, in dem nach dem Tag gesucht werden soll, ein eventuell in einem anderen Bereich vorhandenes Tag gleichen Namens würde von der Löschaktion nicht betroffen. Nachdem wir den zu suchenden Begriff aus dem entsprechenden Textfeld eingegeben haben, verwenden wir die Methode s e a r c h O des Text-Widgets, um nach dem nächsten Vorkommen des Suchbegriffes zu "fanden". Diese Methode besitzt folgende allgemeine Syntax. $widget->search([schalter], 'suchmuster', ' i n d e x S t a r t ' ,

['indexEnde']);

Die wichtigsten Optionen, welche für die " s c h a l t e r " eingesetzt werden können sind: • -forwards: Die Suche findet vorwärts in Richtung Textende statt. • -backwards: Die Suche findet rückwärts in Richtung Textanfang statt. • -exact: Der Suchbegriff muss in der Schreibweise genau mit dem gefundenen Begriff übereinstimmen ; Wortteile werden, sofern diese Option gesetzt ist, nicht gefunden. • -nocase: Die Groß- und Kleinschreibung wird nicht beachtet. Wurde der Suchbegriff gefunden, wird er im Text mit Hilfe der Methode tagAddO hervorgehoben. Auch zu dieser Methode stellen wir die allgemeine Syntax vor. $widget->tagAdd('tagName', ' i n d e x S t a r t ,

'indexEnde');

Dabei ist es möglich, mehrere Textstellen in dieses Tag aufzunehmen, indem Sie für jede Textstelle ein weiteres Index-Paar angeben. Die Länge des zu markierenden Bereiches haben wir mit Hilfe der Funktion l e n g t h ( ) , die im ersten Band dieses Buches beschrieben wurde, ermittelt.

266

9.9

9 Grafische Benutzerschnittstellen mit Perl/Tk

Perl/CGI und Perl/Tk

Wir haben Ihnen nun mit Perl/CGI und Perl/ Tk zwei Möglichkeiten vorgestellt, grafische Benutzerschnittstellen zu erstellen. Welches Toolkit sollen Sie nun bevorzugen? Diese Frage lässt sich gar nicht so leicht beantworten, und es ist wohl zum großen Teil auch eine Frage der Vorliebe des Programmierers. Der Autor hat lange Zeit für Anwendungen, die lokal auf einem Rechner laufen, das Tk-Toolkit vor allem wegen seiner Geschwindigkeit und seiner umfassenden Anzahl an Widgets 26 bevorzugt. Nachdem es in letzter Zeit vorkam, dass Kunden, nachdem eine umfangreiche Anwendung fertig gestellt war, meinten: "... und nun stellen wir das ganze ins Internet ...", geriet diese Präferenz ein wenig ins Wanken. Für kleine Benutzerschnittstellen bevorzugt der Autor auch weiterhin Perl/Tk, bei großen Projekten hängt die Entscheidung nun sehr stark davon ab, ob der Kunde die Anwendung möglicherweise im Netz betreiben möchte, und wie wichtig ihm der Bedienungskomfort ist 27 . Wir haben Ihnen beide Möglichkeiten ausführlich vorgestellt, so dass auch Sie zukünftig die Qual der Wahl haben.

26 27

I m CPAN finden Sie noch eine ganze Reihe weiterer Widgets. Benutzerschnittstellen in Perl/CGI können mitunter recht unhandlich werden.

10

Weitergabe der fertigen Anwendung

Sie haben eine größere Anwendung fertig gestellt und möchten diese an einen Kunden oder einen anderen Benutzer weitergeben. Dies ist zumeist gar nicht so einfach, wie es auf den ersten Blick erscheint. Daher möchten wir Ihnen in diesem letzten Kapitel einige Hinweise zur Weitergabe Ihrer Anwendung geben. Besonders dann, wenn Sie Programme im Kundenauftrag schreiben, sollte die Anwendung sorgfältig geplant werden. Am besten tun Sie dies, indem Sie ein Pflichtenheft erstellen, in dem genau beschrieben steht, was zu tun ist und was nicht. Das Pflichtenheft sollte besonders sorgfältig geführt und alle Punkte genau beschreiben werden und keine sogenannten Selbstverständlichkeiten enthalten. Solche führen fast immer zum Streit, da Kunden in der Regel eine andere Auffassung haben als Sie, was selbstverständlich ist. Sprechen Sie, besonders dann, wenn ein Bestandteil der Programmierung eine Datenbank ist, genau die möglichen Benutzereingaben durch, und planen Sie dann die entsprechenden Datentypen. Der Autor hat im Rahmen eines größeren Projektes in bitterer Weise erfahren müssen, was geschieht, wenn an dieser Stelle zu locker gearbeitet wird. Nach Auslieferung der Anwendung beschwerte sich der Kunde fortwährend, dass es stets beim Abspeichern der Daten zu einem Fehler kommt, und sich der Datensatz nie abspeichern ließe. War der Autor vor Ort, funktionierte stets alles reibungslos. Erst als der Autor den Kunden bat, falls es wieder zu Problemen beim Abspeichern kommt, eine Bildschirm-Hardcopy der Eingabe-Maske zu machen, ließ sich das Problem klären. In der Datenbank gab es das Feld bau j ä h r , in das der Kunde das Baujahr von Häusern eintragen sollte. Der Autor wählte für dieses Attribut den Datentyp INT, ohne zu wissen, dass sich das Baujahr nicht in allen Fällen ermitteln ließ. Der Kunde versuchte in diesem Fall in das Feld Baujahr beispielsweise "ca. 1900" einzutragen, was natürlich einen Fehler im Datenbanksystem hervorrief. Der Autor hat hieraus gelernt, dass er in Zukunft, bevor er die Datenstruktur erstellt, dem Kunden einige Zeit über die Schulter blicken wird. Haben Sie das Pflichtenheft erstellt, ist es sinnvoll, dass beide Partner dieses unterschreiben. Möglicherweise legen Sie Skizzen von Eingabe-Masken etc. bei. So wissen beide Seiten, was Sie zu erwarten haben. Bedenken Sie, dass Kunden zumeist nie genau wissen, was sie wollen. Aber wenn Sie fertig sind mit der Arbeit, wissen sie zumeist was sie nicht wollen - nämlich das, was Sie programmiert haben! Wohl dem, der in dieser Situation ein gutes Pflichtenheft hat und dem Kunden beweisen kann, dass er das Programm erstellt hat, das abgesprochen war. Ist die Arbeit am Programm abgeschlossen, sollten Sie die Anwendung von vorne bis

268

10 Weitergabe der fertigen Anwendung

hinten und zurück testen, bis Sie sicher sind, dass alles einwandfrei funktioniert und die Anwendung auch nicht abstürzt, wenn der Benutzer falsche Eingaben macht. Und glauben Sie mir, er wird mit tötlicher Sicherheit spätestens nach 10 Minuten die Eingabemöglichkeit gefunden haben, die Sie nicht getestet haben und die alles zum Absturz bringt. Stellen Sie sicher, dass die verwendeten Module für alle Zielplattformen vorhanden sind. Dies gilt besonders dann, wenn Sie auf einer anderen Plattform als der Zielplattform entwickeln oder, wenn Ihre Anwendung für mehrere Plattformen konzipiert ist. Bedenken Sie, dass nicht alle Perl-Module auf Windows-Systemen mit allen verfügbaren C-Kompilern erstellt werden können. Manche Perl-Module sind beispielsweise in der Windows-Version auf den Microsoft Visual C-Kompiler angewiesen. Wenn Sie derartige Module in Ihrer Anwendung verwenden, müssen Sie vorher sicherstellen, dass dieser Kompiler auf allen Zielrechnern vorhanden ist. Am besten verwenden Sie auf WindowsPlattformen also nur Module, die Sie als Zip-Dateien von der ActiveState-Webseite beziehen können. Möglicherweise möchten Sie nicht, dass der Kunde oder Anwender Ihren Quellcode einsehen kann. In diesem Falle bieten sich vor allem zwei Lösungen an. Zu einen können Sie das PDK1 ActiveState verwenden. Diese Anwendung ist zwar nicht kostenlos, erlaubt aber Perl-Projekte in ausführbare Dateien (auf Windows-Systemen sogenannte . exe-Dateien) umzuwandeln. Dabei verfügt das PDK über die Möglichkeit, sogenannte freestanding applications zu erzeugen, die den Perl-Interpreter sowie alle benötigten, Module zusammenbinden. Der Anwender benötigt in diesem Falle nicht einmal eine Perl-Installation auf seinem Rechner. Den Code kann er auch nicht einsehen. Das PDK liegt in Versionen für Windows, Linux und Solaris vor. Auf Windows-Systemen können Sie mit dem PDK sogar Installations-Skripten erstellen, mit denen der Kunde die Anwendung problemlos selbst auf seinem System einrichten kann. Der Autor verwendet das PDK zur Auslieferung seiner Anwendungen bereits seit einigen Jahren und war mit der Leistung stets zufrieden. Nebenbei kann das PDK noch einiges mehr, aber das sehen Sie sich bei Interesse am Besten selbst auf der Webseite von ActiveState (http://www.activestate.com) an. Alternativ können Sie, vor allem auf Linux-Systemen, auch den Perl-Kompiler p e r l c c verwenden, der Perl-Code in C-Objekt-Code umwandelt, welchen Sie dann mit Hilfe Ihres C-Kompilers in eine ausführbare Datei umwandeln können. Der Perl-Kompiler ist Bestandteil der Standard-Distribution von Perl. In der aktuellen Version 5.8 liefert er, soweit der Autor das in einigen kurzen Tests ermittelt hat, recht brauchbare Resultate. Auf dem einzigen Windows-System des Autors führten die Versuche mit p e r l c c in das Nirvana der Speicherbausteine. Das kann allerdings auch daran liegen, dass sich der Autor auf diesen Systemen nicht besonders gut (eigentlich gar nicht) auskennt. Wir hoffen, dass diese kurzen Hinweise für Sie hilfreich sind und Ihnen den Programmieralltag erleichtern.

1

Perl

Development

Kit

11

Nachwort

Ein Nachwort? Schon immer hat sich der Autor darüber geärgert, dass die meisten Computerbücher umfangreiche Vorworte besitzen, die den Leser davon überzeugen sollen, das Buch zu erwerben. Hat man sich allerdings durch das Buch durchgearbeitet und dabei den Autor ein wenig kennengelernt, "fliegt" man am Ende der letzten Programmzeile zumeist ohne ein Wort des Dankes abrupt aus dem Buch heraus. Darum hier ein Nachwort: Ich weiß nicht, auf welche Weise Sie hierher geraten sind. Haben Sie das Buch Zeile für Zeile gelesen und durchgearbeitet, was ich mir wünschen würde? Oder haben Sie das Buch "diagonal" gelesen und nur die für Sie wichtigen Teile bearbeitet? Oder stehen Sie gar noch im Laden und wundern sich, warum dieses Buch ein Nachwort hat? In jedem Falle sind Sie hier! Ich hoffe, das Buch und unsere Ausführungen haben Ihnen Spaß gemacht und Sie auf dem Weg zum Perl-Programmierer ein Stück weitergebracht. Vielleicht haben Sie ja auch die eine oder andere Anmerkung zum Buch. In diesem Falle würde ich mich freuen, von Ihnen zu hören: [email protected] Ansonsten bedanke ich mich für Ihr Interesse und die Zeit, die Sie mit der Lektüre dieses Buches verbracht haben.

Landshut 2003 Helmut Seidel

12

Anhang

12.1

Skripten, die bei der Arbeit verwendet wurden

Während der Arbeit an diesem Buch habe ich einige Perl-Skripten geschrieben, um mir bestimmte Aufgaben zu erleichtern. Dabei bin ich mir durchaus bewusst, dass ich nicht immer den schönsten und bequemsten Weg gewählt habe. Vielmehr war mein Anliegen, ohne große Überlegung, so schnell wie möglich das gewünschte Ergebnis zu erzielen. Ich möchte Ihnen diese Skripten nicht vorenthalten. Und dies nicht, weil ich so stolz auf sie bin, sondern vielmehr, weil sie Perl "bei der Arbeit" zeigen. Alle gezeigten Skripten haben sich bei der Arbeit an diesem Buch viele Male bewährt und lassen sich sicherlich leicht an andere Aufgaben anpassen. Ich wünsche Ihnen viel Spaß mit den Programmen, möchte aber darauf hinweisen, dass ich natürlich keine Garantien für das Funktionieren übernehmen kann. Also, bevor Sie die Skripten auf Ihre fast fertige Doktorarbeit loslassen, machen Sie eine Sicherungskopie!1 Noch ein Hinweis: Alle Skripten (auch die im vorangehenden Buch abgedruckt sind) unterliegen der sogenannten GPL 2 (General Public Licence). Dies bedeutet konkret für Sie, dass Sie die Programme beliebig kopieren, weitergeben und verändern dürfen. Was Sie nicht dürfen, ist die Urheberschaft zu verschleiern oder die Skripten für Produkte zu verwenden, bei denen die Quellcodes nicht offen liegen! Das heißt, alle entstehenden Produkte unterliegen wiederum der GPL.

12.1.1

rmtab.pl: Ersetzen von Tabulator-Zeichen durch Leerzeichen

Die LaTeX-Umgebung, mit der ich das Buch gesetzt habe, liebt es nicht besonders, wenn in Code-Fragmenten Tabulator-Zeichen erscheinen. Durch diese gerät die Formatierung in ein heilloses Durcheinander. Andererseits liebe ich es nicht besonders, in meinen Skripten Einrückungen mit Hilfe der Leertaste zu erzeugen. Mein bevorzugter Code-Editor nedit soll wohl in der Lage sein, tabs durch die entsprechende Anzahl von Leerzeichen zu ersetzen, aber wer weiß schon wo? Ich habe daher folgendes Skript erstellt, das alle vorkommenden tabs durch je vier 1 Von

der Arbeit! Nicht von den Skripten! englischen Originaltext zu dieser Lizenz finden Sie unter der Internet-Adresse http://www.gnu.org/copyleft.gpl 2 Den

272

12 Anhang

Leerzeichen ersetzt. Damit es für Sie etwas komplizierter wird, Ihre Doktorarbeit zu zerwürgen, wird nicht die Original-Datei verändert, sondern die Ausgabe erfolgt in eine neue Datei mit dem Namen der Original-Datei und der Endung .tab. #!/usr/bin/perl -w # Script zum Entfernen von Tabulatoren # gefundene Tabulatoren werden durch vier # Leerzeichen ersetzt # Dateinamen abfragen print "Dateiname: "; chomp(my $datName = ); # Datei zum Lesen oeffnen open IN, "$datName" or die "Konnte Datei $datName nicht zum Lesen oeffnen!"; # Ausgabedatei oeffnen open OUT, "> ${datName}.tab" or die "Konnte Datei ${datName}.tab nicht zum Schreiben oeffnen!"; # in Schleife tab-Zeichen durch Leerzeichen ersetzen while() { s/\t/ /g; # Zeile in Datei schreiben print OUT $_;

> 12.1.2

rmcode.pl: Ersetzen von CODE-Tags durch LaTeX-Tags

Bei der Arbeit an meinem Buch ließ ich zunächst die Formatierung von Code-Fragmenten außer Acht. Vielmehr habe ich die Code-Fragmente separat entwickelt, getestet und schließlich per Copy & Paste in das Manuskript übernommen. Um Code-Fragmente sichtbar zu machen, habe ich jeweils vor das Fragment das selbst erfundene Tag [CODE] und hinter das Fragment das Tag [/CODE] gestellt. Um das Ersetzen nicht zu einfach zu gestalten, verwendete ich die obengenannten Tags sowohl für einzeilige als auch für mehrzellige Code-Blöcke, die allerdings in LaTeX unterschiedlich dargestellt werden. Das Skript löst die Aufgabe auf folgende Art. Nach dem zeilenweisen Einlesen der Quelldatei wird in jeder eingelesenen Zeile das [CODE]-Tag gesucht. Ist eines gefunden, testet die Anwendung, ob sich in der Zeile auch das entsprechende Endtag befindet. Ist dies der Fall, werden beide mit Hilfe eines regulären Ausdrucks ersetzt. Findet sich das Endtag nicht in der selben Zeile, wird zunächst das Anfangstag durch den entsprechenden LaTeX-Code ersetzt. Anschließend werden solange weitere Zeilen eingelesen, bis das

12.1 Skripten, die bei der Arbeit verwendet wurden

273

Endtag gefunden und ersetzt ist. Nun wird wieder in die Hauptschleife zurück verzweigt. Es handelt sich also bei der Anwendung um eine geschachtelte while-Schleife.

#!/usr/bin/perl -w # Skript zum Ersetzen von [CODE] und [/CODE] # Tags durch korrekte LaTeX-Anweisungen. # Dabei werden unterschieden: # grosser Code-Block # einzeiliger Code (texttt) # Dateinamen abfragen print "Dateiname: "; chomp(my $datName = ); # Datei zum Lesen oeffnen open IN, "$datName" or die "Konnte Datei $datName nicht zum Lesen oeffnen!"; # Ausgabedatei oeffnen open OUT, "> ${datName}.code" or die "Konnte Datei ${datName>.tab nicht zum Schreiben oeffnen!"; # in Schleife alle [CODE]-Tags finden while() { # Code-Tags finden if (index($_, '[CODE]') >= 0) { # Zeile ausgeben print "\n*****\n"; print $_; # Testen, ob einzeiliger Code-Block if (index($_, '[/CODE]') >= 0) { s/\ [C0DE\]/\\texttt\{/g; s/\[\/C0DE\]/\}/g; print OUT $_; } eise { # Mehrzelliger Block # Anfangstag ersetzen s/\[C0DE\]/\\medskip\n\\begin\{verbatim\}\n/; print OUT $_; # Endtag suchen while (1) { my $zeile = ; if (index($zeile, '[/CODE]') == -1) { print OUT $zeile; y eise { # End-Tag ersetzen

274

12 A n h a n g

>

>

>

$zeile =~ s/\[\/CODE\]/\n\\end\{verbatim\}\n\\medskip\\medskip\n/; print OUT $zeile; last;

} eise { # normale Zeile schreiben print OUT $_;

>

>

13

Literaturverzeichnis

[DesBunceOO] Alligator Descartes, Tim Bunce: Programming the Perl DBI, Sebastopol, 2000 [GantenOO] Peter H. Ganten: Debian GNU/LINUX,

Berlin, Heidelberg, 2000

[GueGunOl] Scott Guelich, Shishir Gundavaram, Gunther Birznieks: CGI-Programmierung mit Perl, 2. Auflage, Köln, 2001 [Hahn03] Christian Hahn: Web Content Management mit Perl, Bonn, 2003 [Juergens94] Manuela Jürgens: GNU EMACS - eine Einführung und ein bißchen mehr ..., Fernuniversität Hagen, 1994 - Download über Internetseite der Universität [Juergens95A] Manuela Jürgens: LaTeX - Eine Einführung und ein bißchen mehr ..., Fernuniversität Hagen, 1995 - Download über Internetseite der Universität [Juergens95B] Manuela Jürgens: LaTeX - Fortgeschrittene Anwendungen, Fernuniversität Hagen, 1995 - Download über Internetseite der Universität [Kaes03] Martin Kästner: Perl fürs Web, Bonn, 2003 [KernRitch90] Brian W. Kernighan, Dennis M. Ritchie: Programmieren in C, München Wien, 1990 [Kloeppel97] Bert Klöppel, Thomas Dapper, Carsten Dietrich, Rene Seeber: Objektorientierte Modellierung und Programmierung mit C++, München Wien, 1997 [KoflerOl] Michael Kofler: MySQL, Einführung, Programmierung, Referenz, München, 2001 [MuscKen99] Chuck Musciano, Bill Kennedy: HTML Das umfassende Referenzwerk, 3. Auflage, Köln 1999 [Oestereich98] Bernd Oestereich: Objektorientierte Softwareentwicklung, München Wien, 1998

276

13 Literaturverzeichnis

[Schicker99] Edwin Schicker: Datenbanken und SQL, Stuttgart, Leipzig, 1999 [SieSpainOO] Ellen Siever, Stephen Spainhour, Nathan Patwardhan: Perl in a Nutshell, Köln, 2000 [Sriniva98] Sriram Srinivasan: Fortgeschrittene Perl Programmierung, Köln, 1998 [Stroustrupp98] Bjarne Stroustrupp: Die C++ Programmiersprache, 3. Auflage, Bonn, 1998 [WallOl] Larry Wall, Tom Cristiansen, Jon Orwant: Programmieren mit Perl, 2. Auflage, Köln, 2001 [Walsh99] Nancy Walsh: Learning Perl/Tk, Sebastopol, 1999 [WelKau98] Matt Welsh, Lar Kaufman: LINUX, Auflage, Köln, 1998

Wegweiser zur Installation & Konfiguration, 2.

Index -anchor, 203 -background, 203 -bitmap, 206 -borderwidth, 203 -columnspan, 249 -command, 206, 211 -expand, 245 -fill, 245 -foreground, 203 -image, 207 -justify, 210 -padx, 203, 244 -pady, 203, 244 -relief, 204 -rowspan, 249 -side, 241 -variable, 212 .bashrc, 14 < / p > , 142 , 147 , 155 , 148 , 184 , 184 , 155 Checkboxen, 157 CHECKED, 158 einzeilige Textfeld, 156 HIDDEN, 156 MAXLENGTH, 156 Passwortfelder, 156 Radiobuttons, 157 Reset-Buttons, 160 SIZE, 156 TYPE, 155 VALUE, 156, 159 < SELECT >, 161 MULTIPLE, 163 SELECTED, 162

SIZE, 163 VALUE, 162 < TEXTARE A >, 160 , 160 NONE, 161 PHYSICAL, 160 VIRTUAL, 160 , 184 , 142
, 142 , 142 , 142 < h l > , 143 , 140 , 143 , 140 , 142 , 144
  • , 145 , 145 , 144 , 142
  • $spaltenNamen";

    >

    print "
    $wert 
    , 145 < t d > , 145 , 141 < t r > , 145 < t t > , 142 < u > , 142
      , 145 #include, 35 SAUTOLOAD, 24 $DBI::errstr, 103 $ENV{'QUERY_STRING'}, 155 $ENV'QUERY_STRING', 163  , 154 1 : 1 - Beziehung, 78 1 : n - Beziehung, 79 Bibliothek, 14

      278 Prozessdaten, 74 ACTION, 155 ActivePerl, 33 ActiveState, 28, 32 addPt(), 190 ADO, 71 Aggregatfunktionen, 92 AVG, 92 COUNT, 92 MAX, 92 MIN, 92 SUM, 92 align, 145 Anker, 144 ANSI, 122 Apache, 135 arc(), 189 ASP, 140 Attribut, 73 autoexec.bat, 14 AUTOLOAD, 23, 24 Autoloading, 23 AutoSplit, 24 available_drivers(), 98 BEGIN, 18 BETWEEN, 88 Bibliothek, 9 bind, 218 bless(), 51 Blessing, 51, 62 BrowseEntry-Widget, 226 BrowseEnty -listcmd, 228 -variable, 228 Button, 200 Button-Widget, 211 C + + , 41 C-Bibliotheken, 35 CGI, 135, 138 CGI::Carp, 153 Checkbutton-Widget, 212 chmod, 139, 150 CODE:, 37 Config.sys, 122

      Index configure, 203 connect(), 100 Content-type, 183 CPAN, 28 CREATE, 93 CREATE TABLE, 94 darstellungsorientierten, 141 DBD, 97 DBI, 96 DBI/DBD, 71 DELETE, 95 Denormalisierung, 77 disconnect(), 104 do(), 130 DocumentRoot, 139 dritte Normalform, 76 DROP, 93 DROP TABLE, 95 Eigenschaften, 42 Einfachvererbung, 64 END, 20 entpacken, 30 Entry -textvariable, 222 delete(), 223 get(), 222 insert (), 222 Entry-Widget, 221 erste Normalform, 75 executeQ, 113 exportieren, 21, 27 fatalsToBrowser, 153 fetchall_arrayref(), 122 fetchrow_array(), 107 fetchrow _ arrayref (), 111 fetchrow_hashref(), 112 fill(), 189 filledPolygon(), 191 filledRect angle (), 189 F O R W A R D O N L Y , 106 Frames, 242 FROM, 83 GD, 182

      Index GD::Font, 182 GD::Graph, 193 GD:: Graph:: area, 194 GD::Graph::bars, 194 GD::Graph::linepoints, 194 GD::Graph::lines, 194 GD::Graph::mixed, 194 GD::Graph::pie, 194 GD::Graph::points, 194 GD-Image, 182 GD::Polygon, 190 GD: ¡Polygon, 182 Geometrie-Management, 197 GET, 155 getBounds, 184 GIF,144, 182 Glade, 240 globalen Variablen, 9 Gnome Tollkit, 241 grafische Benutzerschnittstellen, 27 grid(), 250 GUI, 197 h2xs, 36 HList -command, 235 -data, 235 -drawbranch, 235 -image, 235 -indent, 235 -itemtype, 235 -separator, 235 -text, 235 add,236 deleteAll, 236 deleteEntry, 236 deleteOffsprings, 236 entryconfigure, 236 HList-Widget, 230 HTML, 135 HTTP-Header, 150 httpd.conf, 138 Hyperlinks, 143 inhaltsorientierten, 141 Installation, 29 Instanz, 45, 64

      279 Instanzmethoden, 46, 62 Instanzvariablen, 46 IS NULL, 88 Java, 41 JOIN, 90 INNER JOIN, 90 LEFT JOIN, 92 Natural Join, 90 ON, 91 OUTER JOIN, 92 RIGHT JOIN, 92 JPEG, 144, 182 Kapselung, 28, 44 Klasse, 45 Klassen-Eigenschaften, 46 Klassen-Hierarchie, 64 Klassenmethoden, 46, 62 Kompilieren, 17 Konstruktor, 46, 50, 51, 62 Label-Widget, 208 -command, 209 -text, 209 -textvariable, 209 Layout-Manager, 154 LIKE, 88 line(), 188 Link, 144 Listbox -selectmode, 217 curselection, 217 delete, 217 get(), 218 insert, 216 Listbox-Widget, 216 local, 9 main, 10 MainLoop(), 201 MainWindow, 199 make, 31, 35 make install, 32 Make-Prozedur, 31 Makefile, 31 Makefile.PL, 36

      280 Mehrfachvererbung, 64 Menubutton -accelerator, 255 Menubutton-Widget, 253 Methode, 43 MODUL, 35 Modul, 8, 23, 29, 50 Modularisieren, 7 modularisieren, 9 Module, 27 my, 9 MySQL, 72, 81 n : m - Beziehung, 79 Namensraum, 9, 21, 50 Normalformlehre, 75 NoteBook -createcmd, 225 -font, 225 -raisecmd, 226 -underline, 225 delete, 226 raise, 226 raised, 226 Notebook -state, 226 NoteBook-Widget, 223 NrowseEntry -browsecmd, 228 Oberklasse, 47, 64 Objekt, 43, 45 Objektorientierten Programmierung, 41 ORDER BY, 85 DESC, 85 OUTPUT:, 37 pack(), 201, 241 Package, 9 package, 10, 51 param, 177 Parameter Abfrage, 125 PDK, 268 Perl/Tk, 29 PERL5LIB, 14 perlcc, 268 Pfad, 13

      Index Pfeiloperator, 52, 62 Pflichtenheft, 267 PhP, 140 phpMyAdmin, 93 PNG, 182 Polygon, 189 polygon(), 191 Polymorphie, 69 POST, 151, 163 PostgreSQL, 81 PPM, 32 ppm install, 33 ppm remove, 34 prepare, 105 prepare(), 125 profile, 14 Radiobutton-Widget, 212 rectangle(), 188 Redundanz, 73, 74 Relation, 73 relationalen Datenbanken, 72 require, 11, 12 Resultset NAME, 114 NULLABLE, 116 N U M O F F I E L D S , 113 N U M O F P A R A M S , 116 rows, 116 TYPE, 115 RETVAL, 37 RGB, 187 Runlevel, 136 Script Alias, 139, 150 Scrollbar, 219 -orient, 221 -yscrollcommand, 220 SELECT, 83 selectcol_arrayref(), 119 selectrow_array(), 118 SET, 96 SQL, 72, 81 AND, 87 NOT, 89 OR, 87 Srcollbar

      Index

      281

      yview, 220 Subklasse, 47 SUPER, 68 Superklasse, 47 Symboltabelle, 9, 10

      Validierungen, 28 Vererbung, 46, 62 Verhaltensweisen, 42 VisualBasic, 71 voll qualifizierter Name, 14

      table_info(), 132 REMARKS, 133 T A B L E N A M E , 133 T A B L E O W N E R , 132 TABLEQUALIFIER, 132 T A B L E T Y P E , 133 tables(), 132 Tcl/Tk, 197 Templates, 135, 171 Text -backwards, 265 -exact, 265 -forwards, 265 -nocase, 265 delete(), 264 insert (), 264 searchQ, 265 Text-Tags, 264 tagAdd(), 265 tagRemove, 265 Text-Widget, 256 TixGrid -browsecmd, 239 -editdonecmd, 239 deleteColumn, 239 deleteRow, 239 entrycget, 240 set(), 239 sizeColumn, 239 sizeRow, 239 TixGrid-Widget, 236 Tk, 197 Tk.pm, 197 Toplevel-Window, 199 Tupel, 73

      Webapplikationen, 140 Webserver, 135, 139 WHERE, 86 Widgets, 197

      Uberschreiben, 67 Unterklassen, 47 UPDATE, 96 URL, 143 use, 11

      XS, 35 XSUB, 35 zweite Normalform, 76