Thread stoppt wenn Menüpunkt angeklickt wird

Fragen zu Tkinter.
Antworten
marcia
User
Beiträge: 14
Registriert: Freitag 16. März 2012, 15:36

Hallo,

vorweg ich schreibe mein erstes Fensterbasierendes Python Programm mit TKInter. Die Oberfläche funktioniert ohne Probleme. Zusätzlich starte ich vor mainloop() einen Thread.

Code: Alles auswählen

thread.start_new_thread(test, (0,))
Im Thread wird, zunächst nur als Test, eine kurze Routine ausgeführt. Diese gibt die Zeit alle 2 Sekunden an die Messagebox me1 aus.

Code: Alles auswählen

def test(a):
	global infotext
	infotext = str()
	while True:	
		infotext = infotext[0:5000]
		infotext = "%s\n%s" % (str(time.ctime()), infotext)
		try:
			me1.configure(text=infotext)
		except Exception, detail: 
			print "ERROR \n%s" % (detail) 
		time.sleep(2)
Das funktioniert ebenfalls. Wenn ich aber eine Menüpunkt anklicke, bleibt die Thread Abarbeitung solange stehen. Sobald ich nochmals irgendwohin klicke wird der Thread weiter ausgeführt. Das Menü sieht folgendermaßen aus

Code: Alles auswählen

menubar = Menu(main)
filemenu = Menu(menubar, tearoff=0)
helpmenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="Datei", menu=filemenu)
menubar.add_cascade(label="Info", menu=helpmenu)
main.config(menu=menubar)
filemenu.add_command(label="Exit", command=main.quit)
helpmenu.add_command(label="Info", command=info)
Meine Frage ist, habe ich etwas falsch gemacht oder vergessen? Der Thread soll immer laufen egal was gerade auf der Oberfläche passiert (daher auch while True:). Das Ziel soll sein, einen Status an dieser Stelle mitzuloggen. So wie es momentan funktioniert ist es eher unbefriedigend.

Gruß Marcia
deets

GUIs & multi-threading ist eine schlechte Idee - sowas funktioniert so gut wie nie, bzw. wenn nur ueber speziell dafuer vorgesehene Funktionalitaeten.

In Tkinter ist es denke ich das kluegste einen after-call mit entsprechend sinnvoller Verzoegerung periodisch aufzurufen, der den thread-Zustand prueft, und die GUI manipuliert. Eine Moeglichkeit, direkt vom Thread etwas in den mainloop zu injizieren waere mir da zumindest nicht bekannt - mag aber auch gehen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo und willkommen im Forum!

Wie deet schon geschrieben hat, ist die Kombination von Threads und GUIs immer eine trickreiche Sache. Viele Toolkits bieten für solche Szenarien entsprechende Mechannsmen an, bei Tkinter musst du dich selber darum kümmern. Die vorgeschlagene after-Methode ist hier der richtige Ansatz, am besten kombiniert mit dem Queue-Modul. Immer wenn der Thread seinen Status propagieren möchte, wird der Status in die Warteschlange geschoben und die GUI arbeitet die Elemente in der after-Methode ab.

Weiter solltest du global nicht mehr benutzen, dann machst du im Normalfall etwas falsch. Mittels Rückgabewerten und Parametern lässt sich das viel eleganter lösen. Offensichtlich nutzt dein Thread auch bereits einen Parameter, ein paar mehr schaden also nicht.

Gibt es einen Grund, warum im Thread alle Exceptions blind abgefangen werden? Das sieht mehr eher danach aus, dass du hoffst, dass durch den try-except-Block auf magische Weise irgendwelche Fehler behoben werden. Auch finde ich die Generierung von "infotext" etwas seltsam aus. Warum verwendest du nicht ein entsprechendes Widget?

Sebastian
Das Leben ist wie ein Tennisball.
marcia
User
Beiträge: 14
Registriert: Freitag 16. März 2012, 15:36

Hallo,

vielen Dank für die Antworten. Konnte einiges davon umsetzen.

@deets
Das bedeutet also ein Thread darf schon laufen, sollte nur nichts mit der GUI zu tun haben? Danke für Deinen Hinweis zu der After Funktion. Erinnert mich irgendwie an die JavaScript setTimeout. ;-)

@EyDu
Das mit der Queue Funktion habe ich mir angesehen und auch ausprobiert. Allerdings habe ich die Variable als global markiert damit ich sie von einer weiteren Funktion aus auf Null setzen kann. Mir ging es also um das Löschen des Inhalts. Das funktioniert wohl mit der Queue eben nicht.

Sicherlich kann man darauf verzichten global zu benutzen. Nur muß man dann die Variable bei sämtlichen Funktionsaufrufen "durchschleifen". Das war mir für einen ersten Test einfach zu umständlich. Die OO Programmierung werde ich mir noch intensiver ansehen, hier steckt die wirkliche Lösung des Problems.

Der Aufruf der Exceptions "blind", diente vorallem dazu, das die Beendung des Scriptes übersprungen wurde. Das wird im produktiven Status nicht mehr verwendet!


