Zwei Fenster unabhängig voneinander schließen

Fragen zu Tkinter.
Joa
User
Beiträge: 14
Registriert: Samstag 21. Mai 2011, 12:49

Hey,
ich habe ein Fenster programmiert in das man Werte eingibt um daraus eine Funktion zu plotten. Dieses Fenster verfügt über zwei Buttons, einen "Quit" und einen "Plot" Button. Nach Eingabe der Werte drückt man den "Plot" Button und es öffnet sich ein neues Fenster in das die Funktion geplottet wird. Dieses Fenster verfügt ebenfalls über einen "Quit" Button. Wenn ich nun einen der beiden "Quit" Buttons drücke schließen sich beide Fenster. Ich möchte aber eigentlich immer nur das Fenster schließen dessen jeweiligen Button ich drücke.

Der Code um die Fenster zu schließen sieht so aus:
Fenster mit dem Plot

Code: Alles auswählen

RenRoot = Tk()
rWidget = vtkTkRenderWidget(RenRoot, width='800', height='600')

button = Button(RenRoot, text='quit', command=RenRoot.quit)
button.pack(expand='true', fill='x')
Eingabefenster:

Code: Alles auswählen

root = Tk()
Button(root, text='Quit', command=root.quit)
Wie muss ich die Fenster denn definieren um sie einzeln zu schließen bzw welchen Befehl muss ich dafür benutzen?
deets

War es nicht so, dass man in tk nur *eine* Instanz von Tk() haben darf? Du hast zwei, und das ist schlecht, denke ich.

Schau dir doch mal stattdessen toplevel an, damit bekommt man neue Fenster, die sich auch unabhaengig vom Programm schliessen lassen sollten.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo Joa

Hier ein möglicher Weg:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from functools import partial
import Tkinter as tk

class PlotterWindow(tk.Canvas):
    
    def __init__(self, app_win):
        
        self.app_win = app_win
        
        self.plott_win = tk.Toplevel()
        self.plott_win.geometry('500x500')
        
        tk.Canvas.__init__(self, self.plott_win, bg='steelblue3')
                
        button_frame = tk.Frame(self.plott_win, bg=self.plott_win['bg'])
        button_frame.pack(side='bottom', fill='x')
               
        button_quit = tk.Button(button_frame, text='Schliessen',
            command=self.plott_win.destroy)
        button_quit.pack(side='left', expand=True)
        
def create_plott_win(app_win, plott_win_collection):
    """Neues Plott-Fenster"""
    plott_win = PlotterWindow(app_win)
    plott_win.pack(fill='both', expand=True)
    plott_win_collection.append(plott_win)
    
def plott_app():
    """Hauptprogramm"""
    
    #~~ Lade den Dateiname dieses Skriptes
    script_name = sys.argv[0]

    #~~ Konstanten für die Abmessungen des Hauptfensters
    MAIN_WIN_XPOS   = 10
    MAIN_WIN_YPOS   = 10
    MAIN_WIN_WIDTH  = 320
    MAIN_WIN_HEIGHT = 320

    #--- Erstelle ein Tk-Hauptfenster ----
    app_win = tk.Tk()

    app_win.geometry("%dx%d+%d+%d" % (MAIN_WIN_WIDTH, MAIN_WIN_HEIGHT,
        MAIN_WIN_XPOS, MAIN_WIN_YPOS))
      
    app_win['bg'] = 'khaki'
    app_win.title(script_name)

    plott_win_collection = list()

    prolog = tk.Label(app_win, text='My Plotter', font=('Helvetica', 20, 'bold'),
        fg='blue', bg=app_win['bg'])
    prolog.pack(expand='yes')

    entry_frame = tk.Frame(app_win, bg=app_win['bg'])
    entry_frame.pack(side='top',expand=True)

    entry_prop = dict( bg='white', bd=1, highlightthickness='0')

    entry_1 = tk.Entry(entry_frame, entry_prop)
    entry_1.pack(pady=2)
    entry_1.insert(0, 'Plott-Data-1')

    entry_2 = tk.Entry(entry_frame, entry_prop)
    entry_2.pack(pady=2)
    entry_2.insert(0, 'Plott-Data-2')

    button_frame = tk.Frame(app_win, bg=app_win['bg'])
    button_frame.pack(side='bottom', fill='x')

    button_pack_prop = dict(side='left', expand=True, pady=5)

    cmd_button_start = partial(create_plott_win, app_win, plott_win_collection)
    button_start = tk.Button(button_frame,text='Erstelle Plott-Fenster',
        command=cmd_button_start)
    button_start.pack(button_pack_prop)

    button_end = tk.Button(button_frame, text='Beenden',
        command=app_win.destroy)
    button_end.pack(button_pack_prop)

    app_win.mainloop()

