GUI Designer mit Source File update?

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

Ein Export nach tk sieht so aus:

Code: Alles auswählen

import tkinter as tk
#import DynTkExtend as ext

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.button_frame = ButtonFrame(self,**{'text': 'Buttons'})
        #ext.grid_table(self.button_frame,**{'grid_rows': '(6, 25, 0, 0)', 'grid_cols': '(1, 75, 0, 0)'})
        self.button_frame.pack()

class ButtonFrame(tk.LabelFrame):

    def __init__(self,master,**kwargs):
        tk.LabelFrame.__init__(self,master,**kwargs)
        self.button_one = tk.Radiobutton(self,**{'text': 'One', 'underline': '0', 'anchor': 'w'})
        self.button_one.grid(**{'sticky': 'ew', 'row': '1'})
        self.button_three = tk.Radiobutton(self,**{'text': 'Three', 'underline': '0', 'anchor': 'w'})
        self.button_three.grid(**{'sticky': 'ew', 'row': '3'})
        self.button_two = tk.Radiobutton(self,**{'text': 'Two', 'underline': '1', 'anchor': 'w'})
        self.button_two.grid(**{'sticky': 'ew', 'row': '2'})
        self.check_button = tk.Checkbutton(self,**{'text': 'Checkbutton', 'underline': '2', 'anchor': 'w'})
        self.check_button.grid(**{'sticky': 'ew', 'row': '0'})
        self.menu_button = tk.Menubutton(self,**{'text': 'Menubutton', 'indicatoron': '1', 'relief': 'raised'})
        self.menu_button.grid(**{'sticky': 'ew', 'row': '5'})
        self.standard_button = tk.Button(self,**{'text': 'Button', 'underline': '0'})
        self.standard_button.grid(**{'sticky': 'ew', 'row': '4'})

Application().mainloop()
Was mit tk allein nicht geht habe ich mal auskommentiert.

Für Container Widgets, die andere Widgets enthalten, wird hierbei jeweils eine Klasse erzeugt.

Das Problem ist nun, wie kann man die GUI nachbearbeiten und die geänderte GUI dann wieder in das Source File übernehmen?
Bei den GUI Scripts speichere ich mir die markierten Code Teile in den Container Widgets und kann dann Widgets und Code zusammen abspeichern.
Das geht aber bei den exportierten Files nicht.

Aber mir ist da eine andere Lösung eingefallen. Beim Abspeichern könnte man in bereits vorhandenern Source Files den Beginn der Klassendefinitionen ersetzen.

Dabei wird ein Sourcefile gelesen, bis eine Klassendefinition auftaucht. Wenn es sich um eine Klasse handelt, die auch in der GUI definiert ist, dann wird der Teil bis nach erster Leerzeile nach def __init__ einfach ausgetauscht. Wenn etwas Neues hinzugekommen ist, dann wird dieser Rest vor 'mainloop(' eingefügt.

Gute Idee?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Alfons Mittelmeyer hat geschrieben: Das Problem ist nun, wie kann man die GUI nachbearbeiten und die geänderte GUI dann wieder in das Source File übernehmen?
Dieses Problem existiert vermutlich seit Anbeginn der GUI-Designer :mrgreen:

Qt war das erste Framework (was mir bekannt ist :!: ), welches ab Ende der 1990er Jahre bereits designte UI-Komponenten in einer Metasprache in separaten Dateien abgelegt hat. Aus diesen Dateien (XML) wird dann spätestens zur Compile-Zeit Code erzeugt. Damit sich User-Code und automatisch generierter Code *nicht* in die Quere kommen, ist es iirc ein probates Mittel auf Seiten des User-Codes Vererbung einzusetzen und von den erzeugten Klassen abzuleiten.

Zumindest beim Python-Binding PyQt ist man sogar so weit gegangen, dass man die UI-Typen sogar dynamisch zur Laufzeit aus den UI-Dateien generieren konnte und sich somit den durchaus fehlerträchtigen Generierungsschritt zur Designzeit sparen kann. Ich weiß nicht, ob das auch in C++ möglich ist oder in anderen Sprachbindings ebenfalls angeboten wird.

Zu Gtk kann ich nichts fundiertes sagen, meine mich aber zu erinnern, dass man auch dort auf XML basierte Definitionsdateien aufsetzt.