Wenn ich durch einen Aufruf aus dem Menü mit TopLevel ein Fenster öffne, möchte ich das dieses erst geschlossen wird bevor ich wieder das Hauptfenster erreichen kann. Sprich wenn man im Menü auf Info klickt, soll nicht bei jedem Klick ein weiteres Fenster geöffnet werden. Wie ereiche ich das?
Google ist eine super Sache! Allerdings nur wenn man die richtigen Schlagworte eingibt. Zu dem oben genannten Problem habe ich mit meinen Schlagworten nichts gefunden.
Quasi ein infofenster.isopen() oder so ähnlich. :)

Gruß Marcia
BlackJack

@marcia: Für Dialoge die erst bearbeitet und geschlossen werden müssen, bevor man im Rest der GUI wieder etwas machen kann/darf ist das Suchwort „modal” oder „modaler Dialog”.

Wenn es Dir nur um das Öffnen von einem Fenster geht, welches es nur einmal geben soll, dürfte die Antwort auf Objektorientierung hinauslaufen, denn dann müsstest Du Dir einfach das offene Fenster merken, und es erstellen falls es noch nicht existiert. Falls es das Fenster schon gibt, könnte man es dann zum Beispiel auch in den Fokus holen, wenn der Benutzer die Schaltfläche oder den Menüeintrag aktiviert.
marcia
User
Beiträge: 14
Registriert: Freitag 16. März 2012, 15:36

Hallo Blackjack,

das Ziel soll im Prinzip so aussehen wie z.B. in Firefox der Klick in der Menüleiste auf Extras -> Einstellungen. Bevor ich das dann aufspringende Fenster nicht wieder schließe kann ich in Firefox nicht weiterarbeiten.

Modale Fenster kenne ich, kann ich hier ein Formular unterbringen. Ich habe heraus gelesen das dies nicht geht.

Das ich mir das Öffnen eines TopLevel Fensters merken soll, ist kein Problem. Wie bekomme ich heraus ob der Benutzer das Fenster mit einem Klick oben rechts auf das X geschlossen hat?

Gruß Marcia
BlackJack

@marcia: In einem modalen Fenster kannst Du alles unterbringen was Du auch in einem nicht-modalen Fenster darstellen kannst. Warum sollte es da irgendwelche Einschränkungen geben?

Man kann mit ``widget.protocol('WM_DELETE_WINDOW', handler)`` eine Rückruffunktion (`handler`) registrieren, die aufgerufen wird, wenn das Fenster (`widget`) von der Fensterverwaltung entfernt wird. Unabhängig davon wie das Schliessen zustande kam.
marcia
User
Beiträge: 14
Registriert: Freitag 16. März 2012, 15:36

Hallo BlackJack,

ja genau solche Begriffe wie WM_DELETE_WINDOW muß man kennen. Sonst hilft da google auch nicht weiter, hab Dank!

Bei der Gelegenheit, wofür ist es denn sinnvoll das man mehrere Fenster mit einem Button/Menüpunkt aufrufen kann? Habe mal darüber nachgedacht und ich bräuchte das nirgends. Selbst Programme wie Textverarbeitungen, wo sowas Sinn macht, rufen dazu ein neues Hauptfenster auf.

Wie stelle ich es denn an, daß ein TopLevel Fenster nicht über die Maße das Hauptfensters hinauskommt. Also maximal genauso groß wird, daß es in das Hauptfenster reinpaßt?

Habe mir vorgestellt, daß solche Funktionen längst eingebaut sind und nicht jedesmal neu "erfunden" werden müssen... 8-o
Oder ist das nur bei TKInter so?

Gruß Marcia
BlackJack

@marcia: Mein E-Mail-Programm macht das zum Beispiel wenn ich eine neue Mail schreiben möchte. Da geht jedes mal ein neues Fenster auf, so dass man auch mehrere Mails „parallel” verfassen kann.

Fenster innerhalb von Fenstern nennt sich üblicherweise „multiple document interface” (MDI). Das gibt es in `Tkinter` in der Tat nicht. Da gibt es ja nicht einmal ein Tabellen-Widget in der Grundausstattung. Wobei MDI auch ein wenig eine Glaubensfrage ist. Das stört oft wenn man mit mehreren virtuellen Desktops arbeitet und diese Fenster nicht frei verteilen kann, wie man es gerade braucht. Man kann dann immer nur die gesamte Anwendung auf einmal darstellen.
marcia
User
Beiträge: 14
Registriert: Freitag 16. März 2012, 15:36

Hallo BlackJack,

ja stimmt, macht mein Mail-Client auch und das ist auch recht nützlich! Vorerst werde ich das Formular mit TopLevel aufrufen und nur die Fehlerausgaben mit einem modalem Fenster realisieren.

Ein kleiner Schönheitsfehler ist allerdings, das das TopLevel Fenster hinter das Hauptfenster fällt, wenn ich ein modales Fenster aufrufe. Das modale Fenster steht im focus. Zwar kann ich danach mit focus das Fenster wieder zurückholen, aber eben erst wenn das modale Fenster geschlossen wurde.

Code: Alles auswählen

			tkMessageBox.showwarning("Verbindungstest", "Keine Verbindung möglich! (1)\n%s" % (response_data))
			global top
			top.focus()
Schöner wäre, man behält die Daten im Formular des TopLevel Fensters im Blick, wenn es eine Meldung ala tkMessageBox gibt. Wie stelle ich das an bzw. wie so drängt sich hier das Hauptfenster vor?
Gruß Marcia
Antworten