Menüauswahl abfragen

Fragen zu Tkinter.
Benutzeravatar
__deets__
User
Beiträge: 2142
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: Menüauswahl abfragen

Beitragvon __deets__ » Montag 1. Januar 2018, 19:34

Definitiv besser.

Wobei ich sogar noch einen Schritt weiter gehen würde, und das app_win im Konstruktor definieren. Dann die Konstanten in die Klasse ziehen, und darauf mit self.TITLE etc zugreifen.

Die eigentlichen UI-Elemente (app_container etc) würde ich dann in einer Methode init_ui definieren, die per default leer ist.

Von dieser Klasse KANN man dann sinnvoll ableiten. Und sowohl Position, Name, Größe und UI nach Wunsch umgestalten. Damit bleibt dein Template imme gleich, und nur die variablen Anteile werden in der Ableitung definiert.

Last but not least käme noch eine mainloop Methode dazu, die einfach auf self.app-win.mainloop weiterleitet. Grund: von außen soll schon das Applications-Objekt die Schnittstelle darstellen. Wie es das tut ist (theoretisch) dann sein Problem. Faktisch ruft es natürlich den Tkinter mainloop auf.
Benutzeravatar
wuf
User
Beiträge: 1367
Registriert: Sonntag 8. Juni 2003, 09:50

Re: Menüauswahl abfragen

Beitragvon wuf » Montag 1. Januar 2018, 19:57

Hi __deets__

Habe versucht meine letzte Variante auf deine Ratschläge hin anzupassen. Hoffe dich richtig verstanden zu haben.
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. from functools import partial
  5.  
  6. try:
  7.     # Tkinter for Python 2.xx
  8.     import Tkinter as tk
  9. except ImportError:
  10.     # Tkinter for Python 3.xx
  11.     import tkinter as tk
  12.  
  13. APP_XPOS = 100
  14. APP_YPOS = 100
  15. APP_WIDTH = 300
  16. APP_HEIGHT = 200
  17.  
  18.        
  19. class Application(object):
  20.     APP_TITLE = "NewTemplate_01"
  21.    
  22.     def __init__(self):
  23.         self.app_win = tk.Tk()
  24.         self.app_win.protocol("WM_DELETE_WINDOW", self.close_app)
  25.         self.app_win.title(self.APP_TITLE)
  26.         self.app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
  27.         self.app_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
  28.         self.app_win.option_add("*Button.highlightThickness", 0)
  29.        
  30.         self.init_ui()
  31.         self.run_app()
  32.        
  33.     def init_ui(self):
  34.         # Trial container-01
  35.         self.app_container_01 = tk.Frame(self.app_win, bg='yellow', bd=0)
  36.         self.app_container_01.pack(side='left', fill='both', expand=True)
  37.         tk.Button(self.app_container_01, text='Container-01').pack(expand=True)
  38.  
  39.         # Trial container-02
  40.         self.app_container_02 = tk.Frame(self.app_win, bg='steelblue', bd=0)
  41.         self.app_container_02.pack(side='left', fill='both', expand=True)
  42.         tk.Button(self.app_container_02, text='Container-02').pack(expand=True)
  43.    
  44.     def run_app(self):
  45.         self.app_win.mainloop()
  46.            
  47.     def close_app(self):
  48.         # Here do something before apps shutdown
  49.         print("Good Bye!")
  50.         self.app_win.destroy()
  51.  
  52.        
  53. def main():    
  54.     Application()
  55.  
  56.  
  57. if __name__ == '__main__':
  58.     main()      
  59.  
Gruss wuf :wink:
Take it easy Mates!
Sirius3
User
Beiträge: 7044
Registriert: Sonntag 21. Oktober 2012, 17:20

Re: Menüauswahl abfragen

Beitragvon Sirius3 » Montag 1. Januar 2018, 23:05

@__deets__: da wäre ich anderer Meinung. »Application« ist eine Tk-Instanz. Klarer Fall von Vererbung. So wie es jetzt ist, hat man ja immer die Indirektion über app_win (ist das Saarländisch für Apfelwein?), die meiner Meinung nach unnötig und nur umständlich ist. Ein Template ist auch nicht dafür da, dass man davon ableitet, sondern dass man es an die eigenen Bedürfnisse anpasst und erweitert. Eine »init_ui«-Methode ist auch überflüssig, da sie ja nur das, was eigentlich in __init__ stehen sollte, nochmal verstreut (__init__ ist kein Konstruktor, den gibt es auch und der heißt __new__). Die Variante davor hatte auch ihren Charme, alles in einen Frame zu packen, das vereinfacht das Umschreiben, falls aus der Tk-Instanz doch mal nur ein TopLevel wird.