Microsoft hatte bei MFC einen ähnlich, wenn auch imho total vermurksten Ansatz gewählt: Dort wird ein proprietäres INI artiges Format für die Beschreibung von UI-Komponenten genutzt... allerdings wird dabei Projektweit in *eine* Datei geschrieben... dazu kommen dann noch global eindeutige numersiche IDs, die trotzt automatischer Generierung oftmals Duplikate aufweisen, was tödlich sein kann, usw.

Moderne UI-Frameworks wie JavaFX oder MPF (Nachfolger von WindowsForms) nutzen einen zu Qt analogen Ansatz und setzen auf separate XML-Dateien zur Beschreibung von UI-Komponenten.

Typische Java-Toolkits wie AWT oder Swing hatten / haben afaik kein "offizielles" Vorgehen dabei; die Designer, die ich kenne, haben tatsächlich Code erzeugt und dazu noch nicht offensichtlich zu trennenden oder getrennten vom User-Code. Dies führte oftmals zu Dateien, die den Designer später beim Parsen überfordert haben, oder allgemein zu ziemlich unübersichtlichen Monstren, da die manuell angelegten wichtigen Events nicht von simplen über den Designer zusammengeschraubten ersichtlich sind. Mich hat dieses Vorgehen immer abgeschreckt und es hat wohl auch viele Menschen bezüglich GUI-Designer desillusioniert, die nichts anderes kannten. (Im uu.de Forum hatte ich da schon so viele "lustige" Diskussionen mit solchen Old School Nicht über den Tellerand blickenden Java-Leuten... :twisted: )

Sofern ich das bisher richtig mitbekommen habe, wählst Du ja genau diesen Ansatz?

Bei den Windows Forms hat MS einen "Mittelweg" gewählt (und afaik sogar ein Sprachfeature "partial classes" eingeführt), indem der Designer direkt Code generiert, dieser jedoch *bewusst* vorm User ferngehalten wird. Tatsächlich werden *zwei* Codedateien erzeugt, die jeweils *Teile* der Gesamtklasse beinhalten. Der eine, oftmals sehr große Teil mit langweilgen UI-Initialisierungen, wird dabei ausschließlich vom Designer verwaltet, der andere Teil mit dem Konstruktor und ggf. den manuell hinzugefügten Eventhandling bleibt dabei weitestgehend vom Designer unangetastet, so dass man sich höchst selten in die Quere kommt. Ideal ist das aber imho auch nicht...

Der Trend scheint wirklich der Ansatz über ein neutrales Format zu sein, welcher imho auch am einfachsten zu verwalten ist! Parsen und Erzeugen (inklusive Validieren) ist super einfach und man kommt sich als Entwickler mit dem Designer einfach nicht ins Gehege. Darüber hinaus bieten sich dabei noch weitere Ansatzmöglichkeiten, wie etwa das Transformieren in eine gänzlich andere UI-Technologie uvm.

Iirc ist Dir aber auch schon zu solch einem Weg geraten worden ;-)

Ich hoffe, ich konnte Dich ein wenig erhellen!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hyperion hat geschrieben: Sofern ich das bisher richtig mitbekommen habe, wählst Du ja genau diesen Ansatz?

Iirc ist Dir aber auch schon zu solch einem Weg geraten worden ;-)

Ich hoffe, ich konnte Dich ein wenig erhellen!
Danke Hyperion, aber erhellt bin ich genug. Ob ich genau diesen Ansatz wähle. Aber nein, denn es gibt ja verschiedene Ansätze: Und zwei habe ich realisiert.

Der erste Ansatz: ein GUI Script Format. Man kann entweder direkt in diesen GUI Files programmieren oder sie in sein Programm einbinden. Das Einbinden geschieht etwa mit:

Code: Alles auswählen

DynLoad('mygui.gui')
und Access auf die Widgets hat man dann durch:

Code: Alles auswählen

access=DynAccess('mygui.acc')
Und dann gibt es noch folgende Interface Funktion:

Code: Alles auswählen

widget(parent,name)
Wobei der Name ein String ist und auch Leerzeichen und Sonderzeichen enthalten kann. Die Container Widgets allerdings, die über access.name erreichbar sind müssen eindeutige Namen haben und diese müssen auch den Anforderungen von Variablennamen genügen. Leerzeichen oder Operatorzeichen und dergleichen geht da nicht.

Mit DynLoad allerdings lädt man eine fertige GUI und kann keine Klassen davon bilden. Allerdings hat man hier sein Programm Script getrennt von der GUI und ist nicht an einen bestimmten Programmaufbau gebunden.

