GuiDesigner mit neutralem Format und was dann?

Fragen zu Tkinter.
Antworten
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also vielfach wird gewünscht, dass der GuiDesigner ein neutrales Python unabhängiges Format anbietet.

Das läßt sich leicht realisieren, etwa mit einer Liste - es ist übrigens ein Tree, da es auch children gibt - die man auch mit JSON umwandeln kann. Das würde in etwa so aussehen:

Code: Alles auswählen

[
    ['Radiobutton','button_one',{'text': 'One', 'underline': '0', 'anchor': 'w'}],
    ['grid',{'sticky': 'ew', 'row': '1'}]
..
]
Es ist kein Problem diese Liste hereinzuladen, DynAccess haben wir bereits. Eine weitere Funktion kann dann aus dieser Liste die GUI erzeugen. Eine dritte Funktion macht dann beides zusammen.

Aber Ihr wollt ja diese Liste haben. Kann ja sein, Ihr wollt nicht, dass die GUI automatisch erzeugt wird, denn Ihr wollt ja vielleicht eigene Klassen machen und braucht dann nur die Config und Layout Information, aber nicht für alle Widgets, sondern nur bei einigen.

Wollt Ihr die Liste selber abklappern, oder braucht Ihr Interface Funktionen?
Und wenn Ihr die Information erhaltet, dass ein bestimmtes Widget ein Pack Layout hat und an dritter Stellle zu packen ist, was macht Ihr dann mit dem Rest?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ich glaube, dass man by Pygubu die GUI nochmals nachprogrammiert. Wird das so gewünscht wie bei pygubu oder wie dann?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also, äußert sich niemand dazu?

Aber mir ist dazu etwas eingefallen. Programmiersprachen unabhängiges DynTkInter. Könnte ich TkDynX nennen. Und dazu braucht es nur zwei Befehle:

set_gui: setzt etwas und wartet auf keine Antwort
get_gui: kann etwas setzen oder nicht, jedenfalls wartet es auf eine Antwort.

Bei Python kann man das getrennt von der GUI über einen anderen Thread machen. Und bei anderen Programmiersprachen über TCP und eine andere Anwendung - bei Python natürlich auch, denn warum soll die Anwendung nicht auf dem Smartphone laufen, während man die GUI auf dem Computer bedient?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also das wäre der prinzipielle Aufbau von DynTkInter über Message Transfer. Da gibt es drei Messages:

- 'GUI_SHUTDOWN'
- 'GUI_SET'
- 'GUI_GET'

und außerdem ''GUI_GET_RESPONSE'

Die erste davon ist bereits realisiert:

Code: Alles auswählen

import transx # application as thread in event loop
import DynTkInter as tk
# =========== Message Exchange via event broker =======
import messagebroker.event_broker as event_broker
import messagebroker.public_eventbroker as public_event_broker
public_eventbroker = public_event_broker.eventbroker
app_eventbroker = event_broker.EventBroker(public_eventbroker)
app_eventbroker.public_subscriptions(('GUI_SHUTDOWN',))
# === Message Interface ==============================
root = None

def shutdown_gui(message=None):
    global is_shutdown
    if root != None:
        root.destroy()
    print('TkDynX stopped')
    is_shutdown = True
    app_eventbroker.exit_loop()

app_eventbroker.subscribe('GUI_SHUTDOWN',shutdown_gui)

# Loops - Event loop and GUI main loop ==============
# outer event loop ================================
is_shutdown = False
while not is_shutdown:
    app_eventbroker.loop()
    if not is_shutdown:
        # here shall be the inner GUI loop =======
        print("Application running")
        # after GUI Loop =======================
        root = None
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also, ich hab da mal ein kleines Beipiel mit Messages gemacht und Buttons:

Code: Alles auswählen

import threading
import messagebroker.event_broker as event_broker
import messagebroker.public_eventbroker as public_event_broker

public_eventbroker = public_event_broker.eventbroker
app_eventbroker = event_broker.EventBroker(public_eventbroker)

