Beispiele für komplexere Tk GUIs ?!?

Fragen zu Tkinter.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: was meinst Du jetzt schon wieder mit Scripte? Ein größeres Programm wird zwangsläufig aus vielen Modulen bestehen, aber das ist ja auch kein Problem, das stört den Anwender ja auch nicht.

@jens: es ist auf jeden Fall sinnvoll, logisch zusammengehörende Einheiten in einzelne Frames zu packen, um sowohl logisch, als auch im Fenster eine Hirachie aufzubauen. Was mich an Deinem Beispiel etwas stört ist, dass sich die LabelFrames selbst platzieren. Das ist eigentlich die Aufgabe der höheren Instanz. Bei Label oder Button rufst Du ja auch danach die grid-Methode auf.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

@Alfons Mittelmeyer: Dieses "eine Datei == Programm/App" funktioniert ja eh nur bei überschaubaren Dingen. Also quasi fast nie :P

Dann packt man eh sein Kram gescheit als Package in den Python Package Index: https://pypi.python.org/pypi

Dann muß man auch immer an die Zielgruppe denken. Leute unter Linux nutzten einfach pip und alles gut. Dann ist es vollkommen egal, wie viele Dateien das Ding hat...

Für Windows-Otto-Normalo ist das allerdings eine andere Geschichte. Dazu hatte ich auch eine Idee: http://www.python-forum.de/viewtopic.php?f=1&t=35061

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Sirius3 hat geschrieben:@jens: es ist auf jeden Fall sinnvoll, logisch zusammengehörende Einheiten in einzelne Frames zu packen, um sowohl logisch, als auch im Fenster eine Hirachie aufzubauen. Was mich an Deinem Beispiel etwas stört ist, dass sich die LabelFrames selbst platzieren. Das ist eigentlich die Aufgabe der höheren Instanz. Bei Label oder Button rufst Du ja auch danach die grid-Methode auf.
Das ist doch mal ein Sachdienlicher Hinweis :P

Die Platzierungs-Information kommt ja von Außen. Aber unschön und unlogisch ist es schon!
Hab das Beispiel mal geändert:

[codebox=python file=Unbenannt.txt]import tkinter as tk


class Outputs(tk.LabelFrame):
def __init__(self, master):
tk.LabelFrame.__init__(self, master, text="LCD-Ausgabe")

self._lbl = tk.Label(self, text="Foo", anchor="e")
self._lbl.grid(column=0, row=0, sticky="e")

self._var = tk.StringVar()
self._entry = tk.Entry(self, textvariable=self._var)
self._entry.grid(column=1, row=0, sticky="nse")

self._btn = tk.Button(self, text="Zeile löschen")
self._btn.grid(column=3, row=0, sticky="nse")


class Inputs(tk.LabelFrame):
def __init__(self, master):
tk.LabelFrame.__init__(self, master, text="Eingabe")

self._lbl = tk.Label(self, text="Foo", anchor="e")
self._lbl.grid(column=0, row=0, sticky="e")

self._var = tk.StringVar()
self._entry = tk.Entry(self, textvariable=self._var)
self._entry.grid(column=1, row=0, sticky="nse")

self._btn = tk.Button(self, text="Zeile löschen")
self._btn.grid(column=3, row=0, sticky="nse")


class Buttons(tk.LabelFrame):
def __init__(self, master):
tk.LabelFrame.__init__(self, master, text="Ein paar Buttons")

for no, txt in enumerate(["bla blub", "foo bar"]):
self._btn = tk.Button(self, text=txt)
self._btn.grid(column=no, row=0, sticky="nse")

for no, txt in enumerate(["noch einer", "ende"]):
self._btn = tk.Button(self, text=txt)
self._btn.grid(column=no, row=1, sticky="nse")


class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Foo Bar")

self.add_Widgets()

self.mainloop()

def add_Widgets(self):
padding = 5
defaults = {
"ipadx": padding, # add internal padding in x direction
"ipady": padding, # add internal padding in y direction
"padx": padding, # add padding in x direction
"pady": padding, # add padding in y direction
"sticky": tk.NSEW, # stick to the cell boundary
}

self.outputs = Outputs(self)
self.outputs.grid(column=0, row=0, **defaults)

self.inputs = Inputs(self)
self.inputs.grid(column=0, row=1, **defaults)

self.actions = Buttons(self)
self.actions.grid(column=1, row=0, rowspan=2, **defaults)


if __name__ == '__main__':
App()[/code]

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
David_123
User
Beiträge: 8
Registriert: Montag 26. Juni 2017, 12:40

Bin ich froh diesen Thread gefunden zu haben. Ich bin auf der Suche nach Logik!
Nach meinen ersten Schritten in Python mit tkinter GUI und etwas OOP frage ich mich immer wieder wie baue ich das Programm am gescheitesten auf. Ich finde es sehr schwer dazu Hinweise zu finden. Trennen von GUI und Berechnung ist klar (in dem Zusammenhang habe ich mir auch schon was über Model View Controller erzählen lasse). Aber jetzt frage ich mich wie ich das GUI am besten strukturiere. Programm wächst halt langsam und wird immer verknoteter. Dieser Ansatz hier scheint ganz gut zu sein. Danke dafür! Wenn Ihr noch Literaturtipps oder Links zu dem Thema habt, würde ich mich freuen, diese hier zu lesen.
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.
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: 17703
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: 14480
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: 17703
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: 17703
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: 17703
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?
Antworten