Der zweite Ansatz: direkte Erzeugung eines Python Tkinter Scripts und heute auch mit update eines bestehenden Scripts. Man kann die GUI editieren und bekommt die veränderte GUI gleich eingebunden in sein Source File. Jeglicher dazugefügte Code bleibt hierbei erhalten. Man muss nur nach der __init__ von einer durch den GUI Designer erzeugten Klasse eine Leerzeile haben.
Man kann Werte in der __init__ auch manuell verändern und das aber auch wieder mit dem GUI Designer weiter nachbearbeiten. Das ist ganz gut für schnell erstellte Programme. Man ist allerdings an den Programmaufbau gebunden, dass Container Widgets durch eine Klasse realisiert sind und der Klassenname CamelCase vom Widgetnamen ist.

Also, ich habe es heute schon hinbekommen, aber auf Github wird es erst morgen sein.

Der dritte Ansatz: Das wäre, was Du angesprochen hattest. Schließlich gibt es tk auch für tcl oder Ruby und andere Programmiersprachen. Da könnte man an eine XML Datei denken. Da wäre nur die Frage, welches Format. Gibt es hierfür bereits GUI Designer oder Ähnliches mit einem sehr verbreiteten Formatn für tk? Wenn ja, würde ich gerne mehr darüber wissen. Es ist ja kein Problem, beliebige Formate zu exportieren.

Am Besten wäre, wenn es dafür schon etwas gibt und man nicht noch für Ruby etwa ein Ruby Interface zu programmieren braucht.

Anscheinend versteht Ruby tcl/tk. Da wäre dann auch an einen Export nach tcl/tk zu denken.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Alfons Mittelmeyer hat geschrieben: Der dritte Ansatz: Das wäre, was Du angesprochen hattest.
Deinen zweiter Ansatz (direkt Code erzeugen / parsen) hatte ich sehr wohl auch erwähnt ;-)

Ich habe darüber hinaus nur erwähnt, dass *ich* ein neutrales Format am besten finde und dass sich dieser Ansatz offenbar aktuell durchsetzt...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ein neutrales Format ist doch aber nur ein Teil der Lösung. Klar das kann man maschinell einlesen ändern und wieder ausgeben...

Aber wie verknüpft man dabei Events? Also generiert man dafür dann wieder ein Grundgerüst, damit es einfacher geht?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hyperion hat geschrieben:Ich habe darüber hinaus nur erwähnt, dass *ich* ein neutrales Format am besten finde und dass sich dieser Ansatz offenbar aktuell durchsetzt...
Was ist denn jetzt neutraler Code für tcl/tk?

Neutral kann der Code sowieso nicht sein, denn es geht bei dem GUI Framework um tcl/tk und nicht um ein anderes. Es gibt also keinen neutralen Code. Der Code ist immer GUI Framework abhängig. Da wäre allerdings die Frage, ob tcl/tk ein Programmiersprachen unabhängiges Format unterstützt.

Für Python mit tkinter gibt es das anscheinend nicht, denn dann hätte bestimmt schon jemamd darüber etwas geschrieben.

Für tcl wäre tcl die richtige Lösung. Aber auch Ruby soll anscheinend einen tcl Interpreter haben. Wie sieht es mit anderen Programmiersprachen aus?
Es macht jedenfalls keinen Sinn in ein Format zu exportieren, das niemand versteht. Ich werde auch nach tcl exportieren. Aber nutzt das den Python Usern etwas? Schreibst Du etwa einen tcl/tk Interpreter für Python?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Es geht nicht um ausfuehrbaren Code, sondern um ein neutrales _Format_, eben beispielsweise XML wie es Qt verwendet. Und natuerlich kann das Format neutral sein, du kannst zB das ui Format von Qt benutzen, nur dass du eben Tk Widgets beschreibst statt Qt.
BlackJack

@Alfons Mittelmeyer: Wieso sollte jemand einen tk/tcl-Interpreter für Python schreiben? Den gibt's doch schon — der wird über das `Tkinter`-Modul eingebunden.

Neutral bezieht sich auf die Programmiersprache von der aus das Format verwendet wird. Eben wie bei den vorhandenen Formaten, beispielsweise für Gtk oder Qt. Die Datendateien von den Designern kann man von verschiedenen Programmiersprachen aus benutzen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Wieso sollte jemand einen tk/tcl-Interpreter für Python schreiben? Den gibt's doch schon — der wird über das `Tkinter`-Modul eingebunden.
Ach den gibt es schon? Dann kann ich ja auch dieses Format machen, wenn das Python schon versteht:

