Klassenattribut ist nach Erstellung nicht mehr ansprechbar

Fragen zu Tkinter.
BlackJack

@moccajoghurt: Das kann ich nicht nachvollziehen. Das kann aber eventuell daran liegen, dass Du `Toplevel` kein „Master”-Widget übergibst. Das hier funktioniert bei mir jedenfalls — nach zwei Sekunden (2000 ms) verschwindet der Balken:

Code: Alles auswählen

import Tkinter as tk
from ttk import Progressbar


class DownloadBar(object):

    def __init__(self, master, main=None):
        self.mainclass = main
        self.window = tk.Toplevel(master)
        self.window.overrideredirect(True)
        self.bar = Progressbar(self.window, length=150, mode='indeterminate')
        self.bar.pack(expand=True, fill=tk.BOTH)
        
        self.loop_bar()
    
    def loop_bar(self):
        self.bar.start(interval=1)
    
    def kill_bar(self):
        self.bar.stop()
        self.window.destroy()


def main():
    root = tk.Tk()
    bar = DownloadBar(root)
    root.after(2000, bar.kill_bar)
    root.mainloop()


if __name__ == '__main__':
    main()
Bei Python 2.x sollte man immer von `object` erben um „new style”-Klassen zu bekommen, bei denen alles so funktioniert wie in der Dokumentation beschrieben (Vererbungshierarchie, `property()`, …).

Wie schon gesagt: Typen sollte man wenn möglich aus Namen heraus halten. Zumal wenn sie wie bei `self.mainclass` wahrscheinlich nicht stimmen, denn ich gehe mal davon aus, dass an das Attribut keine *Klasse* gebunden wird, sondern ein Exemplar.

Das `overrideredirect()` würde mich in den Wahnsinn treiben. Da bekommt man ein GUI-Element irgendwo auf den Bildschirm geklatscht — bei mir ist es anscheinend immer links oben in der Ecke — das man als Benutzer überhaupt nicht beeinflussen kann.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Ich habe die Klasse so umgeschrieben, dass nurnoch ein Toplevel erstellt wird, dass danach zerstört werden soll:

Code: Alles auswählen

from ttk import Progressbar
import Tkinter


class download_bar(object):
	
	def __init__(self, main):
		self.mainclass = main
		self.window = Tkinter.Toplevel(self.mainclass.main_window.window)		

	
	def kill_bar(self):
		self.window.destroy()
		print("######DESTROYED######")
		
	
Die .destroy() Funktion wird zwar aufgerufen (der print wird ausgegeben), allerdings ist das Fenster noch sichtbar. Es antwortet allerdings nicht mehr auf Klicks (z.B. minimieren, maximieren, schließen). Verschieben kann ich es noch.
Woran kann das liegen?

Die andern angesprochenen Punkte werde ich wenn ich das Problem gelöst habe noch korrigieren.
BlackJack

@moccajoghurt: Für das verschieben ist die Fensterverwaltung zuständig und nicht das GUI-Toolkit.

Von wo aus wird denn die `kill_bar()`-Methode aufgerufen? Aus einem anderen Thread als dem impliziten Hauptthread wo die `mainloop()` läuft? Und kehrt der Aufruf der Methode auch zu der `mainloop()` zurück?

Bei ``self.mainclass.main_window.window`` würde ich sagen da hat die Klasse zu viel Wissen über das als `main` übergebene Objekt. Und ``main_window.window`` bedeutet eventuell, dass es eine Ebene gibt, genau wie bei ``download_bar.window`` die man sich durch Vererbung sparen könnte.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi moccajoghurt

Funktioniert bei mir ohne Probleme:

Code: Alles auswählen

from ttk import Progressbar
import Tkinter as tk


class DownLoadBar(object):
       
    def __init__(self, main):
        self.mainclass = main
        self.window = tk.Toplevel(self.mainclass) 
        self.window.title('Top')
        self.window.protocol("WM_DELETE_WINDOW", self.kill_bar)            

   
    def kill_bar(self):
        self.window.destroy()
        print("######DESTROYED######")
        
#----- MODUL_TEST -------------------------------------------------------------#
if __name__ == '__main__':

    root = tk.Tk()
    root.title('Main')

    down_load = DownLoadBar(root)

    root.mainloop()
Gruß wuf :wink:
Take it easy Mates!
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Kann es sein, dass es daran liegt, dass ich das Toplevel erst erzeuge, wenn ein event in der mainloop stattfindet und es dadurch erst "nach" der mainloop erstellt wird?
Ich werde es jetzt mal so probieren, dass ich das Toplevel vor dem Start der mainloop erstelle und es erst nach dem event sichtbar mache.
BlackJack

@moccajoghurt: Nein das kann nicht das Problem sein. Natürlich kann man zur Laufzeit neue Fenster erstellen. Denk mal an IDLE, da kannst Du doch zu beliebigen Zeitpunkten beliebig viele neue Editorfenster öffnen.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Ok ich habe festgestellt, dass ich das Toplevel in einem anderen Thread zerstören möchte, als es erstellt wurde. Es handelt sich dabei um einen Thread, der parallel zur mainloop läuft. Ich gehe gerade davon aus, dass es daran liegen könnte.
Jetzt brauche ich eine Möglichkeit der mainloop mitzuteilen, dass der Vorgang des anderen Threads abgeschlossen ist und er das Toplevel zerstören kann.
Wie löse ich das am elegantesten?

Mein Ansatz ist es jetzt eine Warteschleife in die mainloop zu packen, die eine Variable überprüft, die vom anderen Thread geändert wird, sobald dieser fertig ist. Bei Java sind für solche Variablen das Schlüsselwort "volatile" nötig. Gibt es sowas auch für Python?
Oder ist ein anderer Ansatz vielleicht besser?
BlackJack

@moccajoghurt: Eine warteschleife würde ja die `mainloop()` blockieren. Du kannst mit hilfe der `after()`-Methode regelmässig eine Funktion im richtigen Thread aufrufen der das Ende des Threads testet und dann gegebenenfalls das Fenster schliesst.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Es klappt! :)
Danke.
Antworten