Klassenattribut ist nach Erstellung nicht mehr ansprechbar

Fragen zu Tkinter.
BlackJack

@moccajoghurt: Es darf nur ein `Tk`-Exemplar geben und man darf nur aus dem Hauptthread aus GUI-Objekte manipulieren. Solange Du das nicht repariert hast, kann nahezu *alles* passieren, denn Dein Programm hat schlicht kein definiertes Verhalten mehr.

Ansonsten: Am Ende von `download_bar.__init__()` rufst Du eine `mainloop()` auf. Dieser Aufruf kehrt erst zurück wenn das zugehörige Widget zerstört wurde. Du bindest also tatsächlich `mainclass.dl_bar` an keinen Wert bevor es benutzt wird, denn der Aufruf kehrt nicht zurück. Ähnlich wie es nur ein `Tk`-Exemplar geben darf, sollte man nur eine `mainloop()` aufrufen. Das heisst auf deutsch *Haupt*schleife.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Ok jetzt verstehe ich!
Ist es denn möglich eine Progressbar in die Mitte des Hauptfensters zu setzen, ohne dass die anderen Widgets berücksichtigt werden müssen? Das war nämlich der Hauptgrund aus dem ich einfach ein neues Tk() erstellt habe. Ich möchte die Progressbar als unabhängige über den Hauptfenster schwebende Leiste. Geht das?

Ansonsten erstmal danke für die hilfreichen Antworten.
BlackJack

@moccajoghurt: `Tk` ist sozusagen das Hauptfenster. Wenn man weitere Fenster haben möchte kann man `Toplevel` dafür verwenden.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

BlackJack hat geschrieben:@moccajoghurt: `Tk` ist sozusagen das Hauptfenster. Wenn man weitere Fenster haben möchte kann man `Toplevel` dafür verwenden.
Hätte ich das vorher gewusst, hätte ich mir viele Stunden rumprobieren gespart. Aber jetzt weiß ich es.
Vielen Dank nochmal an alle, Thema ist gelöst.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Habe jetzt ein anderes Problem mit der Scrollbar. Alles funktioniert eigentlich wie gewollt, nur wenn die Scrollbar zusammen mit dem Toplevel zerstört werden soll, dann bleibt sie, obwohl das Toplevel zerstört wurde, immernoch sichtbar.

Hier die Klasse:

Code: Alles auswählen

from ttk import Progressbar
import Tkinter


class download_bar():
	
	def __init__(self, main):
		self.mainclass = main
		self.window = Tkinter.Toplevel()
		self.window.overrideredirect(True)
		self.bar = Progressbar(self.window, length=200)
		self.bar.pack(expand=True, fill=Tkinter.BOTH)
		
		self.bar.configure(mode="indeterminate", length=150)
		
		self.loop_bar()
		
	
	
	def loop_bar(self):
		self.bar.start(interval=1)
	
	def kill_bar(self):
		self.bar.stop()
		#~ self.bar.pack_forget()
		#~ self.bar.destroy()
		#~ self.window.withdraw()
		self.window.destroy()
		
	
Wie bekomme ich es hin, dass die Progressbar auch nicht mehr sichtbar ist?
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