Code: Alles auswählen

frame .myFrame1 -background red  -relief ridge -borderwidth 8 -padx 10 -pady 10  -height 100 -width 100
frame .myFrame2 -background blue  -relief ridge -borderwidth 8 -padx 10 -pady 10  -height 100 -width 50
pack .myFrame1 
pack .myFrame2
Das wäre Original tcl/tk also wohl das Optimale. Aber wie bindet man das in Python und tkinter ein?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

cofi hat geschrieben:Es geht nicht um ausfuehrbaren Code, sondern um ein neutrales _Format_, eben beispielsweise XML wie es Qt verwendet. Und natuerlich kann das Format neutral sein, du kannst zB das ui Format von Qt benutzen, nur dass du eben Tk Widgets beschreibst statt Qt.
Was nützt ein neutrales Format das kein Programm versteht? Wennn ich etwa <highligtthickness=4> irgendwo reinschreibe und kein Programm kann etwas damit anfangen? Es kommt nicht auf ein Format an, sondern was drinsteht und ob irgend ein Programm auf der Welt etwas damit anfangen kann. Also gibt es diese Attribute etwa auch für Qt? Kann ich grid_columnconfigure auch für Qt hineinschreiben oder ergibt das keinen Sinn.

Wenn jemand erpicht auf XML ist, kann er ja auch machen:

Code: Alles auswählen

# statt
import tinter as tk
root = tk.Tk()
mybutton = Button(root,text="Hello")
mybutton.pack()

# mit xml
import create_xml as tk
root = tk.Tk()
mybutton_xml = tk.Button(root,text="Hello")
mybutton = mybutton_xml.execute()
mybutton.pack()
Und wenn jemand das pack auch zuvor als XML haben will, kann er sich das ja auch schreiben. Jeder soll es machen, wie es ihm gefällt.

Ich könnte dafür ja ein Dummy bereitstellen, das statt XML einen eval Ausdruck erzeugt. Und wenn einer stattdessen XML will, kann er das dann ändern.

Sorry, aber ich bin die falsche Adresse für ein einheitliches XML Format für tcl/tk. Da musst Du Dich mit den Machern von tcl/tk in Verbindung setzen, dass sie so etwas realisieren sollen. Ich bin nicht der Macher von tcl/tk.

Aber die Änderung habe ich jetzt auf Github gestellt. Dieses File konnte man einfach starten, mit dem GUI Designer ändern und wieder abspeichern. So sah es erst aus:

Code: Alles auswählen

import DynTkInter as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.button_frame = ButtonFrame((self,'button_frame'),**{'text': 'Buttons'})
        tk.grid_table(self.button_frame,**{'grid_rows': '(6, 25, 0, 0)', 'grid_cols': '(1, 75, 0, 0)'})
        self.button_frame.pack()

class ButtonFrame(tk.LabelFrame):

    def __init__(self,master,**kwargs):
        tk.LabelFrame.__init__(self,master,**kwargs)
        self.button_one = tk.Radiobutton((self,'button_one'),**{'text': 'One', 'underline': '0', 'anchor': 'w'})
        self.button_one.grid(**{'sticky': 'ew', 'row': '1'})
        self.button_three = tk.Radiobutton((self,'button_three'),**{'text': 'Three', 'underline': '0', 'anchor': 'w'})
        self.button_three.grid(**{'sticky': 'ew', 'row': '3'})
        self.button_two = tk.Radiobutton((self,'button_two'),**{'text': 'Two', 'underline': '1', 'anchor': 'w'})
        self.button_two.grid(**{'sticky': 'ew', 'row': '2'})
        self.check_button = tk.Checkbutton((self,'check_button'),**{'text': 'Checkbutton', 'underline': '2', 'anchor': 'w'})
        self.check_button.grid(**{'sticky': 'ew', 'row': '0'})
        self.menu_button = tk.Menubutton((self,'menu_button'),**{'text': 'Menubutton', 'indicatoron': '1', 'relief': 'raised'})
        self.menu_button.grid(**{'sticky': 'ew', 'row': '5'})
        self.standard_button = tk.Button((self,'standard_button'),**{'text': 'Button', 'underline': '0'})
        self.standard_button.grid(**{'sticky': 'ew', 'row': '4'})

        self.standard_button['command'] = lambda: print("Hello World")

Application().mainloop('guidesigner/Guidesigner.py')
Und dann hatte ich mit dem GuiDesigner den standard_button und den menu_button geändert und wieder gespeichert:

Code: Alles auswählen

import DynTkInter as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.button_frame = ButtonFrame((self,'button_frame'),**{'text': 'Buttons'})
        tk.grid_table(self.button_frame,**{'grid_rows': '(6, 25, 0, 0)', 'grid_cols': '(1, 75, 0, 0)'})
        self.button_frame.pack()

class ButtonFrame(tk.LabelFrame):

    def __init__(self,master,**kwargs):
        tk.LabelFrame.__init__(self,master,**kwargs)
        self.button_one = tk.Radiobutton((self,'button_one'),**{'text': 'One', 'underline': '0', 'anchor': 'w'})
        self.button_one.grid(**{'sticky': 'ew', 'row': '1'})
        self.button_three = tk.Radiobutton((self,'button_three'),**{'text': 'Three', 'underline': '0', 'anchor': 'w'})
        self.button_three.grid(**{'sticky': 'ew', 'row': '3'})
        self.button_two = tk.Radiobutton((self,'button_two'),**{'text': 'Two', 'underline': '1', 'anchor': 'w'})
        self.button_two.grid(**{'sticky': 'ew', 'row': '2'})
        self.check_button = tk.Checkbutton((self,'check_button'),**{'text': 'Checkbutton', 'underline': '2', 'anchor': 'w'})
        self.check_button.grid(**{'sticky': 'ew', 'row': '0'})
        self.menu_button = tk.Menubutton((self,'menu_button'),**{'text': 'Menubutton', 'bg': '#93d923', 'indicatoron': '1', 'relief': 'raised'})
        self.menu_button.grid(**{'sticky': 'ew', 'row': '5'})
        self.standard_button = tk.Button((self,'standard_button'),**{'text': 'Button', 'underline': '0', 'default': 'active'})
        self.standard_button.grid(**{'sticky': 'ew', 'row': '4'})

        self.standard_button['command'] = lambda: print("Hello World")

Application().mainloop('guidesigner/Guidesigner.py')
Beim standard_button ist nun 'default':'active' und der menu_button hat jetzt einen grünlichen Hintergrund. Und den Rest hat der GuiDesigner wieder genau so abgespeichert, wie er war. Eine Leerzeile Abstand zum __init__ genügt, dass der GuiDesigner den Code nicht anfasst. Der command vom standard_button ist da.
Sirius3
User
Beiträge: 18268
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: es geht doch in erster Linie darum, dass DEIN Designer ein Format hat, das er schreiben und vor allem ohne viel Magie lesen kann. Das ist mit Deinem bisherigen Dyn-Tk-Format nicht der Fall. Da braucht es den Python-Parser und Magie. Netter Nebeneffekt ist, dass man ohne große Probleme für jede Programmiersprache, die eine Tk-Unterstützung hat einen Reader schreiben kann. Warum ist PyQt so beliebt? Sicherlich nicht, wenn man die GUI-Dateien erst in C speichern und dann compilierten müßte. Außerdem kommt so niemand auf die Idee, im Designer-Python-Code herumzupfuschen. Aber das hab ich Dir schon in etlichen anderen Threads geschrieben. Dein DynTk-Python hat gegenüber einem vollständig sprachunabhängigen Format, das dynamischen geladen wird, nur Nachteile. Welche Ansätze es gab und was für Probleme das macht, hat ja Hyperion schon in seinem Beitrag geschrieben.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3 Das neutrale Format bietet natürlich Vorteile.

Zu überlegen wären folgende Formate:

- XML (jedoch kann man da Bitmaps mit einfügen? - etwa Bytearrays für Images)
- modifiziertes JSON (sodass es auch Bitmaps kann)
- MessagePack (leider weniger gut lesbar, aber optimal für Bitmaps)

Übrigens mein GuiDesigner liest überhaupt keine Formate. Und DynTkInter auch nicht. DynTkInter hat nur erweiterte Tkinter Widget Klassen, wobei Name und Referenzen in einem Dictionary des Container Widgets gespeichert werden.

Die Struktur wird einfach durch Ausführung von Python Code erzeugt, entweder im Main Script oder mit DynLoad über compile und eval.
Es wird auf kein Format zugegriffen, sondern nur config(), pack_info(), grid_info() und place_info() der Widgets abgefragt.

Es gibt bisher also noch kein Format, das irgendwie lesend behandelt wird, außer dass die Saveroutinen in Python ausführbare Formate erzeugen.