app_eventbroker.public_publications(('GUI_SHUTDOWN','GUI_SET'))
app_eventbroker.public_subscriptions(('GUI_FINISHED',))

# treat end of mainloop =================================
def end_application(message=None):
    app_eventbroker.publish('GUI_SHUTDOWN')

app_eventbroker.subscribe('GUI_FINISHED',end_application)
# ========= GUI Access ====================================

def set_gui(*args): app_eventbroker.publish('GUI_SET',args)
def set_gui_entry(entry): set_gui(*entry)
def set_gui_list(par_list):
    for entry in par_list: set_gui_entry(entry)

class DynButton():
    def __init__(self):
        set_gui('Button',('DynButton',{'text':'DynButton'}))
        set_gui('pack',{})

def app_start():
    set_gui('Tk',('TkDynX',{'title':'TkDynX'}))
    for i in range(3): DynButton()

_worker_thread = threading.Thread(target=app_eventbroker.loop,args=(app_start,))
_worker_thread.daemon = True
_worker_thread.start()
Und das funktioniert auch.

Mir ist dazu eingefallen, wenn man statt mit set_gui mit get_gui arbeitet, könnte man ja eine Fullpath Referenz zurückerhalten, sodass man eine Referenz auf den Button hätte.
Und könnte dann genauso programmieren, wie sonst auch. Diese Referenz etwa auch als Parent verwenden.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Und das mit Rückgabewerten über Fernverbindung ist auch klar:

Code: Alles auswählen

response_event = threading.Event()
response_message = []

def get_gui_entry(entry):
    response_event.clear()
    public_eventbroker.publish_extern('GUI_GET',entry)
    response_event.wait()
    return response_message

def get_gui(*args): return get_gui_entry(args)

def get_response(message):
    global response_message
    response_message = message
    response_event.set()

public_eventbroker.subscribe_extern('GUI_GET_RESPONSE',get_response)
Man wartet einfach auf ein Event und wenn die 'GUI_GET_RESPONSE' Message eingetroffen ist, kann die Funktion mit dem Rückgabewert zurückkehren.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Was brauchen wir noch?

Sublisten mittels 'children' waren auch sehr einfach:

Code: Alles auswählen

def children(child_list):
    tk.goIn()
    for entry in child_list: set_gui(entry)
    tk.gout()
Bleiben noch Referenzen auf die Widgets, releative sind kein Problem, absolute muss ich noch machen, und dann die Callbacks für command und events.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also, relativen Adressierungsarten habe ich. Das wäre das sogenannte goto:

Code: Alles auswählen

set_gui('Tk',('TkDynX',{'title':'TkDynX'}))
set_gui('Button',('DynButton1',{'text':'DynButton 1'}))
set_gui('Button',('DynButton',{'text':'DynButton 2'}))
set_gui('Button',('DynButton',{'text':'DynButton 3'}))

set_gui('.','DynButton1')
set_gui('pack',{})
set_gui('.',('DynButton',0))
set_gui('pack',{})
set_gui('.',('DynButton',1))
set_gui('pack',{})

set_gui('.','DynButton1')
set_gui('config',{'bg':'yellow'})
set_gui('.',('DynButton',0))
set_gui('config',{'bg':'green'})
set_gui('.',('DynButton',1))
set_gui('config',{'bg':'red'})
Hier wird das betreffende Element ausgewählt und nachfolgende Befahle greifen darauf zu.

Und das ist das widget Commmando:

Code: Alles auswählen

set_gui('Tk',('TkDynX',{'title':'TkDynX'}))
set_gui('Button',('DynButton1',{'text':'DynButton 1'}))
set_gui('Button',('DynButton',{'text':'DynButton 2'}))
set_gui('Button',('DynButton',{'text':'DynButton 3'}))

set_gui('widget',('DynButton1',('pack',{})))
set_gui('widget',(('DynButton',0),('pack',{})))
set_gui('widget',(('DynButton',1),('pack',{})))

