Beispiele für komplexere Tk GUIs ?!?

Fragen zu Tkinter.
David_123
User
Beiträge: 8
Registriert: Montag 26. Juni 2017, 12:40

Danke Alfons,
ich will es zum lernen erst mal so einfach wie möglich halten. Um zu verstehen, was da passiert ist es sinnvoll alles von Hand selbst zu programmieren, denke ich. Ich schaue mir Deinen Designer aber auch gerne mal an.
David_123
User
Beiträge: 8
Registriert: Montag 26. Juni 2017, 12:40

Alfons Mittelmeyer hat geschrieben:@David_123: dieser Thread ist doch schon uralt.
Da kann man mir auch nicht vorwerfen die Suchfunktion vom Forum nicht genutzt zu haben :wink:
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@David_123: dieser Thread ist doch schon uralt.
Aber schau Dir doch mal meinen GuiDesigner an. Da fehlt es noch an Doku.
Aber das Grid Layout ist ganz schön beschrieben.

https://github.com/AlfonsMittelmeyer/py ... -messaging

Und komplexe GUIs sind damit kein Problem. Denn für komplexe GUIs und klare Strukturierung ist er gemacht und globaler Murks wird nicht gefördert - obwohl natürlich auch da mit Gewalt möglich.

Wie Du die GUI aufbaust, ist an sich egal. Änderung im GUI Designer zu jeder Zeit möglich. Wo der Code ist, ist auch egal, soll sich nur in einem anderen Modul befinden, schon deswegen, damit er nicht bei Neugenerierung der GUI überschrieben wird, kann beliebig in verschiedene Module verteilt sein. Das Prinzip: zu jedem Container Widget gibt es eine Fortführung der __init__ als Funktion oder Klasse.
Das trägt man bei config in 'call Code(self)' ein, also etwa 'codemodul.application_init' oder auch ''codemodul.application_init(self,'grün')

Direkte Zugriffe auf andere Containerwidgets und deren children soll man vermeiden und stattdessen einen Eventbroker verwenden:
viewtopic.php?f=18&t=41058#p313536

Ansonsten hätte man eine Abhängigkeit vom GUI Aufbau.

Ein besserer Eventbroker als dieser hier ist beim GuiDesigner mit enthalten. Dürfte Communication/eventbroker.py sein
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: solange das mit dem appcode noch auf dem Kopf steht, würde ich Deinen GUI-Designer nicht weiterempfehlen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: solange das mit dem appcode noch auf dem Kopf steht, würde ich Deinen GUI-Designer nicht weiterempfehlen.
Irgendwie zeichnest Du Dich durch totale Begriffsstutzigkeit aus.

Ist Dir schon mal in den Sinn gekommen, dass man statt das aufzurufen:

python message_gui.py

Das auch so aufrufen kann?

Code: Alles auswählen

import message_gui

import tkinter as tk

def main():

    root = message_gui.Application()
    ....
    root.mainloop()

if __name__ == '__main__':
    main()
Und wenn Du willst, kann ich Dir auch zeigen, wie man mit dem GuiDesigner auch den globalen Murks wie in pygubu hinbekommt, nämlich globale Widget IDs (wenn auch in pygubu) und globale Callbacks, zumindest alle in einer Klasse, viel Spaß, wenn es mal hundert sein sollten.

Auch mit dem GuiDesigner kann man in der __init__ globale IDs in ein Dictionary schreiben. Mit callbacks kann man das auch so machen. Die Fortführung der __init__ muss auch nicht in einem extra File sein. Die kann man zunächst im Gui Source File schreiben und dann kopiert man sich die im GuiDesigner in das Feld 'methods'. Also derselbe globale Murks ist auch mit dem GuiDesigner machbar.

Besonders unterstützt wird es freilich nicht.

Warum soll man eine Klasse haben mit hundert callbacks und dem Callback Dictionary übergibt man dann die Callback IDs und die Callback Referenzen? Oder will man die Callbacks über mehrere Klassen einsammeln gehen?

Auch in pygubu kann man allerdings auch nicht global programmieren, indem man sich seine Gui aus Einzelteilen zusammenfitzelt. Das muss aber nicht sein. Solche Unterteilungen der GUI in einzelne Module kann man auch bereits im GuiDesigner vornehmen. Hierfür gibt es den Eintrag 'baseclass'
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Alfons Mittelmeyer hat geschrieben: Und wenn Du willst, kann ich Dir auch zeigen, wie man mit dem GuiDesigner auch den globalen Murks wie in pygubu hinbekommt, nämlich globale Widget IDs (wenn auch in pygubu) und globale Callbacks, zumindest alle in einer Klasse, viel Spaß, wenn es mal hundert sein sollten.
Namensraeume, die innerhalb eines anderen Namensraumes stecken, sind *nicht* globale Namensraeume.... aber das du gobalsteniker bist hast du ja schon oft genug belegt...
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Alfons Mittelmeyer hat geschrieben:
Alfons Mittelmeyer hat geschrieben:
__deets__ hat geschrieben:Namensraeume, die innerhalb eines anderen Namensraumes stecken, sind *nicht* globale Namensraeume.... aber das du gobalsteniker bist hast du ja schon oft genug belegt...
Ja natürlich, ob man ein einziges Mainscript hat oder ein Mainscript mit einem einzigen main. Ja bei main ist es nur mehr lokal. Aber im Prinzip ist das doch wohl kein Unterschied oder? Aber es gibt eben Fanatiker, die da sagen, ja das eine ist global and das darf nicht sein. Aber das andere ist lokal und das darf dagegen sein. Aber vom Prinzip her ist da Null Unterschied. Wir reden da von einem Script, auf das niemand von außerhalb zugreifen kann. Ja bei einem Modul, wären globale Variablen freilich etwas anderes, da dann jeder der es importiert, diese verändern könnte.

Und ich bin kein Globalsteniker. Ich sage, beides ist global und beides soll nicht sein.

Es geht darum, ob eine Funktion oder Klasse nur ein Containerwidget mit seinen Children behandelt oder ob man kreuz und quer auf die ganze GUI zugreift.

Wenn die GUI größer wird, und man das trennen möchte, wird es äußerst schwer, einen globalen Verhau wieder auseinanderzufieseln.
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 16. August 2017, 18:54, insgesamt 1-mal geändert.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Wir reden da von einem Script, auf das niemand von außerhalb zugreifen kann. Ja bei einem Modul, wären globale Variablen freilich etwas anderes, da dann jeder der es importiert, diese verändern könnte.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: ich verstehe überhaupt nicht, von was Du da redest. Ich hab Dir an anderer Stelle ausführlich geschrieben, was an Deinem Konzept schlecht ist. Und das hat mal ausnahmsweise nichts mit globalen Variablen zu tun.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: ich verstehe überhaupt nicht, von was Du da redest. Ich hab Dir an anderer Stelle ausführlich geschrieben, was an Deinem Konzept schlecht ist. Und das hat mal ausnahmsweise nichts mit globalen Variablen zu tun.
Und ich habe Dir geschrieben dass man es auch anders aufrufen kann, nämlich zuerst eine main von Dir und dann die GUI importieren. Daß also, was Du als schlecht bezeichnet hast, gar nicht zutrifft.

Aber was soll der Zweck sein, es so zu tun, wenn Du keine globalen Zugriffe brauchst. Damit meine ich keine globale Variablen sondern IDs für die gesamte GUI und Callbacks für die ganze GUI. Das kann man auch als etwas Globales bezeichnen, wenn es auch keine globalen Variablen sind.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: auch mit meinem GuiDesigner kann man es so machen, wenn man das unbedingt so haben will:

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        # widget definitions ===================================
        self.hello_label = tk.Label(self,text='...', font='TkDefaultFont 30 {}')
        self.push_button = tk.Button(self,text='push button')
        self.hello_label.pack()
        self.push_button.pack(fill='x')

        # auch das kann man in den GuiDesigner eintragen, hierfür gibt es den Eintrag 'methods' ====
        self.ids = {}
        self.callbacks = {}

        self.ids['hello_label'] = self.hello_label
        self.push_button['command'] = lambda callbacks = self.callbacks: callbacks['push_button']()

class Main():

    def __init__(self):

        self.root = Application()
        self.root.callbacks.update(**{ 'push_button' : self.push_button })
        self.root.mainloop()

    def push_button(self):
        label = self.root.ids['hello_label']
        label['text'] = 'Hello World'

if __name__ == '__main__':
    Main()
PS:

Code: Alles auswählen

# Wenn es schöner aussehen soll, kann man sich noch ein Modul machen, sodaß man statt

label = self.root.ids['hello_label']

# auch schreiben kann:

label = builder.create(self.root,'hello_label')
Willst Du genau wissen, wie man so etwas macht, mit einem Modul builder.py, damit es dann auch schön aussieht und man das dann auch mit lambda nicht seiber schreiben muss?
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: das entfernt sich doch von schöner immer weiter. `self.ids['hello_label']` ist häßlicher als `self.hello_label`, weil mehr Schreibarbeit und indirekter. Genauso der Umweg über das `callbacks`-Wörterbuch. Die update-Methode, die Du verwendest ist übrigens die komplizierteste und unleserlichste Art, wie man einen Eintrag setzt. Die Main-Klasse ist überflüssig und falsch, weil push_button eine Methode von Application sein sollte. Das mit dem `builder.create` kommentier ich erst gar nicht, weil da ja nichts kreiert wird. __init__ soll ein Objekt initialisieren und nicht ewig in einer Schleife hängen bleiben, und eine Instanz, die erzeugt wird aber dann keiner Variablen zugewiesen wird, ist unerwartet.

Will man automatisch erzeugte Klassen von selbstgeschriebenen trennen, dann geht das am besten über Vererbung:

Code: Alles auswählen

# -*- coding: utf-8 -*-
import tkinter as tk
 
class DesignerApplication(tk.Tk):
    # this is an automatically generated class, do not change
    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.hello_label = tk.Label(self,text='...', font='TkDefaultFont 30 {}')
        self.push_button = tk.Button(self,text='push button')
        self.hello_label.pack()
        self.push_button.pack(fill='x')

        
class Application(DesignerApplication):
 
    def __init__(self):
        super(Application).__init__()
        self.push_button['command'] = push_button
 
    def push_button(self):
        self.hello_label['text'] = 'Hello World'
 
def main():
    root = Application()
    root.mainloop()
 
if __name__ == '__main__':
    main()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: das entfernt sich doch von schöner immer weiter. `self.ids['hello_label']` ist häßlicher als `self.hello_label`, weil mehr Schreibarbeit und indirekter. Genauso der Umweg über das `callbacks`-Wörterbuch. Die update-Methode, die Du verwendest ist übrigens die komplizierteste und unleserlichste Art, wie man einen Eintrag setzt.
Ich habe hier nur das Prinzip gezeigt, aber wenn Du Dir selber ein Modul builder.py schreibst, das ist gar nicht schwer, kannst Du es auch so ausdrücken, wie in pygubu:

Code: Alles auswählen

# In pygubu heißt es statt:
label = self.root.ids['hello_label']

# so
label = self.builder.get_object('hello_label')

# und statt:
self.root.callbacks.update(**{ 'push_button' : self.push_button })

# übergibt man in pygubu das Dictionary aber nur wenn man Funktionen statt Methoden verwendet

# Configure callbacks
callbacks = {
    'on_button1_clicked': on_button1_click,
    'on_button2_clicked': on_button2_click,
    'on_button3_clicked': on_button3_click
}

builder.connect_callbacks(callbacks)
Siehe: https://raw.githubusercontent.com/wiki/ ... perties.py

Bei Methoden geht es auch einfacher, da braucht man das Dictionary nicht übergeben, sondern nur das self. Das sollte sich aber auch hinbekommen lassen, denn für solche Fälle gibt es schließlich im builder dann eval. Dazu läßt man das Dictionary im Designer nicht leer sondern trägt da schon mal den key ein mit value None und im Builder wird dann der Value bei connect_callbacks(self) ersetzt:

Code: Alles auswählen

callbacks[key] = eval('self.'+callbacks[key])
Man kann sich das mit geringem Aufwand selber machen. Aber dass ich im GuiDesigner so etwas automatisch generiere aus eintragbaren Widget IDs, command IDs und Event IDs, und so ein builder Modul zur Verfügung stelle: schlichtweg Nööööö
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: wie ich schon geschrieben habe, will das auch niemand so nutzen. Wie kommst Du auf die Idee, hier möchte irgendwer mit IDs rumspielen?

Vor allem nicht `eval`!

Code: Alles auswählen

# statt
callbacks[key] = eval('self.'+callbacks[key])
# schreibt man
callbacks[key] = getattr(self, callbacks[key])
# oder compakt
callbacks = {key: getattr(self, method) for key, method in callbacks.items()}
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: wie ich schon geschrieben habe, will das auch niemand so nutzen. Wie kommst Du auf die Idee, hier möchte irgendwer mit IDs rumspielen?
Ich hatte das gedacht, weil da immer wieder picuntu als Vorbild genannt wurde. Oder habe ich dieses mißverstanden?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben: Will man automatisch erzeugte Klassen von selbstgeschriebenen trennen, dann geht das am besten über Vererbung:
Es ist kein Problem, das bei der Applikationsklasse zu machen. Nur wenn die Applikationsklasse wieder Containerwidgets mit Widgets hat, dann hast Du zwar jetzt eigene Methoden für die Applikationsklasse aber für die Container Widget Klassen hast Du sie nicht, weil die beim Aufruf der Applikationsklasse gleich mit erzeugt werden, wenn man die GUI nicht zerfitzelt.