@wuf: __init__ ist nur dazu da, ein Objekt zu initialisieren. Der Mainloop gehört da definitiv nicht rein. Das sieht man auch an »main«, in dem nur ein verkümmertes Objekt erzeugt wird, mit dem aber sonst nichts passiert. Die feste Vorgabe der Fenstergröße ist normalerweise ungeschickt. Laß doch das Fenster seine passende Größe und vor allem die Position selbst finden, das sollte ein Programm nicht selbst entscheiden.
Benutzeravatar
__deets__
User
Beiträge: 2142
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: Menüauswahl abfragen

Beitragvon __deets__ » Montag 1. Januar 2018, 23:25

@sirius3: zu Beginn war es ja noch nicht mal eine Tk-Instanz, die Indirektion also auch gegeben. Und eine Ableitung sollte eine funktionale Erweiterung der Basisklasse darstellen. Wozu letztere auch in der gedacht sein muss. Da die Tk Klasse das nicht vorsieht, genauso wenig wie der Frame, wird es also reduziert auf eine reine zu-Dekoration von Methoden und Zustand zweier ansonsten völlig getrennter Funktionalitäten. Das ist in meinen Augen keine wünschenswerter Architektur.

Die Argumentation bezüglich des Templates ist allerdings richtig. Ich habe da selbst im Laufe der Diskussion drüber nachgedacht. Ohne eigenes Modul ist das überkandidelt. Die Aufteilung in verschiedene Methoden, in denen die UI aufgebaut wird, empfinde ich allerdings trotzdem (je nach Umfang) sinnvoll, weil der Aufbau der Widget Hierarchie IMHO oft sehr hässlich & repetitiv ist. Das einfach überlesen zu können und sich stattdessen auf die Anmeldung zb von callbacks und timern konzentrieren zu können finde ich durchaus sinnvoll. Für eine Handvoll Widgets lohnt sich das aber eher nicht.

Und was vorher vereinfacht war, sehe ich nicht. Die Ableitung anzufassen vs den Typ eines Members zu ändern - das ist doch gehupft wie gesprungen.
Sirius3
User
Beiträge: 7044
Registriert: Sonntag 21. Oktober 2012, 17:20

Re: Menüauswahl abfragen

Beitragvon Sirius3 » Dienstag 2. Januar 2018, 00:00

@__deets__: wie meist Du das, dass die Tk Klasse eine Vererbung nicht vorsieht? Aus einem leeren Fenster wird ein Fenster mit Inhalt. Typische Erweiterung der Funktionalität. Ein Widget-Hierarchie sollte man auch gar nicht in einem Hauptfenster aufbauen. Wenn man eine Klasse mit Aufgaben überhäuft, dann hilft es auch nichts, eine weitere Methode hinzuzufügen. Es gilt weiterhin die Konvention, dass alle Attribute in __init__ angelegt werden müssen. Statt dessen sollte man Teile in weitere Klassen auslagern, die dann typischerweise von Frame erben, damit man die Teile flexibel zu einem Fenster zusammenbauen kann.
Benutzeravatar
wuf
User
Beiträge: 1367
Registriert: Sonntag 8. Juni 2003, 09:50

Re: Menüauswahl abfragen

Beitragvon wuf » Dienstag 2. Januar 2018, 12:36

Hi __deets__ & Sirius3

Danke noch für eure Kommentare. Habe mich noch kurz bei der PyQt-Familie schlau gemacht und bin auf Konstrukte ähnlich wie folgt gestossen:
  1. import sys
  2. from PyQt4 import QtGui
  3.  
  4.  
  5. class Application(QtGui.QMainWindow):
  6.    
  7.     def __init__(self):
  8.         super(Application, self).__init__()
  9.  
  10.         self.setGeometry(20, 20, 300, 200)
  11.         self.setWindowTitle("Qt Template")
  12.  
  13.         self.show()
  14.  
  15.  
  16. def main():
  17.     app = QtGui.QApplication(sys.argv)
  18.     application = Application()
  19.     sys.exit(app.exec_())
  20.  
  21.  
  22. if __name__ == '__main__':
  23.     main()      