set_gui('widget',('DynButton1',('config',{'bg':'yellow'})))
set_gui('widget',(('DynButton',0),('config',{'bg':'green'})))
set_gui('widget',(('DynButton',1),('config',{'bg':'red'})))
Der nachfolgende Befehl ist der zweite Parameter und nur für diesen Befehl gilt die Widget Auswahl
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Und jetzt mit absoluten Referenzen, schaut es schon direkt gut aus:

Code: Alles auswählen

def app_start():

    a = get_gui('Tk',('TkDynX',{'title':'TkDynX'}))
    b = get_gui('Button',('DynButton1',{'text':'DynButton 1'}))
    c = get_gui('Button',('DynButton',{'text':'DynButton 2'}))
    d = get_gui('Button',('DynButton',{'text':'DynButton 3'}))

    set_gui(b,('pack',{}))
    set_gui(c,('pack',{}))
    set_gui(d,('pack',{}))

    set_gui(a,('config',{'title':'Geht doch'}))
    set_gui(b,('config',{'bg':'yellow'}))
    set_gui(c,('config',{'bg':'green'}))
    set_gui(d,('config',{'bg':'red'}))
Und wenn man jetzt noch die Callbacks für command und events rein macht, dann kan man die GUI fernprogrammieren.

Und einen Parent statt Namen braucht man auch nicht, denn nach:

Code: Alles auswählen

a = get_gui('Tk',('TkDynX',{'title':'TkDynX'}))
Könnte man statt:

Code: Alles auswählen

b = get_gui('Button',('DynButton1',{'text':'DynButton 1'}))
auch den Parent verwenden, ohne den Namen wegzuwerfen:

Code: Alles auswählen

b = get_gui(a.append('Button'),('DynButton1',{'text':'DynButton 1'}))
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Habe über Command und Events nachgedacht. Auch kein Problem. Kann man genauso angeben, wie ich es bereits zuvor mit den Funktionen do_event und do_command genacht hatte, nämlich Widget adressieren, Callback Adresse festlegen und außer etwaigem Event noch zusätzliche Parameter.

Nur in diesem Falle wird das dann automatisch auf zwei Callbacks aufgeteilt. Das Applikationssystem richtet den Callback intern nicht auf das Widget ein, sondern auf eine Message Id und Message. Und dem GUI System wird mitgeteilt, dass bei diesem Widget Command oder Event eine betreffende Message geschickt werden soll (welche dann auf dem Applikationssystem diesen Callback auslöst).

Braucht jemand dieses System? Man kann dann bei gleicher GUI Syntax in allen Programmiersprachen Tk programmieren, indem man das GUI System unter Python mit DynTkX laufen läßt. Und die eigentliche Applikation kommuniziert dann über TCP mit dem GUI System, sei es auf dem selben Rechner oder auch nicht.

Und wie geschieht dann die Anbindung? Am besten wohl erst einmal mit JSON RPC, weil es das schon für verschiedene Systeme gibt.
Wie es dann mit Binärdaten Übertragung aussehen würde, müßte man noch genauer nachsehen.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Alfons Mittelmeyer hat geschrieben:Braucht jemand dieses System? Man kann dann bei gleicher GUI Syntax in allen Programmiersprachen Tk programmieren, indem man das GUI System unter Python mit DynTkX laufen läßt.
Da du wohl alle vergrault hast: Niemand braucht das. Warum soll man denn auf eine Art JSON ausweichen und dabei die Unterstuetzung der jeweiligen Sprache (Syntax- & Namensueberpruefung mit sinnvollen Fehlermeldungen, ...) verzichen?
Der Vorschlag war ein _DATEN_format, keine alternative Syntax.

Schau dir doch mal bitte das schon so oft angesprochene Qt an und wie ui Dateien mit den Designer erstellt und zB in Python verwendet werden.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@cofi, also Datenformate gibt es viele:
Das ist ein Datenformat

Code: Alles auswählen