Man kann aber im GuiDesigner eigene Widget Klassen einbinden. Das geht über baseclass. Sagen wir, es wäre von LabelFrame abgeleitet, dann kann man im GuiDesigner zumindest einen leeren LabelFrame sehen, sofern der bei grid auch ein sticky 'nesw' hat und nicht einen size von width = 1, height = 1. Naja, Im Verzeichnis sieht man ihn dann zumindest auch. Und generiert wird dann die Einbindung Deiner Klasse, die Du dann natürlich wieder von einem generierten LabelFrame in einem anderen Modul ableiten kannst.

Naja, wenn man alles sehen will, kann man natürlich generieren und aufrufen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also der GuiDesigner unterstützt die Aufteilung der GUI in kleinerer Einheiten, die man dann getrennt generiert und deren Einbindung und auch, dass man selbst geschriebene Widget Klassen dabei einbinden kann. Es kann vorkommen, dass man doch eine Methode braucht, die von außen aufrufbar ist. Ein Containerwidget sollte etwa mehrmals als Basisklasse in der GUI verwendet werden. Dabei sollten die verschiedenen Instanzen aber auf verschiedene Messages reagieren.

Daher wurde dann in methods eine Methode eingetragen, die man dann nach Erzeugung des Container Widgets aufrufen konnte um dann die Message ID zu übergeben.

Gedacht ist methods für solche Sonderfälle und nicht, dass man dann in einem Text Entry des GuiDesigners seinen Code schreibt. Syntaxhervorhebung hat man da nämlich nicht. Da ist der Programmeditor doch besser. Man könnte natürlich seinen Code im Programmeditor verändern und dann wieder in methods kopieren. Aber das vergißt man eventuell auch.

Ich sehe es als sinnvoll an, da besser eine Funktion oder Klasse in einem anderen Modul aufzurufen, da wird dann ganz bestimmt nichts überschrieben.

Manchmal möchte man doch auch einen größeren Teil der GUI im GuiDesigner komplett dargestellt sehen und bearbeiten können.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Wenn man eine manuell geschriebene Containerklasse im GUI Designer als 'baseclass' einbindet, sieht man dort zwar deren Inhalt nicht. Aber trotzdem mit cut und paste kann man sie auch woanders platzieren.

Es gibt aber auch die Möglichkeit einen Labelframe mit inhalt zu machen und dazu eine baseclass anzugeben, dann hat man einen LabelFrame mit eventuell erweiteren Methoden. Da hatte ich noch gar nicht daran gedacht. Vielleicht schöner als so eine Klasse:

Code: Alles auswählen

class MyCode:
    def __init__(self,container):
        self.container = container
Dumm ist bei dem zuvor genannten Verfahren allerdings, dass wenn die __init__ der Basisklasse aufgerufen wird, die widgets noch gar nicht da sind. Da bräuchte man noch ein zweites after_init(self). Aber zumindest hätte man dann das richtige self und nicht self.container
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: Du schreibst die ganze Zeit etwas, was man alles total modular und komplex machen könnte, aber jedes mal, wenn Du irgendeinen Code zeigst, ist das umständlicher als es eigentlich sein müßte. Und nichts von der Funktionalität, die Du beschreibst, steht im Widerspruch zu dem, was ich vorschlage.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Alfons Mittelmeyer hat geschrieben:
Sirius3 hat geschrieben:Und nichts von der Funktionalität, die Du beschreibst, steht im Widerspruch zu dem, was ich vorschlage.
Und Du hast bisher nichts vorgeschlagen, was im Widerspruch zum GuiDesigner steht. Was war denn das?

Code: Alles auswählen

class Application(DesignerApplication):
 
    def __init__(self):
        super(Application).__init__()
        self.push_button['command'] = push_button
 
    def push_button(self):
        self.hello_label['text'] = 'Hello World'
 
def main():
    root = Application()
    root.mainloop()
 
if __name__ == '__main__':
    main()
Schreibst Du eben in Zeile 1:

class Application(myapplication.DesignerApplication)

Und myapplication.py ist vom GuiDesigner generiert. Kannst Du doch machen. Wo ist da irgendein Widerspruch, dass Du etwas nicht machen könntest?

Es ist nur so, dass der GuiDesigner diesen globalen ID Murks a la pygubu kaum unterstützt. Machbar ist es aber, wenn jemand die entsprechenden Interfaces in einem Modul dazu implementiert, damit man einfacher darauf zugreifen kann.

Aber äußere Dich einmal dazu. Die Applikation hat jetzt auch Containerwidgets, die wieder Widgets enthalten. Was machst Du da?
Antworten