Ein unabhängiges Format wäre sinnvoll. Wenn man MessagePack nehmen würde, hätte man keine Arbeit.
Und man bräuchte nicht einmal einen Reader zu schreiben, wenn man die GUI etwa unter Python und tkinter laufen ließe und mit der Anwendung dann über TCP darauf zugreift!

Für die Events stehen eh schon Funktionen zur Verfügung, welche die events in Messages verwandelt.
Wenn man auch die Anbindung der Event Callback statt durch Funktionsaufruf über eine Message implementiert, wäre die totale Abkopplung der GUI von jeglicher Programmiersprache perfekt.
Einfach die GUI über Senden und Empfang von Messages programmieren und abfragen.

Und man kann ein universelles tk Format einfach erzeugen:

Code: Alles auswählen

# statt
Radiobutton('button_three',**{'text': 'Three', 'underline': '0', 'anchor': 'w'})

# etwa
[ 'Radiobutton', ['button_three',[['text','Three'],['underline','0'],['anchor','w']]]]
Mit einer Liste sollte jede Programmiersprache etwas anfangen können.
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 14. Oktober 2015, 11:16, insgesamt 1-mal geändert.
Benutzeravatar
sparrow
User
Beiträge: 4537
Registriert: Freitag 17. April 2009, 10:28

Alfons Mittelmeyer hat geschrieben:- XML (jedoch kann man da Bitmaps mit einfügen?
Da würde ich mich ebenfalls daran orientieren, wie das andere GUI-Designer lösen. Ich würde hier die Möglichkeit geben den Pfad zu dem Bild innerhalb des Projektes anzugeben, anstatt tatsächlich große Daten-BLOBs in das Format mit hinein zu speichern.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Alfons:
Hmm, was sollte so ein Designer bzgl. Speichern/Laden können? Als Grundanforderung würde ich folgendes erwarten:
- im Designer erstellte Oberflächen werden mit allen Deklarationen gespeichert
- beim Laden ist alles im Designer Deklarierbare wieder zugänglich

Ein Bonbon gibts, wenn das Austauschformat auch noch texteditierbar ist, entweder direkt (dann brauchts ein Editor gängiges Format, welches alle deklarierbaren Zusammenhänge korrekt abbilden kann und für den Designer wieder parsbar ist) oder halt über eine Editorkomponente im Designer selbst (dann ginge auch ein Binärformat).

Sowohl die Umsetzung der Grundanforderung als auch das Bonbon werden mit Python oder Tcl als Format unmöglich. Warum? Weil Du niemals nicht diese Sprachen mit allen Möglichkeiten in den GUI-Designer abstrahieren willst. Was soll z.B. der Designer laden, wenn jmd. im Editor mittels einer Schleife mehrere Elemente in korrektem Python/Tcl mit Schleifenvariable erstellt? Entweder brichst Du hier mit der Forderung der vollständigen Ladbarkeit oder fängst an, dem Designer die Semantik strukturierter Programmierung beizubringen. Letzteres ist meines Wissen bisher niemandem gelungen.

Und das ist der Grund, warum eigentlich alle hierfür nicht direkt auf Programmiersprachensemantik setzen (Programmiersprachen können einfach zuviel), sondern ein deklaratives Format nutzen - der Designer arbeitet deklarativ, ergo auch das Speicherformat.

tl;dr - Du musst Python als Speicherformat loswerden. ;)

Edit bzgl Deiner letzten Antwort:
Ein Binärformat geht prinzipiell, schränkt aber die Editiermöglichkeit auf ein selbst geschriebenes Tooling ein (z.B. eigene Editorkomponente im Designer). Du kannst auch Binärdaten in XML oder JSON kodiert ablegen, oder entwirfst ein Komplexformat ala odt (ist ein zip-Archiv mit XML- und Ressourcendateien).
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

jerch hat geschrieben:Sowohl die Umsetzung der Grundanforderung als auch das Bonbon werden mit Python oder Tcl als Format unmöglich. Warum? Weil Du niemals nicht diese Sprachen mit allen Möglichkeiten in den GUI-Designer abstrahieren willst. Was soll z.B. der Designer laden, wenn jmd. im Editor mittels einer Schleife mehrere Elemente in korrektem Python/Tcl mit Schleifenvariable erstellt? Entweder brichst Du hier mit der Forderung der vollständigen Ladbarkeit oder fängst an, dem Designer die Semantik strukturierter Programmierung beizubringen. Letzteres ist meines Wissen bisher niemandem gelungen.