['Das','ist','auch','ein','Datenformat']

<Satz>
    <wort1>Und</wort1>
    <wort2>das</wort2>
    <wort3>ist</wort3>
    <wort4>auch</wort4>
    <wort5>ein</wort5>
    <wort6>Datenformat</wort6>
</Satz>
Aber anscheinend geht es Euch darum, die GUIs eurer tkinter Programme nach Qt zu konvertieren, weil Ihr anscheinend mit tkinter nicht mehr zufrieden seid.
Das Qt xml Format ist aber anscheinend recht aufwändig. Das wäre dann ja wohl ein Heidenstück Arbeit für ein tkinter nach Qt Konvertierprogramm und das noch als GNU Lizenz?
pyseidon
User
Beiträge: 19
Registriert: Donnerstag 24. September 2009, 20:25

Alfons Mittelmeyer hat geschrieben:Aber anscheinend geht es Euch darum, die GUIs eurer tkinter Programme nach Qt zu konvertieren, weil Ihr anscheinend mit tkinter nicht mehr zufrieden seid.
Wozu konvertieren? Wenn dann macht man es gleich neu und richtig mit dem Qt-Designer.
Alfons Mittelmeyer hat geschrieben:Das Qt xml Format ist aber anscheinend recht aufwändig. Das wäre dann ja wohl ein Heidenstück Arbeit für ein tkinter nach Qt Konvertierprogramm und das noch als GNU Lizenz?
Das hat XML generell so an sich, dass es bei komplexen Sachen recht aufwändig wird.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Meine Meinung ist, dass man sich nicht zu voreilig auf ein bestimmtes Datenformat festlegen sollte. Wenn man dann feststellt, dass das so nicht reicht und dann Erweiterungen vornehmen muss, geht die Kompatibilität zwischen Versionen verloren. Und das kann nicht der Sinn sein.

Wichtiger wäre eine Ladefunktion, die die GUI lädt, ohne sie gleich aufzubauen, sodass man die Daten auch für eigene Klassen verwenden kann, und sich dabei Gedanken machen, wie die Schnittstellen aussehen sollen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Bevor ich mich nicht auch noch mit ttk befasst habe, kann ich keine Aussage über ein geeignetes Format treffen. Und dann ist außerdem noch zu berücksichtigen, dass ich zwar in DynTkInter einen Verzeichnisbaum für tkinter Widgets verwende, dieser aber ansonsten nichts mit tkinter Widgets zu tun hat und es sich um jede Art von Objekten handeln kann.

Children sollen als self.master ihren Parent haben und sollen eine destroy methdode haben. Objektklassen können eine Config haben oder auch nicht, sie können ein Layout haben oder auch nicht. Container Objekte enthalten außerdem eine Layout Reihenfolge Liste, in welcher die Children verzeichnet sind, für die eine Layoutreihenfolge einzuhalten ist.
Objekte können Container Objekte sein oder nicht. Wenn sie Container Objekte sind, dann haben sie ein Namensverzeichnis ihrer Children zu enthalten. Mehrere der Children können auch den gleichen Namen haben. Daher enthält ein Namenseintrag eine Liste der Objekte, die den gleichen Namen haben. Diese Objekte können dann per Namen und Index adressiert werden.

Wenn die Destroy Methode auf ein Objekt angewendet wird, sollen auch die children 'destroyed' werden. Idealerweise auch der dazugehörige Code. Und wenn man ein Container Objekt löscht, sind auch die Verzeichniseinträge der Children weg. Wenn ein Child gelöscht wird, soll auch dessen Verzeichniseintrag gelöscht werden.
Der Verzeichnisbaum ist kein statischer Baum, sondern unterliegt bei dynamischer Programmierung ständigem Wechsel, kann beliebig zusammenschrumpfen oder sich ausdehnen. Stets kann etwas gelöscht werden und auch wieder neu dazu geladen oder dynamisch generiert werden und das 'unlimited'.

