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.
Beispiele für komplexere Tk GUIs ?!?
-
- 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
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
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Irgendwie zeichnest Du Dich durch totale Begriffsstutzigkeit aus.Sirius3 hat geschrieben:@Alfons Mittelmeyer: solange das mit dem appcode noch auf dem Kopf steht, würde ich Deinen GUI-Designer nicht weiterempfehlen.
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()
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'
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 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.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
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.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...
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.
-
- 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.
@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.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
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.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.
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.
-
- 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:
PS:
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?
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()
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')
@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:
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()
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
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: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.
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)
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])
@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`!
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()}
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Ich hatte das gedacht, weil da immer wieder picuntu als Vorbild genannt wurde. Oder habe ich dieses mißverstanden?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?
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
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.Sirius3 hat geschrieben: Will man automatisch erzeugte Klassen von selbstgeschriebenen trennen, dann geht das am besten über Vererbung:
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.
-
- 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.
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.
-
- 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:
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
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
@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.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Und Du hast bisher nichts vorgeschlagen, was im Widerspruch zum GuiDesigner steht. Was war denn das?Alfons Mittelmeyer hat geschrieben:Sirius3 hat geschrieben:Und nichts von der Funktionalität, die Du beschreibst, steht im Widerspruch zu dem, was ich vorschlage.
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()
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?