Aus Linux-Magazin 05/2005

Erste Schritte: Eigene Tk-Anwendungen entwickeln

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«.

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
Optionen

 

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.

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.

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-Anwender

Michael 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
GUI

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.

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/]

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben