Grid, individuell einstellbar für Zeilen und Spalten

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

Grid, individuell einstellbar, sieht man hier:
Bild
Die neue Version meines GUI Designers bietet das jetzt: https://github.com/AlfonsMittelmeyer/py ... -messaging
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Habe noch einen Bug beseitigt. Wenn man zuvor Place Layout gehabt hatte mit Mouse on und machte dann ein Menü und kehrte direkt zurück mit Mausklick auf das betreffende Objekt (mit mouse on und place oder grid layout), dann gab es einen Fehler.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Noch ein Bug Fix: Toplevel Windows konnten nicht gespeichert werden. Fehler wurde beseitigt.

Ach so, Toplevel Windows werden nicht einfach beim Menüpunkt File Save mitgespeichert. Sie müssen extra gespeichert werden.
Dazu über Menupunkt Special Toproot erst in das Toplevel Window wechseln und dann mit File Save speichern. (Sie werden sozusagen wie eine eigene Applikation behandelt)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Kleine Änderung: für Grid Hintergrund werden jetzt Frames statt Labels benutzt. Die Labels hatten eine einzeilige Höhe und damit war die Höhe nicht gegen 0 stellbar sondern nur minimal 12 oder 13.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Möglichkeiten, eine mit dem GUI Designer erstellte GUI in sein Programm einzubinden:

Möglichkeit 1a:

Code: Alles auswählen

import DynTkInter as tk
tk.Tk(link = 'mygui.gui').mainloop()
In dieser Form nicht unbedingt sinnvoll, weil man da nichts Weiteres hinzufügen kann.

Möglichkeit 1b aber wäre passend:

Code: Alles auswählen

import DynTkInter as tk

root = tk.Tk(link = 'mygui.gui')

# my code

root.mainloop()
Möglichkeit 2a paßt ebenso:

Code: Alles auswählen

import DynTkInter as tk

root = tk.Tk()

tk.DynLoad('mygui.gui')

# my code

root.mainloop()
Möglichgkeit 2b ergibt beim Laden in das root window keinen Unterschied zu 2a. Ein Unterschied würde bestehen, wenn wir gezielt partiell laden, etwa in Frames:

Code: Alles auswählen

import DynTkInter as tk

root = tk.Tk()

tk.DynLink('mygui.gui')

# my code

root.mainloop()
Möglichkeit 3 ist ähnlich wie Möglichkeit 1a. Erlaubt keinen Zugriff auf die Widgets zuvor. Ein Zugriff auf die Widgets könnte erst einige Zeit nach Start von mainloop erfolgen, weil hier das GUI Script erst einige Zeit nach start von mainloop geladen wird:

Code: Alles auswählen

import DynTkInter as tk
tk.Tk().mainloop('mygui.gui')
Möglichkeiten 1a und 3 sind natürlich nicht unsinnig. Wenn man die Programmierung bereits in den gui Scripts erledigt hat und deshalb in der App nichts mehr braucht, kommen auch 1a und 3 in Betracht.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Alfons

Jetzt wird es richtig interessant! Genau dies wollte ich dich als nächstes fragen wie ein Gui-Skript, welches mit dem DynTkinter Designer erzeugt wurde in ein eigenes Skript eingebunden wird.

Ich erstellte folgendes Skript mit dem Designer:

Code: Alles auswählen

config(**{'title': 'Buttontest 1'})

Frame('main_frame')
goIn()

Button('button_01',**{'text': 'Button-1'})
Button('button_02',**{'text': 'Button-2'})
Button('button_03',**{'text': 'Button-3'})

widget('button_01').pack(side='left')
widget('button_02').pack(side='left')
widget('button_03').pack(side='left')

goOut()


widget('main_frame').pack()
Der Gui-Aufbau ist simpel und besteht eigendlich nur aus dem Hauptfenster (Buttontest-1), einem Frame (main_frame), welches drei nebeneinander mit pack platzierten Buttons (button_01, button_02, button_03)

Nun folgt mein einfaches Skript mit welchem ich nur die Beschriftung des ersten Buttons (button_01) von Button-1 auf Hello mittels button_01.configuration(text='Hello') ändern möchte.:

Code: Alles auswählen

import DynTkInter as tk
 
root = tk.Tk(link = 'button_test_01.py')

# my code
button_01.configure(text='Hello') 

 
root.mainloop()
Die Gui wird richtig angezeigt aber es wird folgende Exception ausgelöst:
NameError: name 'button_01' is not defined
Habe ich hier etwas übersehen?

N.B. Als weiteres würde mich noch interessieren wie ich einen callback von den Buttons zu meinem Skript realisieren müsste?

Gruss wuf :wink:
Take it easy Mates!
Sirius3
User
Beiträge: 18328
Registriert: Sonntag 21. Oktober 2012, 17:20

@wuf: Du definierst auch nirgends button_01. Was soll übrigens diese umständliche Schreibweise von keyword-Argumenten: config(**{'title': 'Buttontest 1'})?
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Sirius3
Sirius3 hat geschrieben:@wuf: Du definierst auch nirgends button_01. Was soll übrigens diese umständliche Schreibweise von keyword-Argumenten: config(**{'title': 'Buttontest 1'})?
'button_01' ist im Skript, welches du als umständliche Schreibweise von keyword-Argumenten bezeichnest definiert. Dieses Skript wurde durch den Designer von Alfons Mittelmeyer generiert. Vielleicht kannst du dir einmal das ganze DynTkinter Designer Projekt als Zip-Archiv von GitHub herunterladen und dich dort schlau machen oder last but not least einfach unseren Forum-Kollege Alfon Mittelmeyer fragen.

Gruss wuf :wink:
Take it easy Mates!
Sirius3
User
Beiträge: 18328
Registriert: Sonntag 21. Oktober 2012, 17:20

@wuf: dort gibt es nur einen String 'button_01' und wie soll daraus im Hauptscript ein Variablennamen werden? Du kannst auf den Button nur über die umständliche Schreibweise "widget('button_01')" auf den Knopf zugreifen, was jeden irgendwann einmal nervt, weshalb der ganze DynTkinter-Ansatz ungünstig ist.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

@Sirius3: Hast du dich auch einmal gefragt was dieser Import soll:

Code: Alles auswählen

import DynTkInter as tk
Take it easy Mates!
Sirius3
User
Beiträge: 18328
Registriert: Sonntag 21. Oktober 2012, 17:20

@wuf: es importiert DynTkInter als tk, damit Du es eine Zeile später benutzen kannst. Im Gegensatz dazu wird in button_test_01.py gar nichts importiert, womit diese Datei kein Python ist. Ein durchdachter Ansatz würde soetwas wie

Code: Alles auswählen

import DynTkInter as tk

class MainFrame(tk.Frame):
    def __init__(self):
        tk.Frame.__init__('main_frame')
        self.button_01 = tk.Button('button_01', text='Button-1')
        self.button_01.pack(side='left')
        self.button_02 = tk.Button('button_02', text='Button-2')
        self.button_02.pack(side='left')
        self.button_03 = tk.Button('button_03', text='Button-3')
        self.button_03.pack(side='left')
produzieren, dann kann man im Hauptprogramm auch über die Attribute auf den Knopf zugreifen, wie man das in Python halt so macht.
BlackJack

Sirius3: Ich glaube Du hast da `goIn()` und `goOut()` vergessen. Wobei hier dann natürlich das erste Argument von den `DynTkinter`-Widgets irgendwie sinnfrei ist, womit sich wieder die Frage stellt was das ``import DynTkinter as tk`` soll, statt das normale `Tkinter`-Modul zu verwenden. :-)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@ hi wuf,

heute musst Du das noch so machen:

Code: Alles auswählen

import DynTkInter as tk
 
root = tk.Tk(link = 'button_test_01.py')
 
tk.goto('main_frame')
tk.goIn()
button_01 = tk.widget('button_01')

# my code

button_01.configure(text='Hello')
 
root.mainloop()
Aber in der GUI spazieren gehen ist nicht unbedingt die beste Lösung. Deshalb kommen morgen noch Access Funktionen für eine weit verzweigte GUI. Und ich denke mal Container Widget Ebene genügt. Man könnte natürlich diese Access Functions auch bis auf widget Ebene anbieten. Wäre sinnvoll für kleine GUIs wie Deine, nämlich dass Du gleich Deine Widgets bekommst, etwa mit:

Code: Alles auswählen

access = DynLoad('button_test_01.access')

access.button_01.configure(text='Hello')
Heute nicht probieren, haben wir heute noch nicht.

Für eine weit verzeigte GUI dagegen wäre sinnvoll, gleiche Name in unterschiedlichen Containern durchaus zuzulassen. Da würde das dann so aussehen:

Code: Alles auswählen

widget(access.main_frame,'button_01').configure(text='Hello')
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Alfons

Danke so funktionierts. Die Endung .py meiner link-Datei button_test_01.py war eine unglückliche Wahl sie sollte eigentlich button_test_01.gui heissen.

Gruss wuf :wink:
Take it easy Mates!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hi wuf, das mit der Endung habe ich mir auch gedacht. Nur bekomme ich kein Syntax Highlighting für Python bei der Endung .gui.
Und den ganzen GUI Designer hatte ich ja lediglich als GUI Scripts geschrieben und brauchte da Syntax Highlighting.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hi wuf,

hab in den GUI Designer noch die Erzeugung einer Access Klasse eingebaut. Leider gehen bei mir von Menüs keine Screenshots. Die neuen Funktionen findest Du im Menu unter File -> Save Access.
Und zwar gibt es den Access für Widget Depth und Container Depth.

Hab dann für Dein Script die zwei Access Files erzeugt, test.py für Widget Tiefe und test2.py für Container Tiefe. Mit DynLoad war ein Rückgabewert nicht möglich, deshalb habe ich dann eine neue Ladefunktion DynAccess implementiert. Für Dein Programm gibt es dann diese zwei Fassungen.

Access bis auf Widget Tiefe:

Code: Alles auswählen

import DynTkInter as tk
 
root = tk.Tk(link = 'button_test_01.py')
 
access = tk.DynAccess('test.py') 
button_01 = access.button_01

# my code

button_01.configure(text='Hello')
  
root.mainloop()
Access bis Container Tiefe:

Code: Alles auswählen

import DynTkInter as tk
 
root = tk.Tk(link = 'button_test_01.py')
 
access = tk.DynAccess('test2.py') 
button_01 = tk.widget(access.main_frame,'button_01')

# my code

button_01.configure(text='Hello')
 
root.mainloop()
Ach so, hab die Funktionalität der Funktion 'widget' auch geändert. Wenn der erste Parameter kein String ist, dann ist es der Parent. Und wenn man sich dann die widgets besorgt, kann man sie dann als Parent nehmen und so auch tiefer in die GUI eindringen. Ach, dann hätte ich ja glatt meine Acces Files auch ein wenig kürzer und übersichtlicher schreiben können.
Zuletzt geändert von Alfons Mittelmeyer am Donnerstag 8. Oktober 2015, 03:08, insgesamt 1-mal geändert.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hi wuf,

zu Deinem Beispiel fällt mir noch etwas Lustiges ein. Du könntest das ja nochmal in den GUI Designer laden. Und wenn dann main_frame ausgewählt ist - das ist ein Container Widget. Und bei Container Widgets kann man dann im Menü auswählen: Special -> Expert Options -> Code.

Und wenn Du das auswählst, geht ein Fenster auf. Da könnest Du mal das hineinkopieren und danach OK drücken:

Code: Alles auswählen

widget('button_01')['command'] = lambda: print('Hello World')
Und dann schaust Du mal, was passiert, wenn Du den Button 1 drückst.

