Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Exiting Tkinter when using IDLE

1,317 views
Skip to first unread message

Batista, Facundo

unread,
Mar 11, 2004, 12:50:45 PM3/11/04
to Python-List (E-mail)
I tested the following simple code:

-----------------
from Tkinter import *

class App:

def __init__(self, master):

frame = Frame(master)
frame.pack()

self.button = Button(frame, text="QUIT", fg="red",
command=frame.quit)
self.button.pack(side=LEFT)

self.hi_there = Button(frame, text="Hello", command=self.say_hi)
self.hi_there.pack(side=LEFT)

def say_hi(self):
print "hi there, everyone!"

root = Tk()
app = App(root)
root.mainloop()
-----------------

I'm on Python 2.3 (#46, Jul 29 2003, 18:54:32) [MSC v.1200 32 bit (Intel)]
on win32.

When I double click the program, it works OK.

When I execute it from IDLE, the first time I push the "QUIT" button, I just
receive a second prompt in IDLE and nothing else happens. The second time I
push the "QUIT" button, the program exits, but also exits IDLE!

Do you know what's happening? With this behaviour it's difficult to develop
using IDLE, :(

. Facundo

Jason Harper

unread,
Mar 11, 2004, 5:15:58 PM3/11/04
to
You have two issues here:

1. Don't use the quit method, instead just close your window. Your QUIT
button could perhaps use:
command=self.destroy

2. Don't call mainloop() if IDLE already has one running. Try this:

import sys
if "idlelib" not in sys.modules:
root.mainloop()

Jason Harper

Gerrit Muller

unread,
Mar 12, 2004, 2:14:24 AM3/12/04
to
Jason Harper wrote:

I think that this answer deserves a place in a cookbook recipe. I did
not work with Tkinter for more than a year, but I remember that I
struggled with exactly the same issues and more or less experimentally
reached this solution.

thanks for the clarification!

regards Gerrit

--
Gaudi systems architecting:
http://www.extra.research.philips.com/natlab/sysarch/

Batista, Facundo

unread,
Mar 12, 2004, 12:36:26 PM3/12/04
to pytho...@python.org

Jason Harper wrote:

#- You have two issues here:
#-
#- 1. Don't use the quit method, instead just close your
#- window.  Your QUIT
#- button could perhaps use:
#-      command=self.destroy

Used command=master.destroy. This way I'm telling "destroy" to the Tk instance.


#- 2. Don't call mainloop() if IDLE already has one running.  Try this:
#-
#- import sys
#- if "idlelib" not in sys.modules:
#-      root.mainloop()



Thank you very much!

As Gerrit Muller, I think this should be standard information. It's important because Tkinter is the built in GUI and IDLE is the built in IDE.

Don't know if Fredrik Lundh is reading this, but I used "An Introduction to Tkinter" from him and I think these issues should be there.

.       Facundo





. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

ADVERTENCIA 

La información contenida en este mensaje y cualquier archivo anexo al mismo, son para uso exclusivo del destinatario y pueden contener información confidencial o propietaria, cuya divulgación es sancionada por la ley.

Si Ud. No es uno de los destinatarios consignados o la persona responsable de hacer llegar este mensaje a los destinatarios consignados, no está autorizado a divulgar, copiar, distribuir o retener información (o parte de ella) contenida en este mensaje. Por favor notifíquenos respondiendo al remitente, borre el mensaje original y borre las copias (impresas o grabadas en cualquier medio magnético) que pueda haber realizado del mismo.

Todas las opiniones contenidas en este mail son propias del autor del mensaje y no necesariamente coinciden con las de Telefónica Comunicaciones Personales S.A. o alguna empresa asociada.

Los mensajes electrónicos pueden ser alterados, motivo por el cual Telefónica Comunicaciones Personales S.A. no aceptará ninguna obligación cualquiera sea el resultante de este mensaje.

Muchas Gracias.

Jason Harper

unread,
Mar 12, 2004, 2:21:22 PM3/12/04
to
An update to this advice I gave:

> 1. Don't use the quit method, instead just close your window. Your QUIT
> button could perhaps use:
> command=self.destroy
>
> 2. Don't call mainloop() if IDLE already has one running. Try this:
>
> import sys
> if "idlelib" not in sys.modules:
> root.mainloop()

This only works (on Windows, at least) if IDLE is _not_ running user
code in a subprocess. I've not found any combination of options that
gets usable results (including the ability to introspect the program
while running) when using a subprocess.

Also, it seems to leave the process running forever if launched outside
of IDLE, even after all the windows are closed. The simplest solution,
assuming a single-window application, seems to be giving the main window
a WM_DELETE_WINDOW protocol handler that does a self.quit(), but only if
IDLE isn't running (determined as shown above).

Here's a little inconsistency that had me really confused for a while:
IDLE, when run from the Start menu (Win2K Pro, Python 2.3.3), uses a subprocess.
IDLE, when run via "Open in IDLE" in a .py* file's right-click menu,
does NOT use a subprocess.
I haven't tracked down just where this difference is coming from.
Jason Harper

Eugene Van den Bulke

unread,
Mar 12, 2004, 3:00:01 PM3/12/04
to
On Fri, 12 Mar 2004 08:14:24 +0100, Gerrit Muller
<gerrit...@embeddedsystems.nl> wrote:

> Jason Harper wrote:
>
>> You have two issues here:
>>
>> 1. Don't use the quit method, instead just close your window. Your QUIT
>> button could perhaps use:
>> command=self.destroy
>>
>> 2. Don't call mainloop() if IDLE already has one running. Try this:
>>
>> import sys
>> if "idlelib" not in sys.modules:
>> root.mainloop()
>>
>> Jason Harper
>
> I think that this answer deserves a place in a cookbook recipe. I did
> not work with Tkinter for more than a year, but I remember that I
> struggled with exactly the same issues and more or less experimentally
> reached this solution.
>
> thanks for the clarification!
>
> regards Gerrit
>

I have a probleme with the "if idlelib" solution ... without the
root.mainloop() the window does not appear after I pressed F5 to run the
programm. But when I type root.mainloop() in the shell window that opens
up, it works.

Any idea? Thanks for your help.


--
Eugene Van den Bulke
[-----
www.boardkulture.com
www.actiphot.com
www.xsbar.com
-----]

Jason Harper

unread,
Mar 12, 2004, 4:25:53 PM3/12/04
to
Eugene Van den Bulke wrote:
> I have a probleme with the "if idlelib" solution ... without the
> root.mainloop() the window does not appear after I pressed F5 to run the
> programm. But when I type root.mainloop() in the shell window that opens
> up, it works.

That sounds like your IDLE is running your program in a subprocess,
which means that it can't share IDLE's mainloop. See the followup I posted.

In a subprocess, you can just have root.mainloop() at the end of your
program unconditionally, but this prevents any communication back to the
main IDLE process - your program works fine, but you can't inspect it.
Jason Harper

Kurt B. Kaiser

unread,
Mar 13, 2004, 2:00:44 AM3/13/04
to
"Batista, Facundo" <FBat...@uniFON.com.ar> writes:

> #- 1. Don't use the quit method, instead just close your
> #- window. Your QUIT
> #- button could perhaps use:
> #- command=self.destroy
>
> Used command=master.destroy. This way I'm telling "destroy" to the Tk
> instance.

When the last widget is destroyed, the mainloop exits.

[...]

> As Gerrit Muller, I think this should be standard information. It's
> important because Tkinter is the built in GUI and IDLE is the built
> in IDE.

Start the Python interpreter (not IDLE) and run your code using
self.quit. You'll see it that it works. Now run two Pythons at the
same time with that code. Everything is fine, self.quit works for
both. So it's not some interaction with the windowing system.

When IDLE uses its subprocess, the intent is that there are two
separate processes, each with a Tkinter application running. One is
the user's, the other is the IDLE GUI's. This should work as well as
the example in the previous paragraph.

When IDLE doesn't use a subprocess, the user's Tk gets mixed with
IDLE's, and it's not surprising things fail.

For me, on OpenBSD, Linux, and W2K, using the subprocess: if I use
self.quit, the Dialog becomes unresponsive because the mainloop has
been exited. But the advertised action on mainloop exit is to destroy
any remaining widgets, and that's not happening. If I restart the
shell, the Tk widget is destroyed correctly.

This certainly appears to be a bug in IDLE. It's been reported
before:

https://sourceforge.net/tracker/index.php?func=detail&aid=661324&group_id=9579&atid=109579

First person to get me a solution gets their name in the IDLE credits.

Hint: some part of the IDLE GUI namespace is being passed to the subprocess.

import sys
sys.modules.keys()

--
KBK

Kurt B. Kaiser

unread,
Mar 13, 2004, 2:16:57 AM3/13/04
to
Jason Harper <Jason...@pobox.com> writes:

> Here's a little inconsistency that had me really confused for a
> while: IDLE, when run from the Start menu (Win2K Pro, Python 2.3.3),
> uses a subprocess. IDLE, when run via "Open in IDLE" in a .py*
> file's right-click menu, does NOT use a subprocess. I haven't
> tracked down just where this difference is coming from.

Right now, we only allow one copy of the IDLE subprocess, because the
socket port is hard coded. The specification of the -n switch is in
the Windows Explorer File Types assignment. We did that deliberately
because people often click on several .py files to view them and that
tried to start several copies of IDLE + subprocess, which failed.

You can have as many copies of IDLE without the subprocess as you
like, but they aren't nearly as useful for code development.

The plan is to remove the hard coding of the port so several copies of
IDLE + subprocess can run simultaneously. I am delaying that until I'm
pretty sure that the subprocess is well behaved under all conditions.

--
KBK

Jason Harper

unread,
Mar 13, 2004, 2:51:44 AM3/13/04
to
"Kurt B. Kaiser" wrote:
> When IDLE uses its subprocess, the intent is that there are two
> separate processes, each with a Tkinter application running. One is
> the user's, the other is the IDLE GUI's. This should work as well as
> the example in the previous paragraph.

Running a Tkinter app in the subprocess works, you just can't debug it
if it runs long enough to get into its mainloop.

> When IDLE doesn't use a subprocess, the user's Tk gets mixed with
> IDLE's, and it's not surprising things fail.
>
> For me, on OpenBSD, Linux, and W2K, using the subprocess: if I use
> self.quit, the Dialog becomes unresponsive because the mainloop has
> been exited. But the advertised action on mainloop exit is to destroy
> any remaining widgets, and that's not happening. If I restart the
> shell, the Tk widget is destroyed correctly.

Where did you see that "advertised"? quit() does nothing but cause the
innermost mainloop() to exit, and exiting a mainloop() does nothing
other than no longer processing events. mainloops()s can be nested,
they CAN'T have side-effects like that! If you exit without somehow
calling destroy() on all your toplevels, it's the OS's process
termination cleanup that kills the rest of them, as far as I can tell.
And of course IDLE's subprocess doesn't terminate just because your
Python code finished.

It would appear that IDLE needs to detect that the user program is using
Tkinter (by importing Tkinter itself and overriding mainloop?), and in
that case switch to a different strategy for handling communication with
the main IDLE process - a routine that constantly reschedules itself via
after(), most likely. The problem is the initial RPC request that runs
the user program: I'm pretty sure that the shell window is going to be
non-responsive until it receives the reply, which isn't going to happen
until the program exits.
Jason Harper

Kurt B. Kaiser

unread,
Mar 14, 2004, 3:23:38 AM3/14/04
to
Jason Harper <Jason...@pobox.com> writes:

> Running a Tkinter app in the subprocess works, you just can't debug it
> if it runs long enough to get into its mainloop.

Slightly modified from OP:

=================
from Tkinter import *

class App:

def __init__(self, master):

frame = Frame(master)
frame.pack()

self.button = Button(frame, text="QUIT", fg="red",

command=self.quit)
self.button.pack(side=LEFT)

self.hi_there = Button(frame, text="Hello",
command=self.say_hi)
self.hi_there.pack(side=LEFT)

def say_hi(self):
print "hi there, everyone!"

def quit(self):
print "quitting"
root.quit()
#root.destroy()

root = Tk()
app = App(root)
root.mainloop()

====================

Using IDLE w/subprocess, set a breakpoint (right click) on 'print "quitting"',
Turn on the debugger, then Run/F5 this program. Click on 'Go', then
the apps' QUIT button. The debugger will break on the print
statement. You can then Step into Tkinter. The last line in the
debugger window is

> 'Tkinter".quit(), line 968: self.tk.quit()

So this is working as I would expect. The debugger only traces Python.

Maybe I don't understand what you mean by "debug".

>> When IDLE doesn't use a subprocess, the user's Tk gets mixed with
>> IDLE's, and it's not surprising things fail.
>>
>> For me, on OpenBSD, Linux, and W2K, using the subprocess: if I use
>> self.quit, the Dialog becomes unresponsive because the mainloop has
>> been exited. But the advertised action on mainloop exit is to destroy
>> any remaining widgets, and that's not happening. If I restart the
>> shell, the Tk widget is destroyed correctly.
>
> Where did you see that "advertised"?

line 968 in Tkinter.py:

def quit(self):
"""Quit the Tcl interpreter. All widgets will be destroyed."""
self.tk.quit()

> quit() does nothing but cause the innermost mainloop() to exit, and
> exiting a mainloop() does nothing other than no longer processing
> events. mainloops()s can be nested,

What would be the application? I've never tried that.

> they CAN'T have side-effects like that!

Looking at the code for .../Modules/_tkinkter.c:Tkapp_Quit(), it
merely sets the quitMainLoop global to 1. This causes the 'while'
in Tkapp_MainLoop() to terminate. It looks like the doc string
is incorrect.

...

If the above code is run using IDLE (no debugger), once the QUIT is
pressed the widget becomes inactive and IDLE returns to its prompt,
as OP indicated.

Here's where it gets interesting. Run the above code as follows:

python -i batista3.py

When you click on QUIT, Python prints 'quitting' and returns to its
prompt. However, the buttons are still active, you can print the
messages as often as you like. It appears that the mainloop is
still active. I haven't figured that one out yet. It's the
same on Linux, and with Python2.2.2.

> If you exit without somehow calling destroy() on all your toplevels,
> it's the OS's process termination cleanup that kills the rest of
> them, as far as I can tell. And of course IDLE's subprocess doesn't
> terminate just because your Python code finished.

You are correct about that. The best way is to use root.destroy(),
as commented out in the above code. Most Tkinter code calls
sys.exit or runs off the end when you quit() the mainloop, so the
residual widgets aren't noticed.

It's unfortuate that the sample Tkinter program uses quit():

http://www.python.org/doc/current/lib/node633.html,

IMHO it's an example that gets people off to a bad start.

If you destroy() the root widget, Tkapp_MainLoop()'s call to
Tk_GetNumMainWindows() returns 0 and the MainLoop exits. destroy() is
a good method which works both with IDLE and the interactive
interpreter.

Tkinter's quit() doesn't seem to map to Tk. When using Tcl/Tk, the
usual way to exit Tk_MainLoop is to destroy the root window.

From the Tk8.3 manual:
"Tk_MainLoop is a procedure that loops repeatedly calling
Tcl_DoOneEvent. It returns only when there are no applications left in
this process (i.e. no main windows exist anymore)."

> It would appear that IDLE needs to detect that the user program is
> using Tkinter (by importing Tkinter itself and overriding
> mainloop?), and in that case switch to a different strategy for
> handling communication with the main IDLE process - a routine that
> constantly reschedules itself via after(), most likely. The problem
> is the initial RPC request that runs the user program: I'm pretty
> sure that the shell window is going to be non-responsive until it
> receives the reply, which isn't going to happen until the program
> exits.

I'm not sure what your objective is for this complication. If you
run the above code, you'll see it has no difficulty sending output
to the IDLE GUI's Shell window. You can also add a raw_input() to
the quit() method, and it works fine.

--
KBK

Jason Harper

unread,
Mar 14, 2004, 1:25:34 PM3/14/04
to
"Kurt B. Kaiser" wrote:
> Using IDLE w/subprocess, set a breakpoint (right click) on 'print "quitting"',
> Turn on the debugger, then Run/F5 this program. Click on 'Go', then
> the apps' QUIT button. The debugger will break on the print
> statement. You can then Step into Tkinter. The last line in the
> debugger window is
>
> > 'Tkinter".quit(), line 968: self.tk.quit()
>
> So this is working as I would expect. The debugger only traces Python.
>
> Maybe I don't understand what you mean by "debug".

In a non-subprocess IDLE, you don't have to hit a breakpoint to be able
to examine a running Tkinter program. Both the shell window and the
user program are fully responsive at the same time. For example, you
could type a command to change the appearance of a button, and
IMMEDIATELY try out the button with its new appearance, since the
mainloop is still running. This ability is lost in a subprocess IDLE.

> > quit() does nothing but cause the innermost mainloop() to exit, and
> > exiting a mainloop() does nothing other than no longer processing
> > events. mainloops()s can be nested,
>
> What would be the application? I've never tried that.

Modal dialogs. If you need an answer from the user right now, before
anything else is done, you can create a new toplevel and allow
interaction with it via a nested mainloop (or more likely, something
that internally calls mainloop(), like wait_window() or one of the
standard dialog routines).
Jason Harper

Jason Harper

unread,
Mar 14, 2004, 7:15:00 PM3/14/04
to
Ok, this hasn't been tested very well, but it seems to make
IDLE+subprocess work as I'd want it to with Tkinter applications. Just
replace Lib/idlelib/run.py with this file:
http://home.earthlink.net/~jasonrandharper/run.py

You can even do interactive experimentation with Tkinter now. Try this:

>>> from Tkinter import *
>>> root = Tk()
>>> mainloop()

At this point, IDLE's shell window would normally cease to accept input.
Now, you immediately get a prompt back, although internally it's
working rather differently than a normal prompt. Meanwhile, the Tk
window is fully movable, resizable, and closable.

>>> Button(root, text="spam!").pack()

The button immediately appears and is clickable (of course, it doesn't
do much).

>>> Button(root, text="die!", command=root.destroy).pack()

If you use this button, or close the window, the mainloop will exit.
You'll need to create a new root window, and start a new mainloop() if
you want to do anything further with Tk.
Jason Harper

Kurt B. Kaiser

unread,
Mar 14, 2004, 8:03:28 PM3/14/04
to
Jason Harper <Jason...@pobox.com> writes:

> In a non-subprocess IDLE, you don't have to hit a breakpoint to be able
> to examine a running Tkinter program. Both the shell window and the
> user program are fully responsive at the same time. For example, you
> could type a command to change the appearance of a button, and
> IMMEDIATELY try out the button with its new appearance, since the
> mainloop is still running. This ability is lost in a subprocess IDLE.

OK, another good reason to retain the non-subprocess capability. The
user Tkinter code is mixed in with IDLE's Tkinkter code, but there is
a practical use for experimenting with layouts. To get this to work
cleanly, you eliminate the mainloop() call and use destroy():

===================
IDLE without subprocess (-n switch):

from Tkinter import *

class App:

def __init__(self, master):

frame = Frame(master)
frame.pack()

self.button = Button(frame, text="QUIT", fg="red",
command=self.quit)
self.button.pack(side=LEFT)

self.hi_there = Button(frame, text="Hello",
command=self.say_hi)
self.hi_there.pack(side=LEFT)

def say_hi(self):
print "hi there, everyone!"

def quit(self):
print "quitting"
a = raw_input("prompt: ")
print a
root.destroy()

root = Tk()
app = App(root)

=======================

And, as you say, you can do amusing things like

>>> app.button["text"]
'QUIT'
>>> app.button["text"] = "REALLY QUIT"

--
KBK

Batista, Facundo

unread,
Mar 15, 2004, 7:42:56 PM3/15/04
to Python-List (E-mail)
Kinda lost throug the mails.

At the end, and taking care of all that considerations, what is the
recommended method to quit?

Thank you all!

. Facundo


#- -----Mensaje original-----
#- De: k...@shore.net [mailto:k...@shore.net]
#- Enviado el: Domingo 14 de Marzo de 2004 10:03 PM
#- Para: pytho...@python.org
#- Asunto: Re: Exiting Tkinter when using IDLE
#-
#-
#- Jason Harper <Jason...@pobox.com> writes:
#-
#- > In a non-subprocess IDLE, you don't have to hit a
#- breakpoint to be able
#- > to examine a running Tkinter program. Both the shell
#- window and the
#- > user program are fully responsive at the same time. For
#- example, you
#- > could type a command to change the appearance of a button, and
#- > IMMEDIATELY try out the button with its new appearance, since the
#- > mainloop is still running. This ability is lost in a
#- subprocess IDLE.
#-
#- OK, another good reason to retain the non-subprocess capability. The
#- user Tkinter code is mixed in with IDLE's Tkinkter code, but there is
#- a practical use for experimenting with layouts. To get this to work
#- cleanly, you eliminate the mainloop() call and use destroy():
#-
#- ===================
#- IDLE without subprocess (-n switch):
#-
#- from Tkinter import *
#-
#- class App:
#-
#- def __init__(self, master):
#-
#- frame = Frame(master)
#- frame.pack()
#-
#- self.button = Button(frame, text="QUIT", fg="red",
#- command=self.quit)
#- self.button.pack(side=LEFT)
#-
#- self.hi_there = Button(frame, text="Hello",
#- command=self.say_hi)
#- self.hi_there.pack(side=LEFT)
#-
#- def say_hi(self):
#- print "hi there, everyone!"
#-
#- def quit(self):
#- print "quitting"
#- a = raw_input("prompt: ")
#- print a
#- root.destroy()
#-
#- root = Tk()
#- app = App(root)
#-
#- =======================
#-
#- And, as you say, you can do amusing things like
#-
#- >>> app.button["text"]
#- 'QUIT'
#- >>> app.button["text"] = "REALLY QUIT"
#-
#- --
#- KBK
#- --
#- http://mail.python.org/mailman/listinfo/python-list
#-

Kurt B. Kaiser

unread,
Mar 16, 2004, 1:53:43 AM3/16/04
to
"Batista, Facundo" <FBat...@uniFON.com.ar> writes:

> At the end, and taking care of all that considerations, what is the
> recommended method to quit?

root.destroy()

--
KBK

0 new messages