Wozu von Toplevel-Klasse erben?

Fragen zu Tkinter.
Antworten
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Guten Tag,
ich arbeite gerade an einem Projekt und dort wird eine Klasse benutzt um ein angepasstes Toplevel-Fenster anzuzeigen.
Hier mal der Pseudocode:

Code: Alles auswählen

import tkinter
class dt_TKObjects():
    def __init__(self, parent):
        self.parent = parent
    def createExample(self, type):
        # Hier wird je nach Typ ein eigenes Widget erzeugt

    def createToplevel(self, title, desc, source, type):
        # Hier wird das Toplevel-Element erzeugt und gleich ausgeblendet
        self.title = title
        self.desc = desc
        self.source = source
        self.type = type

        # Hier werden Voreinstellungen getroffen
        
        createExample(self.type)

    def displayToplevel(self, parent):
        self.userParent = parent
        self.userParent.deiconify()
Nun frage ich mich, ist das so sauber, oder sollte man von der Toplevel-Klasse erben?
Und was hat das dann für einen Nutzen, wie kann ich dass denn benutzen?
Muss ich das nur so abändern?:

Code: Alles auswählen

import tkinter

class dt_TKObjects(tkinter.Toplevel):
    # Die __init__-Methode usw.

        # Hier dann der Aufruf in 'createToplevel'
        self.master = self.Toplevel(self.parent)
Kann ich so direkt auf die Toplevel-Funktionen zugreifen, ohne den Weg über tkinter.Toplevel() zu gehen. Wenn ja, was bringt mir das?

Vielleicht ein paar viele Fragen, aber ich will eben genau wissen, was das bringt. Ansonsten würde ich es dann so lassen, weil es so funktioniert.
LG Maik
BlackJack

@daemonTutorials: Ist ein `dt_TKObjects` denn ein Widget? Und wenn ja, ist es semantisch ein `Toplevel`? Denn nur dann macht es Sinn von `Toplevel` zu erben.

Und nein, man würde dann kein Attribut an das Objekt binden das ein zusätzliches `Toplevel` ist, denn das Objekt *selbst* ist dann ja eines.
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

@BlackJack: Ich benutze diese Klasse im Endeffekt nur um ein Toplevel-Element zu erstellen und es mit Inhalten zu füllen. Also muss ich nicht erben. Da ich damit nur das Toplevel-Element selber verändern kann.

Somit hat sich meine Frage erledigt.
LG Maik
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@daemonTutorials
Das kommt ganz darauf an, für dein SplashScreen zum Beispiel könnte es sich durchaus lohnen. Schon alleine um den Text darauf zu ändern oder den von mir vorgeschlagenen Ausblenden-Effekt zuermöglichen, ist es schon sehr praktisch von Toplevel abzuleiten. Vorallem wäre es dort schöner statt einer Funktion quasi eine Anwendung, also ein Tk-Objekt hat, falls man diese, als Nutzer von deinem Script, selbst erweitern möchte.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Also ich leite dann von Toplevel ab und erstelle die nötigen Widgets mit den übergebenen Parametern. Dann doch lieber in einer Klasse.
Etwa so:

Code: Alles auswählen

import tkinter

class DtSplash(tkinter.Toplevel):
    def __init__(self, parent):
        tkinter.Toplevel.__init__(self, parent)
        self.parent = parent
        self.pack()
        self.splash()

    def splash(self, title, image_url, screen_width=500, screen_height=350):
        # Title setzen
        self.title(title) 
 
        # Jetzt werden alle Widgets erzeugt; Beispiel:
        self.splashScreen = tkinter.Button(self)
        self.splashScreen.pack()
So in etwa geht das? Oder wie stellt man sich das vor?
LG Maik
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Der Ansatz ist schon gut, hier einige Verbesserungen:
- Wenn du das Tkinter spezifische "master" anstelle von "parent" nutzt, kannst du dir "self.parent = parent" sparen, da bei dem Aufruf des Konstrucktors vom Toplevel das "master"-Attribut gesetzt wird.