Und wenn Du das Script dann abspeicherst und dann abwechselnd mal mit 'Load & Run' und 'Load & Edit' lädst, kommst Du wahrscheinlich drauf, was da der Unterschied ist.
Und bei der Selektion siehst Du dann bei main_frame eine blaue Umrandung. Das bedeutet auch etwas.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@wuf: es importiert DynTkInter als tk, damit Du es eine Zeile später benutzen kannst. Im Gegensatz dazu wird in button_test_01.py gar nichts importiert, womit diese Datei kein Python ist. Ein durchdachter Ansatz würde soetwas wie

Code: Alles auswählen

import DynTkInter as tk

class MainFrame(tk.Frame):
    def __init__(self):
        tk.Frame.__init__('main_frame')
        self.button_01 = tk.Button('button_01', text='Button-1')
        self.button_01.pack(side='left')
        self.button_02 = tk.Button('button_02', text='Button-2')
        self.button_02.pack(side='left')
        self.button_03 = tk.Button('button_03', text='Button-3')
        self.button_03.pack(side='left')
produzieren, dann kann man im Hauptprogramm auch über die Attribute auf den Knopf zugreifen, wie man das in Python halt so macht.
@Sirius3
Genau das ist die Idee für eine Exportfunktion nach der ich schon gesucht hatte. Wenn man von innen nach außen Klassen für die Container definiert, braucht man am Ende nur noch die Init der App aufrufen und hat dann die GUI als statisches tkinter. Ich muss aber vorläufig dabei noch bei DynTkInter bleiben, da ich Menüitems wie command usw. als Klassen definiert habe.

Kann ich bei Gelegenheit dann auch als tkinter rückübersetzen.

Und wenn man wie in Deinem Beispiel bei dem Namen bleibt statt dem Parent, kann man das auch wieder mit dem GUI Designer editieren mit Erhalt des Namens.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hi wuf,

der Vorschlag von Sirius3 ist gut. Aber wenn man nicht diese Befehle wie goIn() usw. verwenden will, ist der Parent jeweils anzugeben.

DynTkInter erlaubt Angabe:

- des Parents
- des Namens (als String)
- Tuple aus Parent und Name

Mit dem Tuple bekommen wir die Lösung:

Code: Alles auswählen

import DynTkInter as tk
 
class MainFrame(tk.Frame):
    def __init__(self,master):
        tk.Frame.__init__(self,(master,'main_frame'))
        self.button_01 = tk.Button((self,'button_01'), text='Button-1')
        self.button_01.pack(side='left')
        self.button_02 = tk.Button((self,'button_02'), text='Button-2')
        self.button_02.pack(side='left')
        self.button_03 = tk.Button((self,'button_03'), text='Button-3')
        self.button_03.pack(side='left')
        
root = tk.Tk()

main_frame = MainFrame(root)
main_frame.pack()

root.mainloop('guidesigner/Guidesigner.py')
Zu der Exportfunktion die dieses erzeugt, werde ich heute aber wahrscheinlich nicht mehr kommen, denn das erfordert noch ein paar Überlegungen.

Oder wäre es dann richtiger so?

Code: Alles auswählen

import DynTkInter as tk
 
class MainFrame(tk.Frame):
    def __init__(self,master):
        tk.Frame.__init__(self,(master,'main_frame'))
        self.button_01 = tk.Button((self,'button_01'), text='Button-1')
        self.button_01.pack(side='left')
        self.button_02 = tk.Button((self,'button_02'), text='Button-2')
        self.button_02.pack(side='left')
        self.button_03 = tk.Button((self,'button_03'), text='Button-3')
        self.button_03.pack(side='left')

