Zu den großen Stärken von Tcl/Tk gehört das schnelle Entwickeln grafischer Benutzeroberflächen. Wie einfach das geht, belegt ein kleines Programm, das den Wetterbericht der US-Luftfahrtbehörden auswertet.
Viele Programmierer schrecken davor zurück, eine Anwendung mit grafischer Benutzeroberfläche zu schreiben. Bis eine kompilierte Applikation den Bildschirm schmückt, dauert es meist eine Weile. Anders mit Tcl/Tk, hier genügen oft schon Minuten, um Anwendungen jenseits primitiver “Hello World”-Progrämmchen zu entwickeln. Das folgende Beispiel zeigt, wie einfach das mit Tcl/Tk gelingt.
Die Beispielanwendung soll die Wetterdaten deutscher Flughäfen darstellen, die Daten stammen vom US-amerikanischen Wetteramt NOAA. Die National Oceanic and Atmospheric Administration[1] veröffentlicht nicht nur viele Fotos als Public Domain[2], sondern kostenfrei auch Flugwetterdaten im METAR-Format[3] (Meteorological Aviation Report). Quelltext zum Abfragen und Auslesen dieser Daten liegt bereits fertig entwickelt als Bestandteil des Programm Tclweather vor[4]. Dieser Artikel beschäftigt sich damit, die Daten in einer eigenen Oberfläche darzustellen.
Ein GUI entsteht in mehreren Schritten. Das Programm muss die einzelnen Widgets erzeugen, zum Beispiel Knöpfe und Eingabefelder, und sie in der Oberfläche anordnen. Außerdem brauchen die Widgets noch Daten, die sie anzeigen sollen, oder Prozeduren, die auf Benutzereingaben reagieren.
Vorbereitung
Zum Anlegen der Widgets bringt Tk je einen eigenen Befehl mit. Das Kommando »label« erzeugt eine Beschriftung, »button« einen Knopf. Tk enthält alle üblichen Widgets, einen Überblick gibt das mitgelieferte Beispielprogramm »widget«. Es liegt meist unter »/usr/lib/tk8.4/demos/widget« oder »/usr/local/lib/tk8.4/demos/widget«.
Alle Widgets brauchen einen eindeutigen Namen, der sie identifiziert. Der Name spiegelt auch die Struktur, in der die Widgets ineinander geschachtelt sind. Abbildung 1 zeigt einen Ausschnitt der Struktur des Hauptfensters und der Widgetnamen des Wetterprogramms. Das oberste Widget dient als Hauptfenster, es heißt ».«. In ihm liegt ein Rahmen ».frame«, der die Widgets für den Ort oder das Wetter aufnimmt.
Die Namen der Widgets sollten eingängig sein, um beim Programmieren ohne lange zu überlegen auf ihre Aufgabe zu schließen. Wichtige Einschränkung: Sie dürfen keine Leerzeichen enthalten. Praktisch ist, dass die Befehle zum Erzeugen von Widgets den Namen zusätzlich als Rückgabewert weitergeben. Damit kann ein Programm in derselben Zeile ein Widget anlegen und dessen Namen einer Variablen zuweisen: »set Variable [frame Name Optionen]«.
Gemeinsame Optionen
Beim Erzeugen der Widgets können Optionen das Aussehen und Verhalten sehr detailliert festlegen. Viele Optionen wie Farbe oder Schrift gelten für alle Arten von Widgets, einige wesentliche sind in Tabelle 1 enthalten. Welche Optionen jedes Widget versteht, steht in seiner Manualseite, eine genaue Beschreibung aller gemeinsamen Optionen enthält die Manpage »options«.
Der erste Teil der Prozedur »erzeugeOberfläche« in Listing 1 (Zeilen 17 bis 31) zeigt, wie Label- und Frame-Widgets entstehen. Zuständig sind die Kommandos »label« (beispielsweise Zeile 19) und »frame« (Zeile 18). Der erste Parameter dieser Kommandos gibt den Namen des Widget an. Unter diesem Namen entsteht ein neues Kommando, das genau diese Widgetinstanz steuert.
Was beim Aufruf des Widgetkommandos passieren soll, gibt ein Unterkommando an. Alle Widgetinstanzen kennen das Unterkommando »configure«, mit dem das Programm jederzeit Optionen abfragen und ändern kann. Zeile 20 nutzt dies, um die Default-Schrift eines Labels abzufragen. Zeile 21 ändert den Font, Zeile 22 fragt ab, was Tk tatsächlich gewählt hat: Ist die gewünschte Schriftart nicht verfügbar, weicht Tk auf einen ähnlichen Font aus.