Hier wird scheinbar auch von der Klasse QtGui.QMainWindow geerbt.

Habe mich jetzt entschlossen folgendes Konstrukt für kleinere Tkinter-Experimente zu verwenden:
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. from functools import partial
  5.  
  6. try:
  7.     # Tkinter for Python 2.xx
  8.     import Tkinter as tk
  9. except ImportError:
  10.     # Tkinter for Python 3.xx
  11.     import tkinter as tk
  12.  
  13. APP_TITLE = "Trial Template"
  14. APP_XPOS = 100
  15. APP_YPOS = 100
  16. APP_WIDTH = 300
  17. APP_HEIGHT = 200
  18.  
  19.        
  20. class Application(tk.Tk):
  21.  
  22.     def __init__(self):
  23.         tk.Tk.__init__(self)
  24.         self.title(APP_TITLE)
  25.    
  26.     def build(self):
  27.         self.protocol("WM_DELETE_WINDOW", self.close_app)
  28.         self.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
  29.         self.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
  30.         self.option_add("*Button.highlightThickness", 0)
  31.  
  32.         # Trial container-01
  33.         self.app_container_01 = tk.Frame(self, bg='yellow', bd=0)
  34.         self.app_container_01.pack(side='left', fill='both', expand=True)
  35.         tk.Button(self.app_container_01, text='Container-01').pack(expand=True)
  36.  
  37.         # Trial container-02
  38.         self.app_container_02 = tk.Frame(self, bg='steelblue', bd=0)
  39.         self.app_container_02.pack(side='left', fill='both', expand=True)
  40.         tk.Button(self.app_container_02, text='Container-02').pack(expand=True)
  41.        
  42.     def close_app(self):
  43.         # Here do something before apps shutdown
  44.         print("Good Bye!")
  45.         self.destroy()
  46.  
  47.        
  48. def main():        
  49.     app = Application()
  50.     app.build()
  51.     app.mainloop()
  52.  
  53.    
  54. if __name__ == '__main__':
  55.     main()      

Für grössere Tkinter-Experimente habe folgendes Template konstruiert:
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. from functools import partial
  5.  
  6. try:
  7.     # Tkinter for Python 2.xx
  8.     import Tkinter as tk
  9. except ImportError:
  10.     # Tkinter for Python 3.xx
  11.     import tkinter as tk
  12.  
  13. APP_TITLE = "Template_01"
  14. APP_XPOS = 100
  15. APP_YPOS = 100
  16. APP_WIDTH = 300
  17. APP_HEIGHT = 200
  18.  
  19. BUTTON_01 = 'Container-01'
  20. BUTTON_02 = 'Container-02'
  21. BUTTON_03 = 'Close'
  22.  
  23. class AppModel(object):
  24.    
  25.     def __init__(self):
  26.         pass
  27.        
  28. class AppView(tk.Frame):
  29.    
  30.     def __init__(self, app, **kwargs):
  31.         self.app = app
  32.         self.app_win = app.app_win
  33.        
  34.         tk.Frame.__init__(self, self.app_win, **kwargs)
  35.  
  36.     def build(self):
  37.         self.container_frame = tk.Frame(self)
  38.         self.container_frame.pack(fill='both', expand=True)
  39.        
  40.         # Trial container-01
  41.         self.app_container_01 = tk.Frame(self.container_frame, bg='yellow',
  42.             bd=0)
  43.         self.app_container_01.pack(side='left', fill='both', expand=True)
  44.        
  45.         tk.Button(self.app_container_01, text=BUTTON_01,
  46.             command=partial(self.app.buttons_callback, BUTTON_01)
  47.             ).pack(expand=True)
  48.  
  49.         # Trial container-02
  50.         self.app_container_02 = tk.Frame(self.container_frame, bg='steelblue',
  51.             bd=0)
  52.         self.app_container_02.pack(side='left', fill='both', expand=True)
  53.        
  54.         tk.Button(self.app_container_02, text=BUTTON_02,
  55.             command=partial(self.app.buttons_callback, BUTTON_02)
  56.             ).pack(expand=True)
  57.  
  58.         tk.Button(self, text=BUTTON_03, command=partial(
  59.             self.app.buttons_callback, BUTTON_03)).pack(pady=2)
  60.        
  61. class Application(object):
  62.  
  63.     def __init__(self, app_win):
  64.         self.app_win = app_win
  65.         app_win.protocol("WM_DELETE_WINDOW", self.close_app)
  66.    
  67.     def build(self):    
  68.         self.model = AppModel()
  69.         self.view = AppView(self)
  70.         self.view.pack(fill='both', expand=True)
  71.         self.view.build()
  72.    
  73.     def buttons_callback(self, button_name, event=None):
  74.         print("My name is", button_name)
  75.        
  76.         if button_name == BUTTON_01:
  77.             pass
  78.         elif button_name == BUTTON_02:
  79.             pass
  80.         elif button_name == BUTTON_03:
  81.             self.close_app()
  82.                  
  83.     def close_app(self):
  84.         # Here do things before shutdown
  85.         print("Good Bye!")
  86.         self.app_win.destroy()
  87.  
  88.        
  89. def main():
  90.     app_win = tk.Tk()
  91.     app_win.title(APP_TITLE)
  92.     #app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
  93.     #app_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
  94.     app_win.option_add("*Button.highlightThickness", 0)
  95.    
  96.     app = Application(app_win).build()
  97.    
  98.     app_win.mainloop()
  99.  
  100.  
  101. if __name__ == '__main__':
  102.     main()      
