Menüauswahl abfragen

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

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: 1481
Registriert: Sonntag 8. Juni 2003, 09:50

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.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 300
APP_HEIGHT = 200

        
class Application(object):
    APP_TITLE = "NewTemplate_01"
    
    def __init__(self):
        self.app_win = tk.Tk()
        self.app_win.protocol("WM_DELETE_WINDOW", self.close_app)
        self.app_win.title(self.APP_TITLE)
        self.app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
        self.app_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
        self.app_win.option_add("*Button.highlightThickness", 0)
        
        self.init_ui()
        self.run_app()
        
    def init_ui(self):
        # Trial container-01
        self.app_container_01 = tk.Frame(self.app_win, bg='yellow', bd=0)
        self.app_container_01.pack(side='left', fill='both', expand=True)
        tk.Button(self.app_container_01, text='Container-01').pack(expand=True)

        # Trial container-02
        self.app_container_02 = tk.Frame(self.app_win, bg='steelblue', bd=0)
        self.app_container_02.pack(side='left', fill='both', expand=True)
        tk.Button(self.app_container_02, text='Container-02').pack(expand=True)
    
    def run_app(self):
        self.app_win.mainloop()
            
    def close_app(self):
        # Here do something before apps shutdown
        print("Good Bye!")
        self.app_win.destroy()

        
def main():    
    Application()
 
 
if __name__ == '__main__':
    main()      

Gruss wuf :wink:
Take it easy Mates!
Sirius3
User
Beiträge: 8084
Registriert: Sonntag 21. Oktober 2012, 17:20

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.
__deets__
User
Beiträge: 3077
Registriert: Mittwoch 14. Oktober 2015, 14:29

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: 8084
Registriert: Sonntag 21. Oktober 2012, 17:20

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: 1481
Registriert: Sonntag 8. Juni 2003, 09:50

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:

Code: Alles auswählen

import sys
from PyQt4 import QtGui


class Application(QtGui.QMainWindow):
    
    def __init__(self):
        super(Application, self).__init__()

        self.setGeometry(20, 20, 300, 200)
        self.setWindowTitle("Qt Template")

        self.show()


def main():
    app = QtGui.QApplication(sys.argv)
    application = Application()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()      
Hier wird scheinbar auch von der Klasse QtGui.QMainWindow geerbt.

Habe mich jetzt entschlossen folgendes Konstrukt für kleinere Tkinter-Experimente zu verwenden:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_TITLE = "Trial Template"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 300
APP_HEIGHT = 200

        
class Application(tk.Tk):

    def __init__(self):
        tk.Tk.__init__(self)
        self.title(APP_TITLE)
    
    def build(self):
        self.protocol("WM_DELETE_WINDOW", self.close_app)
        self.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
        self.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
        self.option_add("*Button.highlightThickness", 0)

        # Trial container-01
        self.app_container_01 = tk.Frame(self, bg='yellow', bd=0)
        self.app_container_01.pack(side='left', fill='both', expand=True)
        tk.Button(self.app_container_01, text='Container-01').pack(expand=True)

        # Trial container-02
        self.app_container_02 = tk.Frame(self, bg='steelblue', bd=0)
        self.app_container_02.pack(side='left', fill='both', expand=True)
        tk.Button(self.app_container_02, text='Container-02').pack(expand=True)
        
    def close_app(self):
        # Here do something before apps shutdown
        print("Good Bye!")
        self.destroy()

        
def main():        
    app = Application()
    app.build()
    app.mainloop()

    
if __name__ == '__main__':
    main()      
Für grössere Tkinter-Experimente habe folgendes Template konstruiert:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import partial

try:
    # Tkinter for Python 2.xx
    import Tkinter as tk
except ImportError:
    # Tkinter for Python 3.xx
    import tkinter as tk

APP_TITLE = "Template_01"
APP_XPOS = 100
APP_YPOS = 100
APP_WIDTH = 300
APP_HEIGHT = 200

BUTTON_01 = 'Container-01'
BUTTON_02 = 'Container-02'
BUTTON_03 = 'Close'

class AppModel(object):
    
    def __init__(self):
        pass
        
class AppView(tk.Frame):
    
    def __init__(self, app, **kwargs):
        self.app = app
        self.app_win = app.app_win
        
        tk.Frame.__init__(self, self.app_win, **kwargs)

    def build(self):
        self.container_frame = tk.Frame(self)
        self.container_frame.pack(fill='both', expand=True)
        
        # Trial container-01
        self.app_container_01 = tk.Frame(self.container_frame, bg='yellow',
            bd=0)
        self.app_container_01.pack(side='left', fill='both', expand=True)
        
        tk.Button(self.app_container_01, text=BUTTON_01,
            command=partial(self.app.buttons_callback, BUTTON_01)
            ).pack(expand=True)

        # Trial container-02
        self.app_container_02 = tk.Frame(self.container_frame, bg='steelblue',
            bd=0)
        self.app_container_02.pack(side='left', fill='both', expand=True)
        
        tk.Button(self.app_container_02, text=BUTTON_02,
            command=partial(self.app.buttons_callback, BUTTON_02)
            ).pack(expand=True)

        tk.Button(self, text=BUTTON_03, command=partial(
            self.app.buttons_callback, BUTTON_03)).pack(pady=2)
        
class Application(object):

    def __init__(self, app_win):
        self.app_win = app_win
        app_win.protocol("WM_DELETE_WINDOW", self.close_app)
    
    def build(self):    
        self.model = AppModel()
        self.view = AppView(self)
        self.view.pack(fill='both', expand=True)
        self.view.build()
    
    def buttons_callback(self, button_name, event=None):
        print("My name is", button_name)
        
        if button_name == BUTTON_01:
            pass
        elif button_name == BUTTON_02:
            pass
        elif button_name == BUTTON_03:
            self.close_app()
                  
    def close_app(self):
        # Here do things before shutdown
        print("Good Bye!")
        self.app_win.destroy()

        
def main():
    app_win = tk.Tk()
    app_win.title(APP_TITLE)
    #app_win.geometry("+{}+{}".format(APP_XPOS, APP_YPOS))
    #app_win.geometry("{}x{}".format(APP_WIDTH, APP_HEIGHT))
    app_win.option_add("*Button.highlightThickness", 0)
    
    app = Application(app_win).build()
    
    app_win.mainloop()
 
 
if __name__ == '__main__':
    main()      
Hoffe mein Konstrukt ist nicht völlig sinnfrei. Für eventuelle Anregungen bin ich euch dankbar.
Gruss wuf :wink:
Take it easy Mates!
__deets__
User
Beiträge: 3077
Registriert: Mittwoch 14. Oktober 2015, 14:29

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.
Antworten