- "self.pack()" würde ich aus der Klasse rausschmeißen, denn sonst kann man diese Klasse nur nutzen, wenn man auch beim "parent" den Pack-Manager verwendet. Als Nutzer der Klasse ist es immer schöner den Geometrie-Manager selbst wählen kann.
Edit: Gilt generell, ist bei einem Toplevel-Widget egal, da es selbst keinen Geometry-Manager nutzen kann.

- Die Splash-Methode könnte man noch unterteilen, um die Größen auch später noch ändern zu können oder das Bild/Titel zu wechslen, könnte man sich einzelne Methoden/Properties dafür schreiben die das übernehmen, statt einen riesigen Funktionskopf zuhaben.
Zuletzt geändert von Xynon1 am Montag 11. April 2011, 16:27, insgesamt 1-mal geändert.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Ja, im Moment ist die Funktion/Klasse ziemlich starr. Alles muss vorher richtig eingegeben werden.

Dann werde ich deine Vorschläge mal einarbeiten.
LG Maik
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

So, habe ich jetzt fertig. Das Toplevel Element wird angezeigt und auch ausgeblendet wann ich es möchte.

Das Problem liegt jetzt bei der Anzeige des Textes und des Bildes auf dem Canvas.
http://www.python-forum.de/pastebin.php?mode=view&s=182

Hier jetzt mal den Code, den ich zum starten des SplashScreen nehme:

Code: Alles auswählen

import tkinter
import tkh_splash3

root = tkinter.Tk()
root.title("Hello")

splash = tkh_splash3.DtSplash(root)
splash.setWidth(500)
splash.setHeight(350)
splash.setImage("tkhelp/test.gif")
splash.setTitle("Tkhelp 2.1") # Beispiel, intigriere ich ja in tkhelp
splash.generateSplash()

btn = tkinter.Button(root, text="Hello World!")
btn.pack()

root.mainloop()
LG Maik
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

:shock: schon wieder mixedCase. :D wenn du drauf stehst sag es doch einfach, oder hast du das verkehrt herum verstanden? Du solltest wirklich einmal die PEP 8 lesen.

Ok, zurück zum Thema.
- So, wie du jetzt die Attribute (Höhe, Breite,...) setzt ändert das nicht das eigentliche Fenster zur Laufzeit, sondern nur vorher. So wie sie jetzt sind könnte man sie sich auch sparen. Hierfür solltest du direkt das Toplevel-Widget oder den Canvas ansprechen, in diesem Fall natürlich DtSplash. Könnte zB. so aussehen:

Code: Alles auswählen

@property
def width(self):
    return self.cget("width")

@width.setter
def width(self, value):
    self.config(width=value)
Du kannst natürlich auch Funktionen nehmen, es geht nur darum, das du auch tatsächlich die Werte änderst.
Edit: Aber mach es dir nicht zu kompliziert, denn der Aufwand lohnt sich für einen SplashScreen nicht und Höhe und Breite gibst du besser direkt im aufruf über wie man es sonst üblich ist.

- Da sind wir auch schon beim zweiten Punkt, du musst dein Canvas auch auf deinen SplashScreen packen,

Code: Alles auswählen

.pack(fill="both", expand=True)
bietet sich hier an. Im Klartext, dein Canvas ist immer genauso groß wie dein DtSplash-Widget (abzüglich eines eventuellen paddings).

- die Geschichte mit "Wie finde ich die Mitte des Bildschirmes?" würde ich auslagern in eine Funktion, getrennt von der Klasse, denn diese kann man immer mal brauchen. Gib dann einfach das widget als Parameter an und las dir die Daten zurückgeben.

- KeyboardInterupt abzufangen halte ich auch noch für unschön, wenn ich eine Anwendung schließen will, dann hält mich das auch nicht davon ab. Ist eigentlich ziemlich unütz.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Okay, die PEP 8 lese ich demnächst mal.
MixedCase? Mal gucken.
"Die Mitte finden" in eigene Funktion auslagern: Mach ich!

Was bedeuted "@property" und "@width.setter", da kenn ich mich noch nicht so aus.

Dann lager ich die Höhe, Breite, Titel in eine Funktion aus mit optionalen Funktionen.

Ach, das "packen" habe ich ganz vergessen, war zu sehr auf die OOP konzentriert.