Abbildung 1: Die Struktur der GUI-Elemente spiegelt sich in ihren Namen: ».frame.ort« liegt innerhalb seines Vaterwidget ».frame«.
Die neuen Widgets existieren zwar, erscheinen aber noch nicht in der Oberfläche. Das Toolkit muss erst wissen, an welcher Stelle es das Element anordnen soll. Hierfür gibt es mehrere Kommandos, die meisten Anwendungen kommen jedoch mit »pack« und »grid« aus. Ersteres eignet sich für einfache Reihen, ihre Richtung (nach links, oben …) wird durch die Option »-dir« festgelegt.
Ohne weitere Angaben verändern die Widgets ihre Größe nach dem Platzieren nicht mehr. Sollen sei beim Größerziehen des Fensters mehr Platz einnehmen, muss der Programmierer weitere Optionen einsetzen. Mit »-expand true -fill both« gibt Zeile 34 an, dass der Rahmen in beiden Richtungen wachsen und das Fenster komplett ausfüllen soll.
Die Anzeige der Wetterdaten besteht aus neun Widgets (Zeilen 19 sowie 24 bis 31), hier wäre das Platzieren mit »pack« zu umständlich. Das mächtige »grid«-Kommando eignet sich deutlich besser. Es arbeitet mit einem tabellenförmigen Entwurfsraster aus Spalten und Zeilen, wobei die Zellen je ein Widget aufnehmen. Wie die Zeilen und Spalten auf die Größe der enthaltenen Widgets und auf Größenänderungen das Gesamtrahmens reagieren, lässt sich exakt einstellen. Einzelne Widgets dürfen sich auch über mehrere Zellen erstrecken.
|
Tabelle 1: Wichtige |
|
|---|---|
|
Option |
Bedeutung |
|
-background Farbe |
Hintergrundfarbe |
|
-foreground Farbe |
Textfarbe |
|
-padx Abstand, -pady Abstand |
Zusätzlicher Abstand in x- oder y-Richtung |
|
-text Text |
Text (bei Label und Eingabefeldern) |
|
-textvar Variablenname |
Verbindet das Widget mit dem Inhalt einer Variablen |
|
-font Schriftbezeichnung |
Verwendete Schriftart |
|
-label Text |
Text (bei Menüs) |
|
-command Befehl |
Ausgeführter Befehl |
In Abbildung 2 ist das Entwurfsraster in die Anwendung eingezeichnet. Die grünen Pfeile zeigen die Ausrichtung der Widgets innerhalb ihrer Zelle, die roten Pfeile kennzeichnen die Zeilen und Spalten, die wachsen dürfen.

Abbildung 2: Das Entwurfsraster der Oberfläche zeigt, dass Grid die Widgets tabellarisch anordnet und an unterschiedlichen Kanten ausrichtet.
Montage im Raster
Der Einsatz des »grid«-Kommandos ist in den Zeilen 37 bis 54 zu sehen. In 37 und 38 füllt »grid Widget1 Widget2 … Optionen« erste Widgets in das Raster. Wenn im Kommando weder Reihe noch Spalte angegeben sind, fängt das Grid links oben an und legt bei jedem weiteren Aufruf die Widgets in eine neue Zeile. Neben Widgetnamen versteht »grid« auch die Zeichen »-« und »x«: Bei Ersterem erstreckt sich ein Widget über eine weitere Zelle, mit »x« wird eine Zelle ausgelassen. Mit »-row Reihe« und »-column Spalte« kann das Programm eine beliebige Stelle wählen, wie ab Zeile 41 zu sehen ist.
Die Option »-sticky« bestimmt, wie das Widget innerhalb seiner Zelle anzuordnen ist. Im Normalfall liegt es zentriert in der Zelle, durch Angabe einer einfachen Himmelsrichtung (»n«, »e«, »s«, »w«) klebt es an einer Kante. Sind mehrere Richtungen angegeben, dehnt es sich von Kante zu Kante, mit »-sticky nesw« füllt es die Zelle komplett. Das lässt sich am einfachsten beobachten, wenn man dem Elternwidget (».frame« im Beispiel) eine Kontrastfarbe gibt, wie Abbildung 3 zeigt.
Das Verhalten der Zeilen und Spalten wird mit »grid rowconfigure« und »grid columnconfigure« definiert. Normalerweise ändert sich ihre Größe nicht. Die »-weight«-Option auf einzelne Zeilen oder Spalten gibt an, in welchem Verhältnis Grid zusätzlichen Platz verteilt. Im Beispiel erhalten die letzten beiden Spalten das gleiche Gewicht (Zeilen 52 und 53), bei den Reihen bekommt die erste allen zusätzlichen Platz.

