Programm mit CLI und GUI

Plattformunabhängige GUIs mit wxWidgets.
Antworten
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Ich möchte ein Programm schreiben, welches über Konsole (CLI) und GUI bedient werden kann. Ich hab keinen Schimmer, wie man das am besten verkuppelt.

Ich dachte daran, die GUI auf mein Programm aufzusetzen und die CLI als minimale Steuereinheit direkt einzubauen. Nun teilt das Programm zur Laufzeit einige Informationen mit. Auf der Konsole könnte man das einfach 'printen'. Die GUI aber sollte das fein aufbereitet darstellen (Textfelder, bunte Icons, etc. -> ihr wisst schon).

Frage: Wie wird das i.d.R. gemacht? Parsed die GUI einfach nur die Konsolenausgabe oder gibt's da eine elegantere Schnittstelle?

Ich hatte überlegt, ob ich nicht einen XML-RPC-Server in das Programm einbaue, damit die GUI stets den Status bzw. gezielt Infos abfragen kann. Irgendwie ist das aber wie mit Kanonen auf Spatzen zu schießen, oder? V.a. wird über Sockets kommuniziert, was vielleicht gar nicht sein müsste.
Jan-Peer
User
Beiträge: 166
Registriert: Dienstag 2. Oktober 2007, 10:55

Du solltest generell alle Aus- und Eingaben aus dem eigentlichen Programm heraushalten. In deinem Fall schreibst du also zwei Benutzeroberflächen (grafisch und nicht-grafisch), die beide auf deinem Grundprogramm aufsetzen. Das Grundprogramm verarbeitet Daten und stellt Verbindungen zu anderen Quellen/Programmen (z.B. Datenbanken) her. Die Oberflächen kümmern sich ausschließlich um die Ein- und Ausgabe der Daten.
noise
User
Beiträge: 62
Registriert: Donnerstag 7. Februar 2008, 00:15

droptix hat geschrieben: Frage: Wie wird das i.d.R. gemacht? Parsed die GUI einfach nur die Konsolenausgabe oder gibt's da eine elegantere Schnittstelle?
Ich weiß nicht wie das ansonsten generell gemacht wird, aber ich finde den Ansatz von docutils sehr nett :) Studiere einfach mal den source code.

Ansonsten stimme ich grundsätzlich Jan-Peer zu, das man denn eigentlichen Programmcode (Core, logic, wie auch immer), von dem UI trennen sollte. Aber, das ist zwar bei docutils nicht ideal gelöst (die CLI ist nämlich ein teil des cores und mit den anderen Sachen verwoben), aber dennoch finde ich deren Ansatz sehr erfrischend.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Die komplette Trennung hatte ich auch schon mal im Sinn, aber eine UI würde ich sowieso eingebaut mitliefern. Und das würde vermutlich die CLI sein…

Mir stellt sich trotzdem noch die Frage, wie Programm und UI miteinander kommunizieren, wenn sie getrennte Einheiten sind. Ist der Weg über Sockets (also RPC) grundsätzlich empfehlenswert?

Docutils -> das ist haufenweise Code :roll: Wo genau stehen denn die interessanten Ansätze?
Jan-Peer
User
Beiträge: 166
Registriert: Dienstag 2. Oktober 2007, 10:55

Nein, Sockets brauchst du an dieser Stelle in der Regel nicht.

Stell dir das Grundprogramm als ein Objekt vor, das über Methoden verfügt, die Daten entgegennehmen und weiterverarbeiten, oder auf Anfrage zur Verfügung stellen. Beispiele: Eine Methode liefert eine Liste aller verfügbaren Artikel, eine andere nimmt eine Liste von Kundennummern und erstellt Bettelbriefe.

Das UI (egal ob Console oder Grafisch) bekommt bei der Initialisierung einen Verweis auf das Grundprogramm-Objekt übergeben und kann diesen nun nutzen, um die verschiedenen Hebel (Methoden) des Grundprogramms zu bedienen. Das UI sammelt also Daten vom Nutzer, stellt sie in geeigneter Form zusammen und übergibt sie dann an das eigentliche Programm. Umgekehrt genauso: Das UI will alle Artikel ausgeben, also ruft es die entsprechende Methode des Grundprogramms auf, erhält eine Liste, bereitet die Daten so auf, daß es sie darstellen kann - und tut es. Der Vorteil dieses Ansatzes ist, daß du die UIs sauber abkoppeln und gegen andere austauschen kannst.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Achso meint ihr das. Ja das ist prinzipiell klar. Dann meinte ich das ein wenig anders und hab mich umständlich ausgedrückt…