Und genau diesen Zweck erfüllen die GUI Scripte, die auch Code enthalten können. Der Code existiert nur solange, wie er von Callbacks referenziert wird. Ansonsten ist er weg.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

cofi hat geschrieben:
Alfons Mittelmeyer hat geschrieben:Braucht jemand dieses System? Man kann dann bei gleicher GUI Syntax in allen Programmiersprachen Tk programmieren, indem man das GUI System unter Python mit DynTkX laufen läßt.
Da du wohl alle vergrault hast: Niemand braucht das. Warum soll man denn auf eine Art JSON ausweichen und dabei die Unterstuetzung der jeweiligen Sprache (Syntax- & Namensueberpruefung mit sinnvollen Fehlermeldungen, ...) verzichen?
Der Vorschlag war ein _DATEN_format, keine alternative Syntax.
Schau dir doch mal bitte das schon so oft angesprochene Qt an und wie ui Dateien mit den Designer erstellt und zB in Python verwendet werden.
Ich weiß nicht, was Du willst. Anscheinend willst Du kein Datenformat, weil man darauf eine alternative Syntax errichten kann.

Wenn man statt:

Code: Alles auswählen

function('name',**{kwargs})
schreibt:

Code: Alles auswählen

['function_id',['name',{kwargs}]]
dann ist das vollständig JSON kompatibel. Wenn es Dir nicht gefällt, dann schlag doch Du etwas anderes vor! Aber bitte keine Ratschläge, ich solle mir Qt ansehen. Denn wenn ich dann wieder etwas vorschlage, gefällt es Dir wahrscheinlich auch nicht.

Mein Vorschlag wäre, ich sehe im GuiDesigner zwei Menüpunkte vor - einen zum Speichern und einen zum Laden, die auf eine Config Datei zugreifen. Und da kann dann jeder zwei Scripte eintragen, welche die Daten so speichern und laden, wie sie es gerne haben möchten.
Ich kann ja zwei Musterscripte für Speichern und Laden mitliefern, die dann jeder auf seine Bedürfnisse anpassen kann!

Natürlich will ich Erfindergeist keine Grenzen setzen. Wenn jemand nicht schreiben will:

Code: Alles auswählen

mybutton.config(bg = 'green')
Sondern lieber:

Code: Alles auswählen

mybutton.config(bg='<color>green</color>')
ja, warum nicht? Kann man natürlich jederzeit überschreiben. Wenn ich das so abspeichern soll, muss man mir nur sagen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Bzw. macht euch einfach ein Script, welches ein Dictionary **{'bg':'green'} als Parameter so ausgibt.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: Du hast den Kern von cofis Anmerkung verfehlt. In einem Datenformat haben Funktionsaufrufe, egal, ob die nun in irgendwelchen Strukturen codiert sind oder nicht, nichts zu suchen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3 Dann kann man eben nichts machen. Die Information, dass etwas ein 'Button' ist, sorgt ja schon dafür, dass man danach Button aufrufen kann.

Und Parameter in XML Form ändert nichts daran, dass es dann immer noch Funktionsparameter sind.

Der Unterschied, den ich festgestellt hatte, war lediglich der, was ich Schritt für Schritt hintereinander gemacht hatte und dabei goIn() und goOut() benutzt hatte, macht man bei einer Childrenliste mit einer Schleife.

Wenn man allerdings meint, der GuiDesigner soll intern auf einem Datenformat arbeiten, wie es andere Programme auch tun: Das Prinzip dieses GuiDesigner ist ein ganz anders. Wenn man bei grid für row den Wert '10' eingibt, dann führt der GuiDesigner einfach einen tkinter Funktionsaufruf aus: this().grid(**{'row':'10'}) und merkt sich überhaupt keine Daten, lediglich this().Layout = GRIDLAYOUT. Die braucht man sich auch nicht zu merken, sondern bekommt sie jederzeit mit this().grid_info()

Und wel er so arbeitet, beherrscht dieser GuiDesigner auch das Grid-Layout.
Antworten