class Application(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.main_frame = MainFrame(self)
        self.main_frame.pack()

Application().mainloop('guidesigner/Guidesigner.py')
Da wäre schon einiges zu tun, etwa automatische Umwandlung von 'main_frame' in Camel Case 'MainFrame' für die Klasse, oder die im GUI Designer eingeben lassen?

Falsch ist schon mal dieses:

Code: Alles auswählen

class MainFrame(tk.Frame):
    def __init__(self,master):
        tk.Frame.__init__(self,(master,'main_frame'))
MainFrame ist ein Klasse und soll deshalb keinen Objektnamen haben. Richtig wäre es deshalb so:

Code: Alles auswählen

class MainFrame(tk.Frame):
    def __init__(self,master):
        tk.Frame.__init__(self,master)
Und der Aufruf hat dann so zu erfolgen:

Code: Alles auswählen

self.main_frame = MainFrame((self,'main_frame'))
Das heißt, wenn man im GUI Designer den Namen haben will. Sonst heißt dort nämlich ein Frame, einfach nur 'Frame'

Es geht aber auch die für mich gewohnte Reihenfolghe von außen nach innen:

Code: Alles auswählen

import DynTkInter as tk
 

class Application(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self.main_frame = MainFrame((self,'main_frame'))
        self.main_frame.pack()

class MainFrame(tk.Frame):
    def __init__(self,master):
        tk.Frame.__init__(self,master)
        self.button_01 = tk.Button((self,'button_01'), text='Button-1')
        self.button_01.pack(side='left')
        self.button_02 = tk.Button((self,'button_02'), text='Button-2')
        self.button_02.pack(side='left')
        self.button_03 = tk.Button((self,'button_03'), text='Button-3')
        self.button_03.pack(side='left')

Application().mainloop('guidesigner/Guidesigner.py')
Das heißt dann, die Erzeugung wäre kein Problem!

Man geht ein Container Widget zweimal durch. Beim ersten mal schreibt man die Klasse hin. Wenn darin Container Widgets sind, die keine Widgets enthalten, kann man für einen Frame einfach einen tk.Frame nehmen. Ansonsten bildet man einen Klassennamen dafür.

Bei zweiten Durchgang geht man dann in die Subcontainer Widgets hinein und schreibt diese.

Als Script darf man so etwas aber im GUI Designer nicht laden, weil da ein mainloop drin steht und auch eine tk Instanz erzeugt wird.

Nö, als Script laden geht auch. Dazu muß man aber außen noch eine Funktion oder Klasse rum machen, weil sonst 'tk' nicht gekannt wird. Denn Scripts machen keine globalen Definitionen:

Code: Alles auswählen

def main():

    import DynTkInter as tk
     

    class Application(tk.Tk):

        def __init__(self):
            tk.Tk.__init__(self)
            self.main_frame = MainFrame((self,'main_frame'))
            self.main_frame.pack()

    class MainFrame(tk.Frame):
        def __init__(self,master):
            tk.Frame.__init__(self,master)
            self.button_01 = tk.Button((self,'button_01'), text='Button-1')
            self.button_01.pack(side='left')
            self.button_02 = tk.Button((self,'button_02'), text='Button-2')
            self.button_02.pack(side='left')
            self.button_03 = tk.Button((self,'button_03'), text='Button-3')
            self.button_03.pack(side='left')
            
    Application().mainloop()
    
main()
Statt so einer main Funktion oder Klasse könnte man den init Methoden auch tk als Parameter übergeben, damit es gekannt wird, wenn man es als Script lädt, ist aber wohl unschön.

Der Vorteil als Script wäre, dass zusätzlicher Code vom GUI Desgner mit gespeichert werden könnte. Wenn man das Also mit dem GUI DDesigner bearbeiten und speichern würde, könnte zusätzlicher Code dabei erhalten bleiben.

Ich müßte nur die Einrückebene dabei anpassen. Also, was nach Ende von __init__ kommt, könnte man als ### CODE = markieren und das würde dann erhalten bleiben.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ich könnte aber für diese Art von Scripts eine eigene Import Funktion machen. Denn tk.Tk() neu zerstört die Funktion des GUI Designers, weil da das Message System neu initialisiert wird. Die Lösung wäre, dass die Import Funktion die alte App destroyed. Dann das Script lädt und den GUI Designer wieder nachlädt.
Antworten