Und das ist der Grund, warum eigentlich alle hierfür nicht direkt auf Programmiersprachensemantik setzen (Programmiersprachen können einfach zuviel), sondern ein deklaratives Format nutzen - der Designer arbeitet deklarativ, ergo auch das Speicherformat.

tl;dr - Du musst Python als Speicherformat loswerden. ;)
Jerch da täuscht Du Dich völlig darüber wie der GUI Designer arbeitet. Er lädt nichts, er greift nur auf die Widgets zu. Und wenn jemand mit einer Schleife Widgets erzeugt hat, dann sind sie da und können mit dem GUI Designer bearbeitet und gespeichert werden.

Der ganze GUI Designer, der aus 35 Einzelscripts besteht, kann mit dem GUI Designer geladen, bearbeitet und wieder gespeichert werden. Wenn man ihn mit 'Load & Run' lädt, hat man eine zweite voll funktionsfähige Version auf dem Bildschirm.

Wenn man ihn mit 'Load & Edit' lädt, sieht man die sichtbaren Widgets, dynamisch erzeugte aber nicht und er hat auch keine Funktionalität. Aber wenn man ihn dann über Save abspeichert, speichert man ihn als ein einzelnes Script mit über 3000 Zeilen ab. Und das ist dann ein lauffähiger GUI Designern in einem Script File.

Mit dem GUI Designer kann man alle Widgets bearbeiten und speichern, ob sie nun mit dem Designer erzeugt wurden oder in einer Schleife.
Das Abspeichen dynamisch erzeugter Widgets allerdings mußte ich beim GUI Designer für einige Container sperren, weil ich sie dann ja zweimal hätte, nämlich abgespeichert vom GUI Designer und beim nächsten Programmlauf nochmals zusätzlich erzeugt. Und das Bearbeiten einiger Container Inhalte mußte ich beim GUI Designer auch sperren, da sich deren Inhalt dynamisch während der Bearbeitung verändern würde, nämlich alles Löschen und Neuaufbau. Und das verträgt der GUI Designer nicht besonders und würde auch keinerlei Sinn machen.

Du kannst mit dem GUI Designer über den Menüpunkt Special -> Toproot in den GUI Designer wechseln und ihn mit sich selber bearbeiten. Abspeichern mit Code kann man aber dann nichts, da der Code nicht mit hereingeladen wurde, sondern nur ausgeführt wurde.

Wenn Du aber den GUI Designer über den GUI Designer ein zweites mal lädst, hast Du diese Version mit Code - der wird in den Container Widgets gespeichert - und kannst sogar Teile des GUI Designers inclusive Code oder ihn gesamt abspeichern.

Exportieren geht allerdings nicht, da ich mit der Namenswahl schlampig war und Leerzeichen mit drin hab und Operatorzeichen und auch viele Namen doppelt, dreifach und noch öfters verwendet habe für unterschiedliche Container Widgets und mit Code exportieren ginge auch nicht.

Also der GUI Designer interpretiert keine Semantik er sieht nur die Widgets auch solche von Deinen existierenden Programmen, wenn Du etwa 'import DynTkInter as tk' machst statt 'import tkinter as tk' und dann den GuiDesigner etwa mit tk.gui() dazu mit aufrufst. Wenn Du den GUI Designer danach aufrufst, zeigt er bereits richtig an, wenn Du ihn vorher aufrufst, musst Du ihn erst noch benachrichtigen, dass mittlerweile eine Änderung stattfand mittels Menüpunkt Special -> Refresh.

Nicht implementiert habe ich allerdings die Bearbeitung von Menüs, die nicht mit dem GUI Designer entworfen wurden. Da weiß ich ja nicht, kommen noch die DynTkinter MenüItem Widgets oder nicht und hätte dann das Menü eventuell doppelt. Man könnte natürlich die Menüwidgets auf einen Buttondruck hin aus einem nicht mit dem GuiDesigner erzeugtem Menü kreieren.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

jerch hat geschrieben: tl;dr - Du musst Python als Speicherformat loswerden. ;)

Edit bzgl Deiner letzten Antwort:
Ein Binärformat geht prinzipiell, schränkt aber die Editiermöglichkeit auf ein selbst geschriebenes Tooling ein (z.B. eigene Editorkomponente im Designer). Du kannst auch Binärdaten in XML oder JSON kodiert ablegen, oder entwirfst ein Komplexformat ala odt (ist ein zip-Archiv mit XML- und Ressourcendateien).
Also wie bereits geschrieben, der GUI Designer arbeitet nicht deklarativ, sondern er arbeitet direkt auf den tkinter Widgets.