Stellt euch vor mein Programm läuft und verarbeitet Daten. Nun möchte ich z.B. wissen, wie sein Bearbeitungsfortschritt ist. Dazu besitzt das Programm eine Methode get_progress(), die einen Wert zwischen 0 und 100 (%) liefert. Ich könnte das jede Sekunde in der Konsole hinschreiben… das wäre aber störend.

Also muss ich das stattdessen über ein UI abfragen. Wie kann das UI nun den Bearbeitungsfortschritt abfragen? Da das Programm vor dem UI lief, hab ich keinen Zugriff auf STDOUT, richtig? Sonst könnte sich das UI ja per Pipe drauf hängen und horchen… ggf. den Output parsen und entsprechend ausgeben, im CLI oder dem GUI.
Jan-Peer
User
Beiträge: 166
Registriert: Dienstag 2. Oktober 2007, 10:55

Wie liefert get_progress() diesen Wert denn zurück? per print oder per return?
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Was macht das für einen Unterschied?

Ich würde sagen return.
Jan-Peer
User
Beiträge: 166
Registriert: Dienstag 2. Oktober 2007, 10:55

Dann verstehe ich dein Problem nicht. Das UI ruft - timergesteuert oder auf Anforderung (z.B. Mausklick) - die Methode get_progress() auf der ihm bekannten Instanz des Grundprogramms auf und hat damit den aktuellen Wert, den es dann nur noch darstellen muß.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo droptix!

Jetzt kommt es darauf an was du wirklich haben möchtest.

Fall 1: Möchtest du ein Programm schreiben, welches entweder als Kommandozeilenprogramm **ODER** als GUI-Programm gestartet werden soll?

Fall 2: Oder willst du ein Programm schreiben, welches immer als Kommandozeilenprogramm gestartet werdensoll. Will man dann noch ein GUI-Programm, dann wird dieses als eigenständiges Programm gestartet? Dann laufen also zwei Programme. Einmal das Kommandozeilenprogramm und einmal das GUI-Programm. Das Kommandozeilenprogramm ist das Hauptprogramm und das GUI-Programm soll die Daten vom Kommandozeilenprogramm abgreifen.

Im jedem Fall tust du gut daran, einzelne Teilbereiche deines Programmes in Module auszulagern. Alles in einen Projektordner zusammengefasst. Durch das Auslagern der Teilbereiche kannst du einmal ein Kommandozeilenprogramm und einmal ein GUI-Programm schreiben. Beide greifen auf die ausgelagerten Module zu. -- Logik in Untermodule -- Kommandozeilenoberfläche als Hauptmodul -- GUI als Hauptmodul.

Beim Starten des Programms wird also bereits entschieden, ob die Meldungen in der Konsole oder im GUI-Programm ausgegeben werden. Ein Mischen ist nicht möglich und wahrscheinlich auch nicht notwendig. Das Programm wird dann entweder auf die eine oder auf die andere Art gestartet.

Der zweite Fall wird komplizierter. Zuerst gilt auf jeden Fall wieder die Aussage mit dem Auslagern einzelner Bereiche in Module. So bleibt das Programm erweiterbar.

Das Kommandozeilenprogramm muss dann aber als Server gestartet werden. Und du kommst nich um eine Kommunikation über XMLRPC oder ähnliche Kommunikationswege herum.

Das GUI-Programm wird danach gestartet und verbindet sich zum Kommandozeilenprogramm. Das GUI-Programm ruft alle paar Sekunden das Kommandozeilenprogramm an und fragt nach ob es neue Meldungen gibt. Das Kommandozeilenprogramm gibt daraufhin die neuen Meldungen an das GUI-Programm zurück. Diese Meldungen können dann im GUI-Programm angezeigt werden. Gleichzeitig lässt sich das Konsolenprogramm aber auch über die Konsole bedienen und Statusmeldungen können/werden auch an die Konsole ausgegeben.

Zusammenfassung:

Fall 1: Zwei Programme die auf die gleichen Programmteile zugreifen. Aber es wird immer nur eines dieser Programme gestartet.

Fall 2: Zwei Programme. Das Kommandozeilenprogramm ist das Hauptprogramm, welches immer gestartet werden muss. Das GUI-Programm greift nicht auf die Programmlogik über die Module zu, sondern verbindet sich zum Hauptprogramm und führt dort über XMLRPC (das ist die einfachste Art) Funktionen aus um Aktionen auszulösen oder auch nur Statusmeldungen zurück zu bekommen.

