|
|
Wir machen weiter |
Zur Kursübersicht |
|---|
|
|
Auf jeden Fall sollten wir den Komfort verbessern. Unter Linux kann z.B. mit dem Modul curses,bzw unter Windows mit dem Modul WConio die shell so verbessert werden, dass nach Auswahl eines Kommandozeichens nicht mehr hintendran immer Enter gedrückt werden muss, sondern dass wir nach Betätigung der Taste des jeweiligen Zeichens sofort in die gewünschte Funktion springen. Außerdem kann der Bildschirm besser ausgenutzt werden, indem wir Stringausgaben direkt positionieren können. Damit wäre z.B. auch eine zweispaltige Ausgabe der ToDo's möglich, weil die Liste ja mit der Zeit immer länger wird. Oder es wird möglich, mit Tastenfunktionen auch im Textmodus innerhalb der Liste zu blättern (eine mehrspaltige Liste geht natürlich auch im scroll-Modus, wenn man die Zeilen vor der Ausgabe mit Stringoperationen zusammenbaut. Mit einer Möglichkeit der Kursorpositionierung läßt sich das aber leichter organisieren). Noch ein Hinweise zur
Portierbarkeit von Programmen, die eine Kursorsteuerung
benutzen. Programme, die eines der o.g. schönen Module
einsetzen, laufen dann leider nur auf dem jeweiligen
Betriebssystem. Mit etwas Anstrengung kann man allerdings das
Programm dazu bringen, während der Laufzeit eine Abfrage zu
machen, welches Betriebssystem gerade läuft. Das z.B. geht so:
import sys
s = sys.platform if s = 'win32': print 'Windows' Von der Antwort abhängig könnte dann das passende Modul benutzt werden. Wer wegen angespeichertem Frust ohnehin kein Windows benutzt, hat es nun schön. Wer lieber portable Programme schreiben will, sollte hier erstmal weiterlesen, um mehr über Tkinter zu erfahren. Tkinter läuft nämlich verschiedenen Betriebssystemen und erfordert solche Umwege nicht. Mit den bis hierher möglichen
Verschönerungen unserer shell sind wir noch nicht am
Ende. Es besteht nämlich die
Möglichkeit, mit Tkinter eine kleine grafische
Oberfläche auf unsere ToDo - Liste draufzulegen. Dann kann
unser Programm, sofern die Tk - Umgebung richtig und vollständig
installiert wurde, unter Windows oder unter Linux laufen! Als Grundlage
wollen wir das bereits erarbeitet ToDo - Modul verwenden, da es
die organisatorischen Funktionen für die ToDo - Liste schon
bereitstellt. Später werden wir uns daran erinnern und Funktionen
und Module gleich so programmieren, dass sie ohne große Probleme
in solche Oberflächen nachträglich eingebunden werden
können :-)
Einige Überraschungen vorher, die uns Tkinter bereitet Bei ersten Versuchen mit Tkinter ist mir natürlich wieder so ein "unbegreiflicher Knüller" widerfahren! Bei Tkinter werden ja die einzelnen grafischen Komponenten sauber definiert und aus der Tk - Bibliothek abgeleitet. Dabei ist einem Button u.a. auch das Kommando zuzuweisen, das auszuführen ist, wenn er betätigt (gedrückt) wird. In dem Beispiel aus einem Einsteigerbuch war alles klar. button = Tkinter.Button(frame, text='exit', command=tk.destroy) Hier ging es um einen Befehl, der das Programm beenden sollte. Da kann es doch nicht so schwer sein, ein kleines Kommando selbst zu schreiben und das mit Drücken des Button auszuführen :-) Weit gefehlt! Es ist nämlich tückisch! def myaktion(): und etwas weiter unten im Quelltext... button = Tkinter.Button(frame, text='next', command=myaktion()) Und es ging natürlich
nicht! Und es kam auch keine Fehlermeldung! Was tun? Etwas
Glück und Intuition braucht der Programmierer eben,
denn was sollte ihm sonst noch helfen können? Nach
einigen Probieren kam ich dahinter. Die Zeile button = Tkinter.Button(frame, text='next', command=myaktion) Das heißt, es wird hier
nicht die Funktion erwartet, sondern nur der Name der
Funktion! Eigentlich ganz einfach, aber es stand nirgens. Eine andere Art von Überraschung wurde mir von Fritz Cizmaorv übermittelt. Der folgende Codeschnipsel wollte einfach keine Grafik innerhalb eines canvas-Objekts anzeigen: def ShowImageAt(x, y,
imagePath): Mit folgender kleiner Änderung läufts: def ShowImageAt(x, y,
imagePath): Im ersten Falle ist myImage eine lokale Variable
von AddImage, und Da kann man nur sagen, kleine Änderung,
grosse Wirkung! Vielen Dank an Fritz, für diesen
Hinweis. Nun aber mal weiter mit unserem Übungsbeispiel Es soll die grafisch orientierte Oberfläche
über unser Übungsbeispiel drübergelegt
werden. Also müssen wir unser Modul todo.py nun
importieren. Der Aufruf der shell, die es bisher getan hat,
unterbleibt nun, weil der Programmabschnitt im bisherigen
Modul, der ganz unten steht, nur dann aktiv wird, wenn das Modul
als __main__ gestartet wird. Da todo.py hier nicht das
Hauptmodul sein wird, wird es nur als Sammlung vorbereiteter Funktionen
eingesetzt. Pascal - Programmier erinnern sich an die Units, die
oft als Funktionssammlung für bestimmte Aufgaben dienten.
Na das sieht doch schon viel besser aus! Hier ist ein Beispiel gezeigt, wie es unter Linux aussieht. Unter Windows sieht die Oberfläche, abgesehen von der optischen Gestaltung, je nach Einstellung, ähnlich aus (Zumindest sind die Bedienelemente an der gleichen Stelle). Zuerst zur Bedienung: Nach Start des Programms wird die zuvor abgespeicherte Liste gezeigt. Soll ein Eintrag gelöscht werden, muss er zuvor mit Klick markiert werden. Unter markiert verstehen wir die Hervorhebung mit dem Farbbalken und die Unterstreichung der betreffenden Zeile in der Liste. Wer genau hinsieht bemerkt, dass die Farbhervorhebung nach dem Drücken der linken Maustaste, aber die Unterstreichung erst nach dem Loslassen der Maustaste nachrückt. Dies ist ein kleiner Trick, weil diese beiden Markierungsarten getrennt gesteuert werden. Nun erscheint oben neben dem Label "Markiert:" in dem Editierfeld, das vorrangig für Eingaben dient, die markierte Textzeile als Wiederholung. Zum Hinzufügen wird einfach in das Editfeld hineingeklickt, wonach es leer wird.Nachdem man den neuen Eintrag eingetragen hat, wird er durch <Enter> in die Liste übernommen. Auf einen gesonderten Button zum Beenden wurde verzichtet, da dies mit der Markierung "X" in der rechten oberen Ecke des Fensters erfolgt. Mit dem Button Herauf- oder Herabsetzen kann ein markierter Eintrag seine Stellung in der Liste verändern. Dadurch kann eine besondere Behandlung von Prioritäten in der Liste entfallen. Überhaupt ist alles so einfach wie möglich gehalten. Zum einen, damit man es zum lernen nicht zu kompliziert hat, aber zum anderen, damit es in der Praxis möglichst aufwandsarm einsetzbar ist. Deshalb ist auch kein zusätzliches Menue angefügt. Dies läßt sich in Tkinter übrigens auch prima machen, wurde aber hier auch zur Vereinfachung weggelassen. Um diese Erweiterungen gegenüber der Variante im Textmodus auch von der Funktion her zu realisieren, habe ich im Grundmodul todo11.py noch die Funktionen "herauf( )" und herunter() dazugeschrieben, die dadurch auch unter der shell des Moduls todo11.py ansprechbar sind. Dazu wurde auch die Hilfe - Funktion erweitert, damit der Nutzer von dieser Möglichkeit auch erfährt. Die wiederholte Ausführung der Funktion ist unter der shell aber wesentlich umständlicher, als in der grafischen Oberfläche. Die Bearbeiten - Funktion holt einen Eintrag aus der Liste (wie Löschen) und trägt ihn als Voreinstellung in das Editierfeld. Hier kann nun daran rumgeändert werden, bis er mit <Enter> dann wieder in die Liste geschoben wird. Das Löschen eines markierten Eintrags
erklärt sich selbst. Zwei Besonderheiten noch: Nun schauen wir uns die Programmierung näher an: (Listing) Wir wollen zum Beispiel erreichen, dass wir zum Beenden der Editierfunktion einfach Enter drücken. Dazu müssen wir noch ein Binding einfügen, das für das Entry-Widget eine Reaktion auf "<Return>" organisiert. Die dazu passende Funktion (mit den Parametern self und event) heißt hier xrein(), die nichts weiter macht, als die Funktion self.rein() aufzurufen. Es funktioniert dagegen nicht, in der Funktion bind gleich die Funktion aufzurufen, die die Arbeit macht, weil diese den Parameter event nicht erwartet. Daher ist dieser kleine Umweg erforderlich. Nun noch eine Eigenschaft des Listbox - Widgets, die mir recht viele Sorgen machte. Das Markieren eines Eintrages erfolgte optisch zwar nach dem ersten Mausklick, aber die Übernahme in das Editierfeld kam erst nach dem zweiten Klick. In der ersten Version habe ich das Problem erstmal umgangen, indem ich die Funktion der Markierungsübernahme an das Ereignis Mausdoppelklick mit der linken Maustaste (<Double-Button-1> gebunden habe. Aber die Bedienung wurde hierdurch beschwerlich. Nach längerem Herumprobieren fand ich die Lösung, die natürlich wieder nirgendwo stand! Die Markierung des Eintrags in der Liste erfolgt durch die Betätigung der Maustaste automatisch (das wird durch die Listbox erledigt). Dadurch kann der Eintrag nicht rechtzeitig erkannt werden, weil die Markierung mit Ablauf der Funktion, die an dieses Ereignis gebnunden wurde, nicht vollständig abgeschlossen ist. Es erschien stattdessen immer der Eintrag aus der vorigen Markierung, also immer eine Zeitphase zu spät. Erst durch den zweiten Mausklick wurde es dann glattgezogen, aber von den zwei Klicks wollen wir ja gerade weg. Dabei ist die Lösung ganz einfach, aber man muß erstmal drauf kommen. Nun habe ich das Ereignis so gebunden, dass die Funktion eben mit dem Loslassen der Maustaste (<ButtonRelease-1) abläuft. Und nun geht es. Das Drücken der Maustaste bringt die Markierung, nach dem Loslassen geht der markierte Eintrag brav in das Editfeld. Nun stört natürlich noch, dass es nur
mit der Maus geht. Jedes ordentliche Programm lässt
sich natürlich auch mit der Tastatur bedienen! Hier
gilt das Gleiche, das Ereignis funktioniert, wenn es an das
Loslassen der Taste gekoppelt wird (<KeyRelease>).
Zugleich haben wir damit probiert, was eigentlich passiert, wenn an die
Listbox die gleiche Funktion zweimal mit unterschiedlichen
Ereignissen gebunden werden. Es geht einfach! Jeweils das Ereignis,
das eintritt (welches auch sonst :-) ) startet die Funktion. Noch
ein Nachtrag zur Tastaturbedienung. Nach dem Start wird , sofern
die Liste nicht leer ist, der oberste Eintrag aktiviert, was daran
zu sehen ist, dass er unterstrichen dargestellt wird. Drückt
man hier die Leertaste, dann wird aus dieser "Vormerkung"
eine vollwertige Markierung. Der Eintrag erscheint im farbigen Balken.
Tut man das nicht, kann man mit den Kursortasten auf und ab, die
Vormerkung verschieben. Der Unterstrich wandert in der Liste, je
nach Steuerung. Drückt man dann die Leertaste, wird der jeweil
zuletzt vormarkierte Eintrag markiert. Solange der Unterstrich bewegt
wird,bleibt die Markierung, die zuvor festgeelgt wurde, erhalten.
Das heißt, der Farbbalken bleibt stehen und der Unterstrich
wandert weg. Nach Drücken der Leertaste wird wieder glattgezogen,
beide zeigen auf den gleichen Eintrag.
Inzwischen gibt es eine neuere Version, die beide Methoden der Markierung (mittels Kursortasten sowie mit Mausklick) synchron behandelt. Das heist, wenn ich mit der Maustaste einen Eintrag markiere, wird sowohl der farbige Balken gesetzt, der nur durch die Maus gesteuert wird, sowie die Unterstreichung des Eintrages, die wiederum durch die Kursortasten bewegt wird. Beide Markierungen stehen nuin immer auf dem gleichen Eintrag. Das gilt dann auch nach dem Hochsetzen eines Eintrages, wie auch nachdem ein Eintrag gelöscht wurde. Ich hoffe, es klappt nun wirklich immer. Sollten immer noch Situationen erzeugt werden können, die beide Markierungen auseinanderbringen, bitte ich um kurze Mitteilung. :-) Natürlich reicht es auch, sich allein die
letzte, jeweils vollkommenste Version anzusehen, die dann
"relativ" perfekt ist. Aber die mühseligen kleinen
Schritte (beide Versionen unterscheiden sich nur durch eine
einzige Zeile) werden von mir deshalb so kleinlich
dargestellt, damit man sehen kann, wie ein Programm nach
den ersten Erprobungen dann schrittweise verbessert wird. Der kleine Restmangel besteht darin, dass es nach dem Höhersetzen eines untenstehenden Eintrages mitunter passieren kann, dass der markierte Eintrag danach nicht mehr im sichtbaren Bereich liegt, der in Abhängigkeit der Stellung des Rollbalkens angezeigt wird (sofern die Liste länger als der sichtbare Bereich ist). Wie man sieht, müssen für diesen Fehler mehrere Voraussetzungen gleichzeitig eintreffen. Das ist nicht allzu oft, aber wenn es eintritt, sieht das ganze doch recht stümperhaft aus. Daher brauchen wir einen Befehl, der die Lage des sichtbaren Bereiches kontrolliert! Mit listbox1.see(index) wird aus einem angenommenen Widget namens "listbox1" der Eintrag mit der Position "index" in den sichtbaren Bereich gebracht. Der entsprechende Befehl self.fenster.see(self.marked) wurde in der Methode GUINotes.hoch( ) eingefügt. Dadurch wird nach dem Hochsetzen eines Eintrages die markierte Zeile zwangsweise den sichtbaren Bereich gebracht. Das Modul ToDo.py zum TKinter -
Beispielprogramm ist auch selbständig lauffähig,
indem es die dort eingebaute shell( ) nutzt. Das Menue
dieser Shell ist jetzt durch die Funktion "Heraufsetzen"
erweitert worden, die auch schon im TKinter - Beispiel funktionierte.
Die hierzu erforderliche Funktion war in ToDo.py schon enthalten,
wurde aber von der eingebauten Shell bisher nicht benutzt, weil
"herauf(posi) eine später nachgebesserte Funktion war.
Das nun erweiterte Modul erhält die Versionsnummer 1.0 und
ist ebenfalls in TkTodo4x.zip enthalten.
Wenn man sich den Quelltext insgesamt mal anschaut fällt auf, dass die Variable "self.marked", die die Position des markierten Eintrages enthält, mitunter auf "None" gesetzt wird. Das ist der Zustand des Programms, bei dem auf einen neuen Eintrag gewartet wird. Dazu ist das Eingabefeld focussiert und in der Liste der Einträge ist kein Eintrag markiert. Nachdem der neue Eintrag ganz oben in der Liste eingefügt wurde, ist dieser erst mal markiert. Diese Lösung habe ich deshalb gewählt, weil beim Durchblättern der Liste jeweils der markierte Eintrag in dem Editierfeld oben mit gespiegelt werden sollte. Das dient dazu, den markierten Eintrag auffällig zu kennzeichnen, denn es ist der Eintrag, der z.B. mit dem Kommando "Entfernen" dann wirklich entfernt wird. Man könnte aber auch das Eingabefeld tatsächlich nur als Eingabe verwenden oder bei einer Eingabe statt "None" immer den obersten Listeneintrag markiert halten. Es ist hier eben wie überall im Leben, sowie in der Programmierung. Es gibt oft unzählige Möglichkeiten. Inzwischen bin ich noch auf etwas anderes aufmerksam geworden. Wenn man nämlich die Bild auf oder ab Taste drückt, geschieht auch etwas. Der farbige Markierungsbalken bleibt zwar stehen, aber nicht die Unterstreichung der markierten Zeile. Beide Markierungen hatte ich mühselig synchronisiert, solange man mit den Kursortasten die Markierung hoch und runter bewegte. Nun gibt es wieder ein Auseinanderlaufen der beiden Markierungen. Nach dem Drücken von Bild auf oder Bild ab springt die Unterstreichung nämlich in die erste Zeile. Die Farbmarkierung bleibt an der alten Stelle. Nach einigem Probieren habe ich nun herausgefunden, dass der gesetzte Wert (Index) für den markierten Eintrag mit dem Drücken dieser Tasten verlorengeht. Das ist wahrscheinlich dafür nützlich, wenn es bedeutend mehr Einträge gibt, als im sichtbaren Bereich dargestellt werden können. Hier stört es sehr. Daher hier ein Update des TkToDo, das diesen Effekt unterdrückt. Für alle, die es nicht gleich zur Hand haben: Hier ist noch das dazu passende Notizmodul zu finden. Das Update ist jetzt so programmiert, dass mit den Page - Tasten auf den ersten bzw. letzten Eintrag der Liste gesprungen wird. Man kann natürlich auch andere Schrittweiten vorgeben. Zugleich wurden noch einige Zeilen hinzugefügt, damit das Entfernen eines Eintrages künftig auch mit der "Del" - Taste geht. Ferner habe ich einige Kommentare dazugeschrieben, um den Aufbau des Programms etwas transparenter zu machen. Es gibt noch einen klitzekleinen
Schönheitsfehler, den ich bisher nicht beheben konnte.
Da die Page - Tasten die Markierung eines Eintrages direkt
beeinflussen, kommt es beim Drücken von Page-Down
dazu, dass die Unterstreichung nach Niederdrücken nach oben
springt, um dann nach dem Loslassen aber auf die gewünschte
Position zu gelangen. Bei Page-Up stört das nicht, weil der
Eintrag ohnehin nach oben soll. Wenn man die Fenstergröße
veränderte, sah alles nicht mehr schön aus, die
Widgets hingen in unveränderter Größe
irgendwo auf der Fensterfläche herum. Da es ohnehin
Schwierigkeiten beim Arbeiten mit längeren Textzeilen
gab, wurde nun ein waagerechter Scrollbalken dazugepackt und das
Fenster der Listbox variabel in der Größe gemacht. Es
paßt sich nun bei Änderung des Fensters mit an. Erreicht
wird das durch den Parameter expand = 1 im pack-Befehl. Natürlich
muß der Frame, wo die Listbox drin liegt, ebenso variabel
gemacht werden, sonst beengt er die Listbox bei einem
Vergößerungsversuch.
Auch hier besteht wieder ein kleiner Restmangel, der bei der nächsten Version abgestellt wird. Wenn man nämlich das Bearbeiten eingeleitet hat, steht ein Kursor im Editfeld. Dieser kann mit den Kursortasten an die gewünschte Stelle bewegt werden, um dann dort den Text zu behandeln. In dieser Phase darf man vorerst nicht mit dem Mauszeiger auf eine neue Stelle im Text klicken, um vielleicht den Kursor dorthin zu bewegen. Noch ist diese Situation nicht organisiert, so dass das Programm das für den Wunsch einer Neueingabe hält und den Text löscht. Ich werde aber kontextabhängig das so abfangen, dass das Editieren weitergeht, bis wieder <Enter> gedrückt wird. Übrigens, wer ein Programm nach Fertigstellung mal an Nichtprogrammierer zur Nutzung weitergeben möchte, hat bestimmt keine Lust, dem Anderen jedesmal die komplette Python - Entwicklungsumgebung mit draufzuspielen. Um dem abzuhelfen, gibt es für Windows das Modul py2exe. Damit kann man eine Handvoll Files erzeugen,die aus dem Hauptprogramm als *.exe - Datei sowie einigen Lib's besteht. Dieses Modul ist hier zu finden: Python Resources. Übrigens gibt es neue Hinweise zur Handhabung des Programms. Das Programm speichert die aktualisierte Liste immer dann ab, wenn mit dem "beenden" - Button das Programm geschlossen wird. Das ist ja schön, weil nach dem Neustart dann wieder alles vorhanden ist. So war es auch gedacht. Aber wer ganz in Gedanken das Programm beendet, indem er das Fenster schliesst (durch Drücken auf das Symbol "X"), der verliert seine aktuellen Änderungen, weil die Speicherfunktion nicht mehr wirksam wird. Das muss also auch noch geändert werden! Nach einer Rückfrage in der äußerst hilfreichen deutschsprachigen Python - Mailingliste wurde folgende Lösung für dieses Problem gezeigt: self.wm_protocol('WM_DELETE_WINDOW', self.quit) Dabei handelt es sich um einen Befehl zum
Zusammenwirken mit dem Windowmanager. Die Funktion
self.quit ist dann die selbstgeschriebene Funktion, die
anstelle dem voreingestellten Schließen der Anwendung
abgearbeitet wird. Daher muss unsere eigene Funktion natürlich das
Schließen der Anwendung mit realisieren. Ich habe es in
unserer ToDo-Liste aus Vereinfachungsgründen so gelöst,
dass damit die bereits vorhandene Funktion "beenden mit speichern"
gestartet wird, die für den beenden - Button vorgesehen ist.
Übrigens gibt es hier noch einen kleinen Fallstrick!! Wir sind
ja bisher gewohnt, dass an bestimmte Ereignisse gebundene Funktionen
die Übergabeparameter (self, event) enthalten. Nicht so bei
dieser. Hier wird event nicht mit übergeben, sonst kommt eine
Fehlermeldung. Anscheinend benötigt die Funktionssammlung für
den Windowmanager das nicht. Man muss es eben nur wissen! Es wurden 2 kleine anderweitige Verbesserungen
gleich mit eingebracht. Die erste beseitigt einen Fehler,
der immer dann auftrat, wenn man alle Einträge
einschliesslich dem letzten herauslöschte. Die zweite
betrifft den beenden - Button, der durch seine Beschriftung
nun klarstellt, dass er "speichern und beenden"
bewirkt. Wie lange wollen wir denn nun noch an diesem kleinen Beispiel rumfummeln? Denn eigentlich sollte das hier ja ein Programmierkurs für Python sein. Ja, stimmt und es wird an dieser Stelle auch nicht mehr lange gehen, von kleinen Verbesserungen, die es immer mal gibt, einmal abgesehen. Aber daraus ist zu sehen, dass man Wissen aus mehreren Gebieten benötigt, um komplette Programme zu fertigen. Die Hauptsache: Man muß natürlich die Programmiersprache kennen (hier Python). Aber dann geht es schon los mit der grafischen Oberfläche. Egal, welche man wählt, es hängen immer Bibliotheken dran mit Funktionen, die man kennen muss (hier Tk). Aber dann fehlt eben immer noch was, nämlich die Sache mit einer ergonomischen Bedienoberfläche (hier einer in sich möglichst widerspruchsfreien Window-Philosophie). Und da gibt es allerlei zu beachten, wie eben an unserem Beispiel zu sehen ist. Trotzdem möchte ich für die ToDo - Liste nun langsam die Zielgerade einläuten, damit bald mal Zeit bleibt, sich einem anderen Thema zuzuwenden. Also hier erstmal die nächste, vorläufig letzte Portion von Veränderungen/Verbesserungen. Diese sind durch Hinweise von Nutzern entstanden, die unser kleines Beispielprogramm praktisch nutzen, aber selbst nicht programmieren - also richtige Anwender! In der nun vorliegende Version 4.7 sind folgende Veränderungen eingebracht worden:
|