Abbildung 3: Durch den rot gefärbten Hintergrund sind Position und Größe der einzelnen Widgets im Hauptfenster bei diesem Testlauf gut zu erkennen.
Mit Daten verknüpfen
Widgets sind kein Selbstzweck, sie sollen Benutzereingaben zulassen oder Daten anzeigen. Die Verbindung zwischen Daten und Widgets kann auf zwei Wegen erfolgen. Beim Label für die Temperatur ist die Option »-textvariable« mit dem Namen einer Variablen angegeben (Zeile 26). Damit zeigt das Label alle Änderungen des Variablenwerts sofort an. Bei einem Eingabefeld gilt dies in beiden Richtungen, jede Benutzereingabe ändert die verbundene Variable.
Die zweite Methode kommt bei den Labels für Luftdruck und Wolken zum Einsatz (Zeilen 30 und 31). Die Prozedur »::gui::formatData« bereitet die Daten für die Darstellung auf und setzt mit »$f.luftdruck configure -text neuer Wert« den angezeigten Wert.
Beide Wege sind möglich und üblich, die Textvariable ist aber oft eleganter. Aufgerufen wird »::gui::formatData« von »::metar« aus der Datei »metar.tcl« (eingebunden in Zeile 5). Diese Funktion lädt die Wetterdaten. Ihr Code stammt überwiegend aus Tclweather[4].
Angepasste Speisekarte
Ein wichtiges Steuerelement für Benutzer sind Menüs. Die drei Menüs des Beispiels entstehen in »erzeugeMenus«. Ausgangspunkt ist die Menüzeile, die das Programm zunächst mit dem »menu«-Kommando erzeugt (Zeile 60) und dann über die »-menu«-Option in das Hauptfenster einklinkt (Zeile 61). Obwohl in Tk Menüs wie alle anderen Widgets mit »pack« oder »grid« platzierbar sind, ist die »-menu«-Option sehr zu empfehlen. Damit verhält sich das Menü zum Beispiel unter Mac OS X so, wie es ein Anwender auf diesem System erwartet: Es erscheint am oberen Bildschirmrand und nicht im Fenster.
Die Zeilen 63 bis 89 erzeugen die drei Untermenüs und füllen sie mit Einträgen. Zunächst ist ein Untermenü ebenfalls ein Menü, das mit »menu« zu erzeugen ist (zum Beispiel in Zeile 64). Der Aufruf ».menubar add cascade -label Eintrag -menu Untermenü« hängt es in das Hauptmenü (Zeile 65). Die Untermenüs erhalten mit folgendem Kommando ihre Einträge: » Untermenü add command -label Text -command Befehl«. Den Befehl führt Tk aus, sobald der Benutzer diesen Menüpunkt aufruft.
|
Das Neueste |
|---|
|
Die Arbeiten an der nächsten Tcl-Version gehen weiter, schließlich soll 8.5 im Herbst fertig sein. Insbesondere das Tk-Aussehen verdient und erhält viel Aufmerksamkeit: Mit Tile[6] wird auch Tk Theme-fähig. Ein neu entwickelter Tcl-Interpreter findet sich bei Berlios: Jim[7]. Anders als die normale Tcl-Shell enthält er Features wie Garbage Collection und Unterstützung für Objektorientierung, soll aber wesentlich schlanker ausfallen. Zurzeit ist Jim zwar interessant, aber noch kein vollwertiger Ersatz für die etablierte Tcl-Shell. Treffen der Tcl/Tk-AnwenderMichael Haschek veranstaltet am 27. und 28. Mai 2005 das fünfte europäische Tcl/Tk-Benutzertreffen[8], diesmal in Bergisch Gladbach bei Köln. In lockerer Runde können Anwender und Entwickler ihre Erfahrungen austauschen. Einige Vorträge des amerikanischen Benutzertreffens von 2004 stehen inzwischen öffentlich zur Verfügung[9]. PDF wird immer wichtiger als Ausgabeformat. Mit Pdf4tcl stellt Frank Richter eine Tcl-Erweiterung vor, mit der Tcl-Programme bequem PDF-Dateien erzeugen können[10]. Das Ergebnis ist in Abbildung 4 zu sehen. Auch Mac Codys Trampoline[11] wird immer besser darin, den Inhalt eines Canvas-Widget nach PDF zu exportieren. Während beim Entwickeln einer Anwendung mehrere getrennte Dateien übersichtlicher sind, wäre für die Installation eine ausführbare Datei praktischer. Bei interpretierten Sprachen entfällt aber das Übersetzen und Linken. Dennis LaBelles Freewrap löst dieses Dilemma. Es erzeugt aus beliebig vielen Tcl-Skripten, Bibliotheken und Icons eine komplette Anwendung als binäre Datei, die sogar den Tcl-Interpreter enthält[12]. |
Wichtige Kommandos wie »Beenden« sollen nicht nur über das Menü, sondern auch durch eine Tastenkombination bereitstehen. Die »bind«-Anweisung in Zeile 72 sorgt dafür, dass [Strg]+[Q] die gleiche Prozedur auslöst (Zeile 103) wie der »Beenden«-Menüpunkt. Damit jeder über die Tastenkombination Bescheid weiß, ist sie auch im Menü angegeben. Die Option »-accelerator« erledigt dies in Zeile 71. Für besondere Fälle können Menüs noch weitere Knopftypen (Checkbutton, Radiobutton) oder Icons [5] enthalten. Trennlinien entstehen durch » Untermenü add separator« (Zeile 68).
|
Listing 1: Einfaches |
|---|
001 package require Tk
002 package require msgcat
003
004 source listing2.tcl
005 source metar.tcl
006 source tclweather-1.12/lib/tclweather_scanner.tcl
007 source tclweather-1.12/lib/conversions.tcl
008
009 namespace eval ::gui {
010 msgcat::mclocale de
011 }
012
013 # Die Oberfläche erzeugen
014 proc ::gui::erzeugeOberfläche {} {
015 variable f
016
017 # Widgets anlegen
018 set f [frame .frame -padx 5 -pady 5]
019 label $f.ort -anchor w -textvar ::tw(ort)
020 puts "Font war «[$f.ort configure -font]»"
021 $f.ort configure -font {Palatino 18 bold}
022 puts "Font ist «[$f.ort configure -font]»"
023
024 label $f.wetter -textvar ::gui::ddata(wetter)
025 label $f.temperaturL -text "Temperatur : "
026 label $f.temperatur -textvar ::gui::ddata(temp,air)
027 label $f.windL -text "Wind : "
028 label $f.wind -textvar ::gui::ddata(wind)
029 label $f.luftdruckL -text "Luftdruck : "
030 label $f.luftdruck
031 label $f.wolken
032
033 # Widgets mit »pack« platzieren
034 pack $f -expand true -fill both
035
036 # Widgets mit grid« platzieren
037 grid $f.ort - - -sticky w
038 grid x $f.wetter -sticky w
039
040 set row 2
041 grid $f.temperaturL -row $row -column 0 -sticky e
042 grid $f.temperatur -row $row -column 1 -sticky w
043 grid $f.luftdruckL -row $row -column 2 -sticky e
044 grid $f.luftdruck -row $row -column 3 -sticky w
045
046 incr row
047 grid $f.windL -row $row -column 0 -sticky e
048 grid $f.wind -row $row -column 1 -sticky w
049
050 grid x $f.wolken -sticky w
051
052 grid columnconfigure $f 0 -weight 1
053 grid columnconfigure $f 1 -weight 1
054 grid rowconfigure $f 0 -weight 1
055 }
056
057 # Die Menüs erzeugen
058 proc ::gui::erzeugeMenus {} {
059 # Menüleiste
060 menu .menubar
061 . configure -menu .menubar
062
063 # Dateimenü
064 set m [menu .menubar.datei]
065 .menubar add cascade -label "Datei" -menu $m
066 $m add command -label Einstellungen
067 -command ::gui::einstellungen
068 $m add separator
069 $m add command -label Beenden
070 -command ::gui::beenden
071 -accelerator ^q
072 bind . <Control-q> ::gui::beenden
073
074 # Ort-Menü
075 set m [menu .menubar.ort]
076 .menubar add cascade -label "Ort" -menu $m
077
078 foreach {lc ort} [list EDDI Berlin EDDL Düsseldorf
079 EDDF "Frankfurt Main" EDHI "Hamburg Finkenwerder"
080 EDDV Hannover EDHL Lübeck EDDM München]
081 {
082 $m add command -label $ort
083 -command [list ::gui::setzeOrt $lc $ort]
084 }
085
086 # Hilfemenü
087 set m [menu .menubar.help]
088 .menubar add cascade -label "Hilfe" -menu $m
089 $m add command -label Info -command ::gui::zeigeInfo
090 }
091
092 # Die komplette Oberfläche initialisieren
093 proc ::gui::init {} {
094 # Hauptfenster
095 wm title . Wetter
096 wm geometry . 400x130
097
098 erzeugeOberfläche
099 erzeugeMenus
100 }
101
102 # Kommandos
103 proc ::gui::beenden {} {
104 exit 0
105 }
106 proc ::gui::zeigeInfo {} {
107 tk_messageBox -icon info -type ok
108 -message "TkWetter,nein GUI zur Anzeige von Wetterdaten"
109 -title "TKWetter"
110 }
111 proc ::gui::setzeOrt {location_code ort} {
112 set ::tw(location_code) $location_code
113 set ::tw(ort) $ort
114 metar::update_data
115 }
116
117 # Daten einlesen
118 proc ::gui::formatData {liste} {
119 variable ddata
120 variable f
121
122 array set data $liste
123 parray data
124 set ddate(wetter) ""
125
126 foreach key [array names ddata] {
127 set ddata($key) ""
128 }
129
130 if {[info exists data(cond)]} {
131 set ddata(wetter) [join $data(cond) ", "]
132 }
133 if {[info exists data(wind,speed)]} {
134 set ddata(wind) "[format "%.1f kn" $data(wind,speed)],
135 [format %.1i¡ $data(wind,dir)]"
136 }
137 if {[info exists data(temp,air)]} {
138 set ddata(temp,air) [format %g¡C $data(temp,air)]
139 }
140
141 $f.luftdruck configure
142 -text [format "%.f mbar" [expr $data(pres) / 0.752790648]]
143 if {[info exists data(cloud,type)]} {
144 $f.wolken configure -text $data(cloud,type)
145 } else {
146 $f.wolken configure -text ""
147 }
148 }
149
150 gui::init
151 metar::update_data
152 # Für Debugging:
153 # set fd [open latest_data.tcl]
154 # set liste [read $fd]
155 # close $fd
156 # gui::formatData $liste
|