Habe ich was vergessen?

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

gerold hat geschrieben:Fall 2: Zwei Programme. Das Kommandozeilenprogramm ist das Hauptprogramm, welches immer gestartet werden muss. Das GUI-Programm greift nicht auf die Programmlogik über die Module zu, sondern verbindet sich zum Hauptprogramm und führt dort über XMLRPC (das ist die einfachste Art) Funktionen aus um Aktionen auszulösen oder auch nur Statusmeldungen zurück zu bekommen.
Danke euch. Ich denke Fall 2 trifft auf mich zu :)
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

droptix hat geschrieben:Danke euch. Ich denke Fall 2 trifft auf mich zu :)
Hallo droptix!

Dann musst du dein Hauptprogramm so schreiben, dass der XMLRPC-Server in einem eigenen Thread läuft. Weiters solltest du die Kommunikation zwischen dem Haupt-Thread und dem XMLRPC-Thread noch mit Locks absichern. Der Rest bleibt deiner Phantasie überlassen.

Z.B. könntest du deine Statusmeldungen zuerst mit ``print`` an die Konsole schicken und im gleichen Atemzug die Statusmeldungen in eine Queue schreiben. Wenn sich dann das GUI-Programm per XMLRPC zum Hauptprogramm verbindet, wird die Queue in eine Liste ausgelesen, welche an das Hauptprogramm zurückgegeben werden kann.

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
droptix
User
Beiträge: 521
Registriert: Donnerstag 13. Oktober 2005, 21:27

Da ich keine "alten" Infos brauche, muss die UI immer nur aktuelle Prozesse abfragen. Eine Queue ist denke ich nicht nötig.

Was heißt "mit Locks absichern"?
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo droptix!
droptix hat geschrieben:Eine Queue ist denke ich nicht nötig.
Ob du mit oder ohne Queue arbeitest ist deine Entscheidung. Aber hier findest du ein Beispiel mit Queue und normalen Statusmeldungen:

hauptprogramm.py:
http://paste.pocoo.org/show/26469/

guiprogramm.py:
http://paste.pocoo.org/show/26470/

Ein bischen viel Threading -- ich weiß. :roll: Aber ich programmiere gerne ein wenig komplizierter. :wink:
droptix hat geschrieben:Was heißt "mit Locks absichern"?
Das übliche Locking -- welches man verwendet, wenn man threadübergreifend mehrere Arbeitsschritte durchführen möchte.

Ein einzelner Python-Arbeitsschritt ist theoretisch durch GIL abgesichert. Deshalb bräuchte ich die threading.Event's nicht unbedingt im Programm. Aber ich hoffe, dass Python bald mehrer Prozessorkerne ausnutzen kann und hoffe, dass dafür die üblichen Threading-Mittel eingesetzt werden. Deshalb arbeite ich (vorsorlich) ;-) mit threading.Event's. Stattdessen könnte man einen Lock setzen, bevor etwas an einer threadübergreifenden Variable geändert wird und danach den Lock wieder aufheben... --- alles für dich im Moment nicht so wichtig.

lg
Gerold
:-)

PS: Ich habe ein paar Kleinigkeiten im Code gefunden, die ich jetzt ein wenig anders lösen würde. -- Ich will also nicht behaupten, dass das Beispiel absolut super ist. -- Aber es funktioniert. :mrgreen:
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
BlackJack

Wobei Fall 1 von oben die einfachere Variante ist. Fall 2 ist was für Programme wo man keinen Zugriff oder keinen Einfluss auf den Quelltext hat, oder die nicht in Python geschrieben sind.

So etwas wie Fortschrittsmeldungen (G)UI-unabhängig in Algorithmen ein zu bauen, geht am einfachsten mit einer Rückruffunktion:

Code: Alles auswählen

from __future__ import division
import time

# Program logic.

def long_running(n, progress_callback=lambda current, total: None):
    for i in xrange(n):
        time.sleep(1)   # Some heavy work.
        progress_callback(i + 1, n)
    return 42

# Text UI.

def print_progress(current, total):
    print 'Done %d of %d (%0.2f%%)' % (current,
                                       total,
                                       float(current) / total * 100)

print 'Answer:', long_running(23, print_progress)
Bei einer GUI übergibt man eine entsprechende Funktion, die zum Beispiel Text in der Statuszeile des Programm ausgibt und/oder einen Fortschrittsbalken aktualisiert.
Antworten