if __name__=="__main__": 

    plott_app()
Gruß wuf :wink:
Take it easy Mates!
problembär

Wo ist das ".wait_window()" (ohne das Tk-Anwendungen mit Dialogfenstern instabil werden)?
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Habe ".wait_window()" noch nie eingesetzt. Wurde bis jetzt auch noch nicht nie mit Instabilitäten konfrontiert. Hast du ein Beispiel, welches die von dir erwähnte Instabilität auslösen kann?
Take it easy Mates!
problembär

Nein, aber Du glaubst mir nicht, daß der Mainloop korrumpiert wird, wenn die Programmausführung nach Erstellung eines Toplevels sofort zurückkehrt?
Wozu gibt es dann wohl ".wait_window()"?

Wie gesagt, ein Beispiel kann ich hier nicht posten, aber ich hatte mal ein größeres Programm mit mehreren Dialog-Fenstern, das schmierte unter Windows öfters plötzlich ab. War mir zuerst unerklärlich. Bis ich die Sache mit ".wait_window()" entdeckte.
Ich hatte das sogar schon in der instabilen Version an andere herausgegeben. Das war ziemlich peinlich.
Seitdem ist ".wait_window()" für mich das allerwichtigste. Kann ich nur empfehlen.

Mensch wuf, Du weißt doch nun schon so viel über Tkinter. Diesen Trick kannst Du dann doch auch noch lernen. ;)

http://www.pythonware.com/library/tkint ... indows.htm
BlackJack

@problembär: Also ich glaube das auch nicht. `wait_window()` braucht man wenn man einen modalen Dialog haben möchte. Aber wenn man das zwingend verwenden *müsste*, könnte man ja nicht mehrere, unabhängig aktive Fenster in einem Programm haben. Was mit Tkinter aber definitiv möglich ist.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Sehe ich genauso, "wait_window" wird eigentlich nur benötigt, wenn ein Toplevel-Widget tatsächlich auf ein anderes warten muss und das ist in Dialogen immer der Fall. Mir fällt allerdings keine Gegebenheit ein wo man das außerhalb eines Dialoges benötigen würde.

@wuf
Bau dein Beispiel mal bitte anders rum. Ich finde das ziemlich verwirrend das ein Canvas-Widget ein eigenes Toplevel-Widget aufruft und sich dann selbst darauf packt. Besser du leitest von TopLevel ab und packst dann den Canvas drauf.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
problembär

BlackJack hat geschrieben:@problembär: Also ich glaube das auch nicht. `wait_window()` braucht man wenn man einen modalen Dialog haben möchte. Aber wenn man das zwingend verwenden *müsste*, könnte man ja nicht mehrere, unabhängig aktive Fenster in einem Programm haben. Was mit Tkinter aber definitiv möglich ist.
Für Dialog-Fenster trotzdem die empfohlene Methode. Steht auch in allen guten Tutorials, z.B.:

http://effbot.org/tkinterbook/tkinter-d ... indows.htm