Hoffe mein Konstrukt ist nicht völlig sinnfrei. Für eventuelle Anregungen bin ich euch dankbar.
Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
__deets__
User
Beiträge: 2142
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: Menüauswahl abfragen

Beitragvon __deets__ » Dienstag 2. Januar 2018, 18:00

@Sirius3 Vererbung ist in tkinter nicht notwending, um damit zu arbeiten. Mir waere jedenfalls nicht ein Fall bekannt, wo man ein Ziel nur dadurch erreicht, das man ableitet.

Das steht im Gegensatz zu anderen UI-Frameworks. Bei Qt zB musste man frueher ableiten, wenn man events bekommen wollte. Jetzt gibt es dafuer event-filter, und man muss das nicht mehr zwingend. Ein weiterer Grund bei Qt abzuleiten besteht darin, das man in C++ mit Memory-Management hantieren muss, und die Baumstruktur einem da viel abnimmt - man gewinnt also etwas davon, von QObject oder gar QWidget zu erben. Das setzt sich teilweise fort in die PyQt-Welt, weswegen es sich da auch anbietet. Bei Cocoa muss man (ggf. hat sich auch das geaendert, aber das war Stand vor 2-3 Jahren) ableiten, wenn man ein rahmenloses Fenster will. Bei AWT musste man zwingend ableiten, wenn man einen Button eine Action ausfuehren lassen wollte. Bis sie ihre Listener eingefuehrt haben, bzw. Swing oder wie der Nachfolger hiess (+++ fuer nicht mehr im Java-Land sein...)

Der Trend aber klar dahin, das Framework nutzen zu koennen, ohne sich durch Ableitung eine Abhaengigkeit zu bestimmten Protokollen und Invarianten einfangen zu muessen. Alles was hier diskutiert wird *kann* ohne Ableitung erreicht werden, und meine Praeferenz ist da: wenn es ohne geht, dann mache ich das auch ohne.

Und eine Widget-Hierarchie ist auch schon drei Frames, mit jeweils zwei Frames noch mal darin, um ein bestimmtes Layout zu erreichen. Und das ist ganz bestimmt kein Grund, eine weitere Klasse einzufuehren. Aber im Zweifel trotzdem 10 Zeilen Code. Oder 20.

Womit wir bei der Konvention sind: ich kenne die, und ich finde sie prinzipiell auch gut, erweitere sie fuer mich persoenlich aber ganz klar zu "entweder in __init__, oder in direkt aufgerufenen Methoden". Eben weil eine solche Strukturierung in meinen Augen (nach Umfang natuerlich) Vorteile hat.

Last but not least: wir reden hier im konkreten ueber ein Template fuer eine *Applikation*. Und eine Applikation ist ueblicherweise deutlich mehr, als eine einzelne Widget-Hierarchie. Da kommen noch Threads fuer Nebenlaeufige Taetikeiten zu, asynchrone Events per file-descriptor, etc. Deswegen leite ich aber auch nicht von threading.Thread oder SimpleHTTPServer ab.

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder