„Taschenrechner” (Tischrechner) mit GUI (2) für Expression Parser Klasse
Da wir das Modul mit der grafischen Oberfläche für unser Modul aus dem letzten Kapitel anpassen müssen, habe ich gleich noch zwei Verbesserung vorgenommen:
Das Ergebnis wird jetzt automatisch in die Zwischenablage gestellt. Da dies manche Benutzer sicherlich nicht wollen, ist es mittels einer Checkbox (heißt in Python Checkbutton) abschaltbar.
Python verlangt, dass man einen Dezimalpunkt verwendet (wie im angelsächsischen Raum üblich). Im Deutschen verwendet man allerdings das Dezimalkomma. Daher wird unser neues GUI-Modul es erlauben, dies umzuschalten. Man kann dann das Dezimalkomma verwenden, muss dann aber bei Funktionen, die mehr als ein Argument erwarten (z. B. atan2(y,x) [Python-Dokumentation - math functions]), das Semikolon statt des Kommas verwenden. Im folgenden Bild sehen Sie, wie unser Rechner am Ende dieses Kapitels aussehen wird:
Zunächst wieder der komplette Quellcode des Moduls, Erklärungen folgen danach. (Dank Plugin NppExport für Notepad++ jetzt mit Syntax-Hiliting (andere, gebräuchliche Schreibweise für das korrekte „HighLighting”); sind aber andere Farben/Stile als in Idle[!]):
Hinweis: Da sich an dem bisherigen existierenden Code relativ wenig geändert hat, werde ich hier nur die Anpassungen erklären, die notwendig sind, damit das Modul mit unserem neuen Parser-Modul funktioniert. Lesen Sie daher gegebenenfalls im Kapitel Tischrechner mit GUI nach. Die neu hinzugekommenen Teile für das Einfügen in die Zwischenablage und der optionalen Verwendung von Dezimalkomma werden natürlich ausführlich erläutert.
Nach den 3 Kommentarzeilen am Anfang kommt zunächst ein Python Dokumentationsstring (siehe vorhergehendes Kapitel). Bei den „import”-Anweisungen wurde das Modul re hinzugefügt, das kennen Sie schon aus dem vorhergehenden Kapitel. Die „import”-Anweisung zur Einbindung unseres Parsers wurde natürlich ebenfalls geändert. Aber auch das wurde schon bei der Kommandozeilenversion am Ende des vorhergehenden Kapitels erklärt.
Als nächstes legen wir zwei reguläre Ausdrücke an (floatPoint und floatComma). Die Muster dafür werden unten erklärt, wenn wir den jeweiligen Ausdruck zum ersten mal verwenden.
Da unser Parser jetzt in einer eigenen Klasse gesichert ist, müssen wir jetzt nicht mehr für alle Variablen die „komischen” Namen mit den Unterstrichen verwenden, können also z. B. win statt _win__ verwenden.
Neu ist der Teil ab der Zeile
autoIns=ttk.tkinter.IntVar(value=1)
Die Klasse ttk.tkinter.IntVar() ist ähnlich wie die Klasse ttk.tkinter.StringVar(), die wir bereits im Kapitel Tischrechner mit GUI kennengelernt haben, allerdings werden in einer IntVar statt Zeichenketten (Strings) Ganzzahlwerte (Integer) gespeichert. Beim Anlegen mit obiger Zeile rufen wir den Konstruktor der Klasse auf und übergeben gleich den Wert 1 an das Attribut value. Diese IntVar benötigen wir für die Checkbox, mit der man einstellen kann, ob das Ergebnis automatisch in die Zwischenablage gestellt werden soll. Wenn in der IntVar der Wert 1 gespeichert ist, ist die Checkbox angehakt (Ergebnis wird in die Zwischenablage gestellt) bei 0 nicht. Wie schon bei StringVar ändert sich der Wert von IntVar automatisch, wenn der Benutzer die Checkbox anklickt. Umgekehrt ändert sich der Status der Checkbox wenn man den Wert der IntVar (autoIns in unserem Programm) ändert.
Als nächstes legen wir mit
chkAutoIns=ttk.tkinter.Checkbutton(win,text="automatically insert result into clipboard", variable=autoIns) chkAutoIns.pack(anchor="nw",padx=5)
eine Checkbox (ein Objekt der Klasse ttk.tkinter.Checkbutton) an. „win” ist das Fenster in dem die Checkbox angezeigt werden soll, das Attribut text (eine String-Variable) zeigt die Beschreibung für diese Checkbox an. Das Attribut „variable” der Checkbutton-Klasse legt fest, ob die Checkbox angehakt ist oder nicht. An diese muss ein Objekt der Klasse ttk.tkinter.IntVar zugewiesen werden, wir weisen hier unsere oben angelegte Variable autoIns zu. Das folgende „chkAutoIns.pack(...) kennen Sie schon aus unseren vorherigen GUI-Programmen.
Danach legen wir auf ähnliche Weise eine Checkbox an, mit der eingestellt werden kann, ob wir Dezimalpunkt oder Dezimalkomma, falls angehakt, verwenden wollen (wie Sie sehen, können Sie lange Strings mittels des „+”-Operators verketten und so auf mehrere Zeilen verteilen).
Im folgenden benötigen wir eine Callback-Funktion (cbInNewSelection), die aufgerufen wird wenn der Benutzer einen History-Eintrag (aus der ausklappbaren Listbox der Combobox wählt). Dies ist notwendig, weil wir in der History alle Eingaben mit Dezimalpunkt und Komma als Argumenttrenner speichern. Wenn ein Eintrag aus der History gewählt wird müssen wir daher prüfen, ob die Checkbox für Dezimalkomma angehakt ist, und falls ja wandeln wir zunächst alle Kommas in Semikolons um und danach alle Dezimal-Punkte in Kommas um. Letzteres erledigen wir nicht wie beim Argumenttrenner für Funktionsargumente mit der replace() Methode für Strings, sondern mit Hilfe des oben angelegten regulären Ausdrucks „floatComma;” und dessen „sub()” Methode (für substitute, deutsch ersetzen). Das Muster für den regulären Ausdruck floatComma (siehe ziemlich am Anfang des Programmcodes) ist:
r"([0-9]*)(\.)([0-9]*)"
Das r vor dem String-Begrenzer (") bedeutet, dass es ein roher String ist (siehe im Kapitel Ein-/Ausgabe auf Bildschirm). Die 3 Klammerpaare stehen für 3 Gruppen (siehe z. B. hier). Die Gruppen sind fortlaufend nummeriert, beginnend bei 1. Noch nicht benutzt haben wir das spezielle Zeichen „*”, es bedeutet der vorhergehende Teil darf null-, ein- oder mehrmals vorkommen. Das „\.” in der zweiten Gruppe bedeutet, dass dort ein Punkt stehen muss. Der vorhergehende Backslash hebt die Sonderbedeutung des Punkts in einem regulären Ausdruck auf. Mit folgender Anweisung nehmen wir die Ersetzung vor:
hlp=floatComma.sub(r"\1,\3",hlp) # and convert point to decimal comma
Dies bedeutet: Nehme zuerst das gefundene Muster aus Gruppe 1 (\1 steht für Gruppe 1), füge dann ein Komma ein und hänge dann das gefundene Muster aus Gruppe 3 an. Das ganze muss auf den zweiten String-Parameter (hlp) der sub() Methode angewendet werden.
In der Callback-Funktion cbReturn hat sich auch einiges geändert. Nachdem wir mit „expr=cbV.get()” die Benutzereingabe geholt haben, testen wir zunächst mit „if useComma.get() == 1:” ob die Checkbox für Dezimalkomma-Eingabe angehakt ist und falls ja, wandeln wir zuerst alle Dezimalkommas in Punkte um. Dazu verwenden wir den regulären Ausdruck floatPoint und verwenden wieder die sub() Methode. Das funktioniert aber so wie oben die Umwandlung oben von Dezimalpunkt nach Komma. Das sollten Sie daher selbst nachvollziehen können. Danach wandeln wir noch die Semikolons zu Komma. Auch das funktioniert wie oben die Umwandlung von Komma zu Semikolons. Danach hat sich bis zum Aufruf der eigentlichen Berechnungsroutine nichts geändert, diese lautet nun:
rslt, err=EP.evaluateExpression(expr)
Wir rufen die statische Methode „evaluateExpression” aus unserer Klasse „ExpressionParser” (die wir in der import-Anweisung unter dem Namen EP verfügbar gemacht haben) auf. Danach testen wir wie in der ursprünglichen GUI-Version, ob ein Fehler aufgetreten ist, und geben diesen im Eingabefeld unserer Combobox aus. Hier hat sich nichts geändert.
Im else-Teil hat sich aber einiges geändert. Zunächst formatieren wir das Ergebnis (rslt) mit der „formatResult” Methode unserer „ExpressionParser” Klasse. Dann testen wir wieder ob die Checkbox für Dezimalkomma-Eingabe gesetzt ist (das Ergebnis wird immer mit Dezimalpunkt ausgegeben) und wandeln es, bevor wir es im Eingabefeld unserer Combobox ausgeben um (Dezimalkomma und Semikolon als Argument-Trenner). Dies funktioniert aber genauso wie oben in unserer Callback-Funktion „cbInNewSelection”.
Nachdem wir das formatierte (und evtl. umgewandelte) Ergebnis in die Eingabezeile unserer Combobox ausgegeben haben - mit cbV.set(...) - testen wir in der folgenden if-Anweisung, ob das Ergebnis automatisch in die Zwischenablage (Clipboard) gestellt werden soll, damit man es (ohne es erst markieren zu müssen und mit Strg-C kopieren muss) in einem anderen Programm (z. B. einem Textverarbeitungsprogramm) sofort mit Strg-V einfügen kann. Dazu leeren wir zuerst die Zwischenablage mit win.clipboard_clear() (win ist unser Fenster). Dann hängen wir den Inhalt aus dem Eingabefeld unserer Combobox mit win.clipboard_append(...) an die (jetzt geleerte) Zwischenablage an. Das abschließende win.update() sorgt dafür, dass der Inhalt der Zwischenablage auch erhalten bleibt, wenn man das Programm beendet. Leider hat Python/Tkinter hier zur Zeit einen Bug, siehe stackoverflow. Wenn Sie einen Ausdruck berechnen und dann sofort unseren Rechner beenden, steht das Ergebnis nicht in der Zwischenablage. Wenn Sie es aber vor dem Beenden irgendwo einfügen, dann bleibt das Ergebnis in der Zwischenablage.
Danach entfernen wir wie in der ursprünglichen Version eine eventuelle Markierung im Eingabefeld der Combobox und setzen den Cursor (die Schreibmarke) an das Ende, damit die Benutzerin sofort mit dem Ergebnis weiter rechnen kann. Zuletzt binden wir die Eingabetaste an unsere obige Callback-Funktion cbReturn und lassen unseren jetzt sehr komfortablen Tischrechner ("Taschenrechner") mit win.mainloop() laufen. Aber das kannten Sie ja schon.
Wie Sie sehen, sind beide Module unseres Programms deutlich umfangreicher geworden. Allerdings ist der Parser jetzt sehr einfach und komfortabel zu benutzen (z. B. müssen wir bis auf wenige Ausnahmen keine Namen mit Unterstrichen verwenden). Siehe hierzu auch das Kommandozeilen Testprogramm aus dem vorhergehenden Kapitel, welches erstaunlich kurz ist. Die Leistungsfähigkeit und Bedienerfreundlichkeit unserer Oberfläche haben deutlich zugenommen. Wie schon bei der ersten grafischen Version empfehle ich Ihnen, den Großteil der Kommentare zu löschen. Wenn Sie sich dann den Code ansehen, wird er Ihnen viel übersichtlicher erscheinen.
Damit schließe ich das Tischrechnerbeispiel erst mal ab. Als Erweiterung können Sie versuchen, ähnlich wie bei meinem Coca, eine virtuelle Tastatur hinzuzufügen, um den Tischrechner direkt über Touchscreen bedienen zu können. Für Literale (Buchstaben und Ziffern) sollte das nicht allzu schwierig sein, verwenden Sie die Button Klasse von Tkinter für die Tasten. Lesen Sie in der offiziellen Python-Dokumentation welche Methoden eine Combobox unterstützt. Bei den Cursor-Tasten (nur Vor und Zurück wird benötigt), der (den) Löschtaste(n) und der Eingabetaste, könnte es eventuell etwas schwierig werden. Wenn ich mal wieder Zeit habe, werde ich das evtl. mal hier ergänzen.
Ansonsten werde ich hier demnächst noch ein abschließendes Kapitel bringen, mit allgemeinen Sachen zu Python, wie diese kleine Einführung benutzt werden kann und Literatur-Hinweisen bzw. Links auf andere Seiten. Schauen Sie also ab und zu mal wieder vorbei. (Mittlerweile habe ich einige Ergäzungen zum Rechner hinzugefügt, einfach für mich selbst, um diverse Sachen in Python zu testen. Ich hoffe ich finde mal Zeit, das hier einzufügen.)
Last but not least bedanke ich mich dafür, dass Sie diese kleine Einführung gelesen (und hoffentlich verstanden) haben.
Mittlerweile gibt es einen weiteren Abschnitt: Python für Android. Da gibt es aber nichts neues zu Python, sondern es wird eine Entwicklungsumgebung (IDE) vorgestellt, mit der Sie direkt auf Ihrem Android-Smartphone Programme erstellen können. Unseren Tischrechner (der auf einem Smartphone ein Taschenrechner ist), habe ich damit „zum Laufen bekommen” :-)