Ein JSON Format wäre ganz einfach zu implementieren, denn das wäre kaum eine Änderung. Das wäre einfach nur ein großes Array, mit jeweils einem Array pro bisheriger Scriptzeile. Und der Befehl würde dann als String drinstehen. Und die Parameter müßten dann in das Dictionary Format gebracht werden. Allerdings 'goIn' und 'goOut' könnten irritierend wirken.

Vielleicht dann wohl besser XML, das die Baumstruktur in gewohnter Weise widergibt.

Aber warum soll man Python als Speicherformat loswerden? Etwas dagegen, wenn ich Python besonders unterstütze?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Könnte man natürlich so machen:

Code: Alles auswählen

#anstatt
Radiobutton('button_one',**{'text': 'One', 'underline': '0', 'anchor': 'w'}).grid(**{'sticky': 'ew', 'row': '1'})

#XML
<button_one class=Radiobutton>
    <config>
        <text>One</text>
        <underline>0</underline>
        <anchor>w</anchor>
    </config>
    <layout type=grid>
        <sticky>ew</sticky>
        <row>1</row>
    </layout>
</button_one>
Wenn jemand dieses Format brauchen kann, ja warum nicht?

Der bräuchte dann nur noch einen Reader zu schreiben, der das rückübersetzt.

Das Format ist komplizierter zu schreiben als bisher:

Code: Alles auswählen

    if len(conf_dict) != 0:
        filehandle.write(",**"+repr(conf_dict)+")\n")
    else:
         filehandle.write(")\n")
Oder gibt es eine Routine, die ein Dictionary in XML wandelt?
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 14. Oktober 2015, 13:48, insgesamt 1-mal geändert.
BlackJack

@Alfons Mittelmeyer: Den Begründungsschritt von JSON mit goIn und goOut zu XML um die Baumstruktur wiederzugeben kann ich jetzt nicht ganz nachvollziehen. Die Baumstruktur kann man in JSON doch genau wie in XML abbilden. Also Dein Button-Beispiel um einen `Frame` erweitert wo der Button drin angezeigt wird:

Code: Alles auswählen

{
  "class": "Frame",
  "name": "frame_one",
  "layout": {
    "type": "pack",
    "side": "top"
  }
  "children": {
    "class": "Radiobutton",
    "name": "button_one",
    "config": {
      "text": "One",
      "underline": 0,
      "anchor": "w"
    },
    "layout": {
      "type": "grid",
      "sticky": "ew",
      "row": 1
    }
  }
}
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@BlackJack ja so ginge es. Noch eine Frage, hier sieht man es nicht deutlich, weil wuf die Buttons schon in der Packreihenfolge benannt hatte:

Code: Alles auswählen

class MainFrame(tk.Frame):
 
    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.button_01 = tk.Button(self,**{'text': 'Button-1'})
        self.button_02 = tk.Button(self,**{'text': 'Button-2'})
        self.button_03 = tk.Button(self,**{'text': 'Button-3'})
        self.button_01.pack(side='left')
        self.button_02.pack(side='left')
        self.button_03.pack(side='left')
Aber ich war da etwas faul und habe die Buttons zuerst aus dem Dictionary genommen und erst ganz zum Schluss den Pack aus einer sortierten Packliste draufgesetzt. Ginge das mit JSON auch oder sollte man die Widgets statt alphapetisch in der richtigen Packreihenfolge sortieren (gilt auch für Panes und Menüeinträge).

Ach so sortieren ginge eh nicht, weil das ja auch ein Dictionary war, was Du vorgeschlagen hattest. Man müßte da dann auch so eine Packliste im Container haben, in der man dann die Widgets in der richtigen Reihefolge benennt inclusive Layout Information.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Den Begründungsschritt von JSON mit goIn und goOut zu XML um die Baumstruktur wiederzugeben kann ich jetzt nicht ganz nachvollziehen.
Also der Befehl goIn() in meinen GUI Scripts hat die gleiche Funktion wie Dein Eintrag 'children'. Ein goOut() braucht man für das JSON Format allerdings nicht, weil das gleichbedeutend mit dem Ende des Dictionaries für die children ist.

Also, ich würde sagen, man braucht noch einen Eintrag für die Reihenfolge - etwa bei Pack, Pane, MenuItem. Dann läßt sich das leicht generieren sowohl hin wie auch zurück.
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 14. Oktober 2015, 14:31, insgesamt 1-mal geändert.
Antworten