Abbildung 4: Weniger als 20 Tcl-Codezeilen genügen, um dieses PDF-Dokument zu erzeugen. Besonders praktisch: Pdf4tcl besteht selbst nur aus Tcl-Code und arbeitet damit plattformunabhängig.
Dessert
Die Prozedur »::gui::init« (Zeilen 93 bis 100) ist der Einstieg für die Oberfläche. Sie setzt den Titel des Fensters und seine Startgröße. Für diesen Teil des Fensters ist nicht Tk selbst, sondern der Windowmanager zuständig. Folglich werden diese Optionen nicht beim Hauptfensterwidget ».« konfiguriert, sondern mit dem getrennten »wm«-Kommando (Zeilen 95 und 96).
Etwa 150 Zeilen genügten, um eine Oberfläche für Wetterdaten zu schaffen. Das Programm nutzt nur einen kleinen Teil der Fähigkeiten von Tk, kann aber als gute Basis für eine eigene Entwicklung dienen. Wer bisher gezögert hat, eine Anwendung mit GUI zu entwickeln, sollte den Versuch wagen. Dank Tk wird ihm das schneller gelingen, als er vielleicht befürchtet. (fjl)
|
Infos |
|---|
|
[1] NOAA, National Oceanic and Atmospheric Administration: [http://www.noaa.gov] [2] Gimp Savvy: [http://www.gimp-savvy.com] [3] Wetteranzeige mit METAR, Meteorological Aviation Report: [http://adds.aviationweather.gov/metars/] [4] Tclweather holt METAR-Informationen und zeigt sie in einer eigenen Oberfläche: [http://tcl.jtang.org/tclweather/] [5] Icons für Tcl/Tk: [http://www.satisoft.com/tcltk/icons/] [6] Tile, Theming-fähige Widgets für Tk: [http://tktable.sourceforge.net/tile/] [7] Tcl-Interpreter Jim: [http://jim.berlios.de] [8] Tcl-Benutzertreffen: [http://www.t-ide.com/tcl2005e/tcl2005.html] [9] Tcl-Benutzertreffen 2004: [http://www.tcl.tk/community/tcl2004/] [10] PDF-Erweiterung für Tcl: [http://truckle.in-chemnitz.de/pdf4tcl/pdf4tcl.html] [11] Trampoline exportiert Canvas in PDF: [http://trampoline.sourceforge.net] [12] Freewrap 6.0: [http://freewrap.sourceforge.net] [13] Alle Dateien zum Download: [ftp://ftp.linux-magazin.de/pub/listings/magazin/2005/05/Federlesen/] |