P.s.: Supi, jetzt kann ich das auf mein anderes Projekt anwenden.
LG Maik
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

mixedCase, das ist CamelCase siehe dein Thread in Showcase.

Zu dem Funktionseigenschaften findest du hier die Erklärung http://docs.python.org/library/functions.html#property

Es ging nicht darum Höhe und Breite in Funktion zu packen, sondern in eine Dictionary bzw. diese als Schlüsselwörter direkt zu über geben. In deinem Beispiel müsste es also

Code: Alles auswählen

splash = tkh_splash3.DtSplash(root, width=500, height=300)
heißen, den Rest macht das Toplevel und der Pack-Manager.
Den Titel allerdings wirst du selbst setzen müssen, da er ja in großen Buchstaben irgendwo stehen soll, dies bietet das Toplevel aber nicht von sich aus an.

Hier noch die Fadeout-Funktion:

Code: Alles auswählen

alpha = (math.sqrt(a) / 10.0 for a in xrange(100, 0, -1))
def fadeout(toplevel, alpha):
    try:
        toplevel.wm_attributes("-alpha", next(alpha))
        toplevel.after(10, fadeout, toplevel, alpha)
    except StopIteration:
        toplevel.destroy()
Wenn der SplashScreen schon Standardgemäß leicht transparent ist, musst du die 100 entsprechend runter setzen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Endlich, es funktioniert: http://paste.pocoo.org/show/369649/
LG Maik
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Und jetzt zum fadeout.

Bei mir wir der StopIteration Fehler geworfen(habe ihn mal manipuliert).

xrange() wird nicht erkannt. Und 'alpha' hat einen Inhalt von 1.0 zu 0.1.
Hier mal meine abgeänderte:

Code: Alles auswählen

alpha = (math.sqrt(a) / 10.0 for a in range(100, 0, -1))
Ausgabe:

Code: Alles auswählen

1.0
0.994987437107
0.989949493661
0.98488578018
0.979795897113
0.974679434481
0.969535971483
0.964365076099
0.959166304663
0.953939201417
0.948683298051
0.943398113206
0.938083151965
0.932737905309
0.92736184955
0.921954445729
0.916515138991
0.911043357914
0.905538513814
0.9
0.894427191
0.888819441732
0.883176086633
0.877496438739
0.871779788708
0.866025403784
0.860232526704
0.854400374532
0.848528137424
0.842614977318
0.836660026534
0.830662386292
0.824621125124
0.818535277187
0.812403840464
0.80622577483
0.8
0.793725393319
0.787400787401
0.781024967591
0.774596669241
0.768114574787
0.761577310586
0.754983443527
0.748331477355
0.74161984871
0.734846922835
0.728010988928
0.721110255093
0.714142842854
0.707106781187
0.7
0.692820323028
0.68556546004
0.678232998313
0.67082039325
0.663324958071
0.65574385243
0.648074069841
0.640312423743
0.632455532034
0.62449979984
0.616441400297
0.60827625303
0.6
0.59160797831
0.583095189485
0.574456264654
0.565685424949
0.556776436283
0.547722557505
0.538516480713
0.529150262213
0.519615242271
0.509901951359
0.5
0.489897948557
0.479583152331
0.469041575982
0.458257569496
0.4472135955
0.435889894354
0.424264068712
0.412310562562
0.4
0.387298334621
0.374165738677
0.360555127546
0.346410161514
0.331662479036
0.316227766017
0.3
0.282842712475
0.264575131106
0.244948974278
0.22360679775
0.2
0.173205080757
0.141421356237
0.1

LG Maik
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Ich habe ein kleines Problem. Die Fadeout-Funktion benutzt '.after' und ich benutze '.after' um den SplashScreen nach soviel sec. auszublenden!
Also kann ich das nicht in eine Funktion packen, dann würde self.master.deiconify() mehrmals ausgeführt werden.

Wie kann ich das Problem umgehen?
LG Maik
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

So, weiter geht's mit meckern :twisted:

- Du kannst width und height dem Toplevel direkt zuweisen, in dem du die typische Tkinter-Syntax im Konstruktor nutzt.
Beispiel:

Code: Alles auswählen