Mein (kurzes und schon etwas älteres) Beispiel für ein Dialog-Fenster sieht ja aus wie folgt. Zu beachten ist, daß das Hauptfenster trotz ".wait-window()" nicht blockiert wird, obwohl es "wartet". Man hat da also trotzdem mehrere, unabhängig aktive Fenster in einem Programm:

Code: Alles auswählen

#!/usr/bin/env python

import Tkinter as tk
import tkMessageBox

class myWindow:

    def __init__(self):
        self.mw = tk.Tk()
        self.mw.option_add("*font", ("Arial", 15, "normal"))
        self.mw.geometry("+250+200")
        self.mw.title("Example of Custom Dialog-Window")
        self.btn1 = tk.Button(self.mw,
                                   text = "Click button to open dialog-window.",
                                   command = self.btnClick)
        self.btn1.pack(padx = 20, pady = 20)
        self.btn2 = tk.Button(self.mw,
                                   text = "Exit",
                                   command = self.mw.destroy)
        self.btn2.pack(pady = 10)
        self.mw.mainloop()

    def btnClick(self):
        self.dialogwindow = tk.Toplevel()
        self.dialogwindow.title("Dialog Window")
        self.dialogwindow.geometry("+300+250")
        self.lab1 = tk.Label(self.dialogwindow,
                                  text = "Please enter something:")
        self.lab1.pack()
        self.entr1 = tk.Entry(self.dialogwindow)
        self.entr1.pack()
        self.entr1.focus()
        self.btn3 = tk.Button(self.dialogwindow,
                                   text = "Ok",
                                   command = self.dialogEnd)
        self.btn3.pack()
        # This is the important line: It tells the main-window to wait:
        self.mw.wait_window(self.dialogwindow)

    def dialogEnd(self):
        e = self.entr1.get()
        self.dialogwindow.destroy() 
        tkMessageBox.showinfo(title = "Input", message = "You entered:\n\n" + e + "\n")
        
if __name__ == "__main__":
   app = myWindow()
Nur, daß diese Fenster dann die Chance haben, mit den Eingaben in die anderen richtig umzugehen.

... Lustigerweise gibt's da trotzdem einen Fehler, wenn man gleichzeitig mehrere Instanzen des Eingabefensters öffnet und dort Eingaben macht. :) Na, besser ein Fehler als ein unerklärlicher Absturz.

Ich kann mir immer noch nicht vorstellen, daß ihr nur aus Sturheit instabile GUIs schreiben wollt ...
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

wuf hat geschrieben:Habe ".wait_window()" noch nie eingesetzt.
Geht mir genauso.
BlackJack

@problembär: Dein Beispiel wartet in der Tat nicht und ist deshalb kaputt. Man kann mehrere von den Eingabefenstern gleichzeitig öffnen, die funktionieren aber nicht mehr richtig wenn man eines davon geschlossen hat. In der Konsole tauchen dann beim druck auf ”Ok” der weiteren Fenster Ausnahmen nach dem Muster ``TclError: invalid command name ".166968748.166969036"`` auf und man muss die Fenster über das ”x” von der Fensterverwaltung schliessen.

Ich will nicht aus Sturheit instabile GUIs schreiben, ich habe nur noch nie irgendwo den Hinweis gelesen dass man `wait_window()` benötigt. Zeig mir eine Dokumentation, die das aussagt, und ich werde es verwenden. Ansonsten glaube ich das jetzt einfach mal nicht.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

".wait_window()" und ".wait_visibility()" braucht man durchaus wenn man einen stabilen Dialog erzeugen möchte. Wenn man keinen Dialog haben möchte kann man noch ".wait_visibility()" durchaus nutzen, aber ".wait_window()" ist dann ziemlich Sinnfrei, da außerhalb von Dialogen keine Daten von einem Toplevel zurückgegeben werden sollten. Es wird auch von der Tkinter Dialog-Klasse benutzt.

Aus der offiziellen Documentation:
Tk/Tcl 8.5.10 Doku hat geschrieben:...the tkwait command waits for that window to be destroyed. This form is typically used to wait for a user to finish interacting with a dialog box before using the result of that interaction.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi poblembär

Erstmals Danke für dein Test-Skript mit welchem sich deine Feststellung nachweisen lässt. Da ich bis jetzt eher selten auf dem von dir verwendeten OS TK- Anwendungen programmiert habe fehlt mir deine Erfahrung die du damit gemacht hast. Da gibt es sicher immer wieder Unterschiede von OS zu OS.
problembär hat geschrieben:Mensch wuf, Du weißt doch nun schon so viel über Tkinter. Diesen Trick kannst Du
dann doch auch noch lernen. ;)
Sicher nehme ich deine Warnung ernst und werde sie zukünftig profilaktisch auch in meinen Skripts berücksichtigen damit diese auf anderen OS nicht schmieren :-). Danke noch für deinen sehr interessanten Hinweis. Von wegen lernen. Lernen müssen wir das ganze Leben durch. Darum möchte ich auch dir auch noch eine Tipp geben (sei mir nicht böse). Schau doch auch wieder einmal in das Dokumment mit dem mystischen Namen PEP8. Es lohnt sich. Ich weiss ich habs auch noch nicht ganz reingezogen.

@BlackJack: Danke, dass du problembärs Test-Skript auf dem benannten OS schon austesten konntest. Ich bin leider noch nicht dazugkommen. Werde dies aber sicher noch nachholen.
problembär hat geschrieben:Ich kann mir immer noch nicht vorstellen, daß ihr nur aus Sturheit instabile GUIs schreiben wollt ...
Finde ich eine harte Äusserung!

@Xynon1: Danke auch noch für deine Hinweise, Links und Anregungen.

OK! Macht es gut.
Gruß an alle von wuf ;-)
Take it easy Mates!
BlackJack

@wuf: Wieso glaubst Du das jetzt plötzlich oder wenn nicht, was ist der Sinn etwas ”prophylaktisch” in ein Programm aufzunehmen was keinen weiteren Effekt hat als das Programm komplexer zu machen als es sein müsste?

Wenn man für ein Toplevel zwingend ein `wait_window()` bräuchte, damit das Programm nicht instabil wird, dann sollte das doch wenigstens in *irgendeiner* Tkinter- oder Tk-Dokumentation stehen. Das wäre dann ja nicht ganz unwichtig. Ich kann diesen Hinweis aber nirgends finden. Auf der Gegenseite finde ich aber `Tkinter`-Quelltext, wie zum Beispiel IDLE wo `wait_window()` auch nur bei modalen Dialogen verwendet wird. Bei anderen `Toplevel`-Objekten wird es dort nicht verwendet.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hallo BlackJack
BlackJack hat geschrieben:@wuf: Wieso glaubst Du das jetzt plötzlich oder wenn nicht, was ist der Sinn etwas ”prophylaktisch” in ein Programm aufzunehmen was keinen weiteren Effekt hat als das Programm komplexer zu machen als es sein müsste?
Danke BlackJack, dass du mich wachgerüttelt hast. Habe mich näher mit problemsbärs Test-Skript beschäftigt. Deine herausgefundenen Effekte konnte ich unter Linux (Kubuntu) und Windows (Vista) nachvollziehen. Es muss etwas mit dem Programmierstil problembärs zu tun haben. Überarbeitete das Testskript völlig und stellte unter Linux & Windows für den Moment keine Probleme mehr fest. Dies auch ohne jemals die Methode ".wait_window()" verwendet zu haben. Erstelle meine Dialoge normalerweise selber und verzichte auf die zur Verfügung gestellten tk.Dialoge. Hier mein Skript:

Code: Alles auswählen

#!/usr/bin/env python

# Forum-Adr: python-forum.de/viewtopic.php?p=202101#p202101

from functools import partial
import Tkinter as tk
import tkMessageBox

class MyDialog(tk.Toplevel):

    def __init__(self, app_win):

        tk.Toplevel.__init__(self)
        self.title("Dialog Window")
        self.geometry("+300+250")
        
        label = tk.Label(self, text="Please enter something:")
        label.pack()
        
        entry = tk.Entry(self)
        entry.pack()
        entry.focus()
        
        button_ok = tk.Button(self, text="Ok",
            command=partial(self.dialog_end, entry))
        button_ok.pack()
        
    def dialog_end(self, entry):
        
        entry_data = entry.get()
        self.destroy()
        tkMessageBox.showinfo(
            title="Input", message="You entered:\n\n" + entry_data + "\n")

def create_dialog(app_win):
    
    app_win.dialog_collection.append(MyDialog(app_win))
    
def main():

    app_win = tk.Tk()
    app_win.option_add("*font", ("Arial", 15, "normal"))
    app_win.geometry("+250+200")
    app_win.title("Example of Custom Dialog-Window")
    
    app_win.dialog_collection = list()
    
    button_dialog = tk.Button(app_win,
        text="Click button to open dialog-window.", 
        command=partial(create_dialog, app_win))
    button_dialog.pack(padx=20, pady=20)
    
    button_exit = tk.Button(app_win,
        text="Exit", command=app_win.destroy)
    button_exit.pack(pady=10)
    
    app_win.mainloop()

if __name__ == "__main__":
   app = main()
wuf hat geschrieben:Sicher nehme ich deine Warnung ernst und werde sie zukünftig prophylaktisch auch in meinen Skripts berücksichtigen damit diese auf anderen OS nicht schmieren :-)
Ich wiederufe meinen Spruch und werde dies sicher nicht tun!

Feststellung:
@problembär: Du musst noch einmal über deine Bücher gehen!

Gruß wuf :wink:
Take it easy Mates!
problembär

BlackJack hat geschrieben:Wenn man für ein Toplevel zwingend ein `wait_window()` bräuchte, damit das Programm nicht instabil wird, dann sollte das doch wenigstens in *irgendeiner* Tkinter- oder Tk-Dokumentation stehen. Das wäre dann ja nicht ganz unwichtig. Ich kann diesen Hinweis aber nirgends finden. Auf der Gegenseite finde ich aber `Tkinter`-Quelltext, wie zum Beispiel IDLE wo `wait_window()` auch nur bei modalen Dialogen verwendet wird. Bei anderen `Toplevel`-Objekten wird es dort nicht verwendet.
Ich weiß nicht, ob "Beweise einzufordern" hier die richtige Vorgehensweise ist. Es gibt jedenfalls dieses '.wait_window()' und in manchen Dokumentationen wird empfohlen, es für Dialog-Fenster auch zu verwenden. (Zwei hatte ich schon angegeben, aber von mir aus auch z.B. für Perl/Tk hier.)
Dann sollte man sich doch eher fragen, 'Was ist das?' und 'Wozu ist es da?' anstatt gleich festzustellen 'Das ist für gar nichts da!' und 'Ich weigere mich, das zu verwenden!'.
Ich stecke in den Interna von Tk nicht drin, deshalb weiß ich nicht genau, was dieses 'wait_window()' bewirkt und wie. Allerdings habe ich wie gesagt beobachtet, daß nur damit eine Anwendung mit mehreren Dialogfenstern stabil lief.
Ob das an meinem "Programmierstil" liegt, kann zwar sein, aber ich glaube eigentlich nicht. Mein Beispiel unterscheidet sich ja nun nicht so stark von wufs letzter Version.
Mein Beispiel war übrigens leider sehr unglücklich gewählt: "tkMessageBox" erstellt ja gerade Dialog-Fenster. Wenn also '.wait_window()' dafür nötig sein sollte, wird es davon wahrscheinlich auch aufgerufen. Das würde in wufs letztem Beispiel die Probleme verschleiern, weil ja wahrscheinlich über 'tkMessageBox' ein '.wait_window()' ausgeführt würde. Das ist aber mein Fehler: Ich hatte ja 'tkMessageBox' in meinem Beispiel verwendet.
Also: Ich kann euch nicht mit letzter Gewißheit sagen, wann es ohne '.wait_window()' zu Schwierigkeiten kommt. Aber es gibt diese Methode, und wahrscheinlich hat sie einen bestimmten Zweck. Das zu ignorieren ist keine Lösung.

Gruß
BlackJack

@problembär: Warum sollte Beweise einfordern nicht die richtige Vorgehensweise sein? Du stellst eine Behauptung auf ohne sie belegen zu können.

Ich habe nie behauptet die Funktion wäre nutzlos oder das ich nicht weiss für welchen Zweck sie da ist. Im Gegenteil: Ich weiss das man sie für modale Dialogfenster verwendet. Und wenn man kein modales Fenster haben möchte, dann braucht man auch diese Funktion nicht.

Ich ignoriere die Funktion nicht — ich finde es bloss unsinnig sie einfach so zu verwenden um vielleicht irgendwelche Instabilitäten zu verhindern, von denen Dir ja anscheinend auch nicht so klar ist, ob es sie überhaupt gibt. Das sieht nach Voodoo-Programmierung aus. Die Tk-Götter besänftigen oder so. ;-)
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

problembär hat geschrieben:Ich stecke in den Interna von Tk nicht drin, deshalb weiß ich nicht genau, was dieses 'wait_window()' bewirkt und wie. Allerdings habe ich wie gesagt beobachtet, daß nur damit eine Anwendung mit mehreren Dialogfenstern stabil lief.
In der aktuellen 4. Auflage von Mark Lutz' "Programming Python" wird wait_window() auf S. 440ff ausführlich behandelt.
Über die Suche im Buch nach wait_window bekommt man die Seiten 440ff angezeigt: http://oreilly.com/catalog/9780596158118/preview
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@BlackJack
"Die Tk-Götter besänftigen oder so." - :lol: den muss ich mir merken.
Du sprichst immer von modalen Dialogfenstern, zeichnet sich ein Dialog nicht zuletzt dadurch aus das es ein modales Fenster ist?

@problembär
Die in der Perl Dokumentation beschriebene Warnung
http://cpan.uwinnipeg.ca/htdocs/Tk/Tk/Widget.html#i_widget_i_gt_strong_waitWindow_stro hat geschrieben:...The return value is always an empty string...
spielt bei diesem Beispiel keine Rolle, weil die Daten nur in MyDialog ausgewertet und daher nicht von einem anderen Fenster benötigt werden. Wie ich schon sagte solange man keine Daten von einem Fenter zu einem anderen übermitteln möchte welches das andere Fenster zu einem bestimmten Zeitpunkt benötigt, so brauch man kein ".wait_window()". Zu Abstürzen sollte ein fehlendes ".wait_window()" auch nicht direkt führen, sondern nur in einen leeren Rückgabewert, welcher dann bei falscher Handhabung zum Absturz führen könnte.

@wuf
Ich wäre bei der Namensgebung bei ...Window geblieben, da es sich ja nicht wirklich um einen Dialog handelt. Bei echten Dialogen würde ich im übringen von tkSimpleDialog ableiten.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
BlackJack

@Xynon1: Es gibt ja nicht-modale Dialoge. Sollte man die dann anders nennen, und wenn ja wie?

Es gibt auch Diskussionen im Netz die dafür plädieren Dialoge möglichst nicht-modal zu implementieren. Also nicht die Freiheiten des Nutzers einschränken, nur weil es für den Programmierer bequemer ist, den Rest der Anwendung solange ”anzuhalten”.
Antworten