Fortschrittsfenster

Fragen zu Tkinter.
Antworten
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Ich habe ein Software-Architektur-Problem.

Ich möchte mit tkinter ein Fenster öffnen und darauf den Fortschritt meiner Berechnungen anzeigen, kein einfacher Fortschrittsbalken, sondern ein informatives Canvas. Das Fenster wäre also nicht besonders interaktiv. Wenn ich aber mit mainloop() die Kontrolle ans Fenster übergebe, kriege ich sie nie wieder zurück. Das Fenster soll ja weiterbestehen, während ich meine Rechnungen ausführe (wie jeder normale Fortschrittsbalken auch), ich möchte nur hin und wieder die Berechnungen unterbrechen und aufs Fenster zugreifen können, um es zu verändern. Ich finde bei tkinter kein Befehl "zeichne das Fenster und übergebe ans Hauptprogramm".

Ich könnte (rein theoretischerweise) die Kontrolle einmal und entgültig an tkinter übergeben, und mein Number-Crunching vom Fenster aus ausrufen, aber ich habe ein *sehr* ungutes Gefühl dabei, (1) weil ich die Möglichkeit offenhalten will, verschiedene und je nach Bedarf verschiedenartige Fortschrittsfenster zu öffnen, und (2) weil dann bei Tkinter vieles Unnötige im Hintergrund läuft, schließlich wird am Fenster während meiner (sehr aufwändigen) Berechnungen nichts verändert.

Wie macht man so etwas richtig?
dahaze
User
Beiträge: 75
Registriert: Freitag 13. März 2009, 10:57
Wohnort: im Schwabenland

Hallo Goswin,

ansich musst du in Tkinter nicht zwingend den Mainloop aufrufen.
Man könnte dessen Funktionalität auch "manuell" mit der update()-Methode erreichen allerdings verarbeitet Tkinter dann entsprechende Events (klicks im Fenster, etc.) auch nur zum Zeitpunkt des Aufrufs von update().
Das funktioniert im ersten Wurf auch mit 2 Rootwindows. Aber ob das so sauber ist...
Ich weiss es nicht mehr genau aber entweder darf es keine 2 Roots oder nur keine 2 mainloops in einer Applikation geben sonst crashts...

Code: Alles auswählen

>>> import time
>>> import Tkinter
>>> root = Tkinter.Tk()
>>> label = Tkinter.Label(root, text=0)
>>> label.grid()
>>> root2 = Tkinter.Tk()
>>> label2 = Tkinter.Label(root2, text=0)
>>> label2.grid()
>>> for i in xrange(100):
... 	label.configure(text=i)
... 	root.update()
... 	time.sleep(0.5)
... 	label2.configure(text=i*2)
... 	root2.update()
... 	time.sleep(0.5)
... 	
>>> root.destroy()
>>> root2.destroy()
Gruß,
dahaze

[wahrscheinlich werde ich wegen diesem Post jetzt gleich von den Admins gesteinigt. ;-)]
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

:D Prima es funktioniert, vielen Dank!

Vom Namen her ist es natürlich schon irgendwie seltsam, dass man etwas "updaten" kann, das noch nicht existiert; deshalb wäre ich auch nie auf die Idee gekommen, "update()" zu benutzen.

Ich habe übrigens auch zwei Fenster erstellt und bei mir ist nichts abgestürzt. Mein Testprogramm war natürlich extrem einfach, aber bisher läuft alles bestens.
BlackJack

@dahaze: Es darf nur ein `Tk`-Objekt geben. Da hängt der Tk-Interpreter dran und dessen interner Zustand und wenn es zwei davon gibt, dann ist Chaos möglich.

@Goswin: Das das läuft ist egal, man darf das auf keinen Fall machen. Das ist wie der Typ der aus dem Hochhausfenster springt und bei jedem Stockwerk meint, bis jetzt ging's gut. ;-)

Das mit `update()` kann man machen, man hebelt damit allerdings die normale Vorgehensweise bei GUI-Programmierung aus. Ausserdem steht im `Tkinter Book` von effbot dieser Satz: „This method should be used with care, since it may lead to really nasty race conditions if called from the wrong place (…).”
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

BlackJack hat geschrieben:Es darf nur ein `Tk`-Objekt geben. Da hängt der Tk-Interpreter dran und dessen interner Zustand und wenn es zwei davon gibt, dann ist Chaos möglich.

Das das läuft ist egal, man darf das auf keinen Fall machen.
Was bei mir läuft, ist sehr ähnlich, aber nicht genau dasselbe wie bei dahaze, nämlich:

Code: Alles auswählen

class Fortschritt(object):

   def __init__(self):
      self._fenster = t.Tk()
      self._cv = t.Canvas(self._fenster,width=400,height=300)
      self._cv.pack()
      self._fenster.update()

   def zeige(self,farbe):
      self._cv.config(bg=farbe)
      self._fenster.update()


fs1 = Fortschritt()
fs2 = Fortschritt()
sleep(2)
#
print("Programm gestartet")
fs1.zeige(farbe='red')
sleep(2)
#
 print("Programm halb durchgelaufen")
fs1.zeige(farbe='green')
fs2.zeige(farbe='cyan')
sleep(2)
#
print("Programm durchgelaufen")
fs2._fenster.destroy()
fs1._fenster.mainloop()
@BlackJack:
Darf man das auch nicht? Und wenn nicht, was ist die empfohlene Alternative, für das und für update()? Es muss ja irgendeine "normale" Möglichkeit geben, mehrere Fortschrittsbalken zu zeichnen.
Zuletzt geändert von Anonymous am Donnerstag 17. Januar 2013, 15:51, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@Goswin: Das sollte so gehen, allerdings friert zwischen den `update()`-Aufrufen die GUI ein. Das ist zum Beispiel unschön wenn man ein anderes Fenster „davor vorbeischiebt” und dann der Fensterinhalt zerstört wird und nicht neugezeichnet wird bis wieder ein `update()` aufgerufen wird.

Der übliche Weg wäre, dass man den länger laufenden Code in einen Thread auslagert, der dann mittels `Queue.Queue` und der `after()`-Methode auf Widgets mit den Fortschrittsbalken kommuniziert.

Edit: Argh, ich hatte nur die Anzahl der im Quelltext vorkommenden `Tk`-Aufrufe gezählt. Aber der Code davon wird ja zweimal ausgeführt und das darf man natürlich *nicht* machen. Das `Tk`-Objekt ist *das* *Haupt*fenster. Davon darf es nur eines geben. Wenn man mehr will, dann ist `Toplevel` die Klasse dafür. Man kann auch mehrere `Toplevel` erstellen und das Hauptfenster mit der `withdraw()`-Methode „verstecken” wenn man es nicht braucht.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@BlackJack:

Danke. Ich teste jetzt einmal folgenden Konstruktor

Code: Alles auswählen

   def __init__(self):
      t.Tk().withdraw()
      #
      self._fenster = t.Toplevel()
      self._cv = t.Canvas(self._fenster,width=400,height=300)
      self._cv.pack()
      self._fenster.update()
Habe ich zu verstehen, dass bei jedem Aufruf des Konstruktors mit "t.Tk().withdraw()" dasselbe Tk immer wieder neu versteckt wird? (Wenn es nur eines davon gibt, dann kann ich mir nichts anderes vorstellen)
Zuletzt geändert von Goswin am Donnerstag 17. Januar 2013, 20:13, insgesamt 2-mal geändert.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Nein, du erstellst mittels ``t.Tk()`` immer wieder ein neues Tk-Objekt und versteckst dieses. Du darfst Tk aber nur einmal in deinem Programm aufrufen. Der Tk-Aufruf hat in der init-Methode nichts zu suchen, den musst du hinter das ``if __name__ == "__main__":`` packen. Wenn du mehrere Fenster brauchst, dann musst du, wie BlackJack es schon geschrieben hat, Toplevel-Objekte erzeugen.

Für Python-Code gibt es übrigens Python-Codeblöcke. Die solltest du verwenden und nicht Zitate, bei denen auch noch die Einrückung verloren geht ;-)
Das Leben ist wie ein Tennisball.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

EyDu hat geschrieben:Für Python-Code gibt es übrigens Python-Codeblöcke. Die solltest du verwenden und nicht Zitate, bei denen auch noch die Einrückung verloren geht ;-)
Alles klar, inzwischen berichtigt!
Zuletzt geändert von Goswin am Donnerstag 17. Januar 2013, 20:15, insgesamt 2-mal geändert.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Goswin hat geschrieben:
EyDu hat geschrieben:Für Python-Code gibt es übrigens Python-Codeblöcke. Die solltest du verwenden und nicht Zitate, bei denen auch noch die Einrückung verloren geht ;-)
Tippfehler, bereits berichtigt!
Schau noch einmal genauer auf die Buttons: Python-Codetags ;-)
Das Leben ist wie ein Tennisball.
Antworten