Grid, individuell einstellbar für Zeilen und Spalten

Fragen zu Tkinter.
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.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Alfons

Habe noch etwas aufbauend auf die gestrigen Variante ausprobiert. Das Designer Gui-Skript ist nach wie vor button_test_01.py

Code: Alles auswählen

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(expand='1')
Um Callback's von den Buttons zu erhalten habe ich mein button_test_code_01.py Skript noch mit folgendem erweitert:

Code: Alles auswählen

from functools import partial
import DynTkInter as tk

def buttons_callback(button):
    print("You have pressed {}".format(button))

def close_app():
    print('close')
    app_win.destroy()

# designed gui related    
app_win = tk.Tk(link = 'button_test_01.py')
tk.goto('main_frame')
tk.goIn()
button_01 = tk.widget('button_01')
button_02 = tk.widget('button_02')
button_03 = tk.widget('button_03')

# my code
app_win.geometry('300x200')
app_win.protocol("WM_DELETE_WINDOW", close_app)

button_01.configure(text="Hello")
button_01.configure(command=partial(buttons_callback, button_01['text']))
button_02.configure(command=partial(buttons_callback, button_02['text']))
button_03.configure(command=partial(buttons_callback, button_03['text']))
 
app_win.mainloop()
Funktioniert bestens. Jetzt noch eine Frage beim beenden (schliessen des Hauptfensters wirft proxy.py in Zeile-17 folgende Exception:
close
Exception ignored in: <bound method Proxy.__del__ of <proxy.Proxy object at 0x7f4a47d6d438>>
Traceback (most recent call last):
File "/home/nikola/Downloads/python-gui-messaging-master/GuiDesigner/proxy.py", line 17, in __del__
File "/usr/lib/python3.4/tkinter/__init__.py", line 1886, in __getattr__
AttributeError: 'tkapp' object has no attribute 'undo_receiveAll_extern'
Dies kann sicher noch unterdrückt werden oder müssen da noch weitere Abfang-Massnahmen getroffen werden?

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

Hi wuf,

bei mir trat dieser Fehler bisher nicht auf. Aber stimmt, da ist ein Fehler. Diese Zeile hätte nicht aufgerufen werden dürfen.

Der Fehler befindet sich in DynTkInter.py im __init__ von 'class Tk' Bei mir ist es zur Zeit Zeile 718. Da steht fehlerhafterweise:

Code: Alles auswählen

# das ist falsch, das self muss weg
proxy = dynproxy.Proxy(self)

# richtig ist:
proxy = dynproxy.Proxy()
Da hatte ich mich wohl vertippt. Dieser Parameter ist dafür da, diesem EventBroker einen anderen EventBroker mitzuteilen, über den er dann mit noch weiteren EventBrokern in anderen Threads kommuniziert. Doch die GUI App ist kein Eventbroker und hat daher auch nicht diese Methode, die dann nicht aufgerufen werden konnte.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hi wuf,

hab noch eine andere Script Save Routine implementiert. Steht dann morgen als Menüpunkt 'Export (as tk)' zur Verfügung. Damit sieht es dann so aus:

Code: Alles auswählen

import DynTkInter as tk

class Application(tk.Tk):

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

class MainFrame(tk.Frame):

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

Application().mainloop()
Wenn man das mit dem GUI Designer nachbearbeien will, dann nicht als Script laden, sondern mit:

Code: Alles auswählen

Application().mainloop('guidesigner/Guidesigner.py')
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Hi wuf,

ich biete jetzt dann zwei Export Versionen an, nämlich mit und ohne Namen. Und wenn ohne Namen, kann man dann in vielen Fällen auch tkinter statt DynTkInter importieren:

Code: Alles auswählen

import tkinter as tk

class Application(tk.Tk):

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

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')

Application().mainloop()
Antworten