class MyWidget(tkinter.Toplevel):
    def __init__(self, master, **kwargs):
        tkinter.Toplevel.__init__(self, master, kwargs)
So kann man dein Widget wie jedes andere benutzen. Funktioniert so bei jedem Widget aus dem Tkinter und Tix-Modul, bei anderen, wie ttk musst du beim Aufruf des Konstrucktors der Superklasse ebendfalls "**kwargs" schreiben.

- bei center, sollte der Name des Parameters nicht "master" sein, da du hier ja keine, ähm Meister also kein übergeordnetes Widget brauchst, sondern jedes beliebiges Widget nehmen kannst, da jedes widget die "winfo"-Methoden hat.

- Die "image_url" würde ich auch nicht so setzen, da das wenig bringt, überleg mal was passieren würde wenn jemand "generateSplash" vor "configSplash" aufruft. Ich würde eher zu einer "set_image(url)"-Methode tendieren. In welcher dann das Image sofort in den Canvas gesetzt wird.

- und mir ist noch aufgefallen, das du den Screen mit "withdraw" entfernst. hier ist die "destroy"-Methode angebrachter, da du den SplashScreen danach eigentlich nicht mehr brauchst.

Und weiter:
- xrange, mein Fehler hatte vergessen das du Python 3 nutzt.
- StopIteration, soll ja auch geworfen werden, weil bei dieser Exception ist der SplashScreen unsichtbar und wird nicht mehr benötigt
daemonTutorials hat geschrieben:Wie kann ich das Problem umgehen?
- Du brauchst doch nur nach der Exception die Zeile anhängen.
- die "startApp"-Funktion brauchst du dann nicht mehr.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

So, jetzt klappt es endgültig. Mit Fadeout(obwohl es bei X11 anscheinend nicht geht, schade)

http://paste.pocoo.org/show/369712/
LG Maik
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Doch :o, wenn du compiz installiert und aktiviert hast. Ist mir auch erst heute aufgefallen.

Zwei Anmerkung noch :twisted:,
- Dein "d" in dtSplash, kannst du groß schreiben. In Klassen ist CamelCase erwünscht
- und momentan sollte die Funktion wohl eher "add_image" heißen, denn genau das tut sie im Moment

Edit: Wie der Zufall so will.
- Bei deinem Canvas kannst du dir das "width" und "height" sparen, das macht der Pack()-Manager für dich durch "expand". Hatte ich oben irgendwo schon mal geschrieben

- die fadeout Funktion greift auf das Attribut "photo_is_set" zu, dies ist kein Standard für Toplevel-Widgets, also setzt du die fadeout-Funktion dem SplashScreen als Methode ein, oder du solltest den Name des Parameters in "dt_splash" ändern.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
daemonTutorials
User
Beiträge: 171
Registriert: Sonntag 6. Februar 2011, 12:06
Kontaktdaten:

Achso, stimmt ja! Leider *funzt* das mit compiz nicht(warum weiß nur Gott).
Das mit width und height hast du gesagt, bloß verstanden hab ich es nicht.
Die Variable habe ich geändert, packe ich evtl. noch in die Klasse rein!

Und das Funktionen offen auf die Variablen einer Klasse zugreifen, ist nicht konventionell, das stimmt.

Danke. Werde ihn noch mehr verbessern und posten.
LG Maik
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

quote="daemonTutorials"]Das mit width und height hast du gesagt, bloß verstanden hab ich es nicht.[/quote]
Eine gute Erklärung findest du wieder auf effbot.org. Kurz um "fill" lässt zu das die Widgets sich bis zum Rand des übergeordneten Widgets strecken dürfen und "expand" gibt an ob das Widget bei einer Veränderung der Größe des übergeordneten Widgets auch seine eigene ändern darf.

Hier ein Beispiel:

Code: Alles auswählen

import tkinter as tk

if __name__ == "__main__":
    root = tk.Tk()

    frame = tk.Frame(root)
    frame.pack(fill="both")
    
    button1 = tk.Button(frame, text="\xdcbernehmen")
    button1.pack(side="left", fill="x")
    button2 = tk.Button(frame, text="Abbrechen")
    button2.pack(side="right", fill="x", expand=True)
    
    root.mainloop()
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Antworten