Is it necessary to call Tk() when writing a GUI app with Tkinter?

J

John Salerno

The book I'm reading about using Tkinter only does this when creating the top-level window:

app = Application()
app.mainloop()

and of course the Application class has subclassed the tkinter.Frame class.

However, in the Python documentation, I see this:

root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()

Is it necessary to explicitly call Tk(), then pass that result as an argument for the Application call? Is it also necessary to call destroy() on the root frame?

I tried the above and I got the following error:

Traceback (most recent call last):
File "C:\Users\John\Desktop\gui.py", line 12, in <module>
root.destroy()
File "C:\Python32\lib\tkinter\__init__.py", line 1714, in destroy
self.tk.call('destroy', self._w)
_tkinter.TclError: can't invoke "destroy" command: application has been destroyed

So apparently closing the window with the X button (on Windows) implicitly calls the destroy() method of the root frame. If that's the case, why does the documentation explicitly call it?

Furthermore, I pasted the exact example from the documentation into IDLE and ran it, and I also go the same error, so the example in the documentation doesn't even work.

So is it sufficient simply to create an Application instance, use mainloop, and then handle the closing of the window elsewhere in the program (such as a widget calling the destroy method on-click, or just letting the X button do it)?

Thanks!
 
R

Rick Johnson

The book I'm reading about using Tkinter only does this when creating thetop-level window:

app = Application()
app.mainloop()

and of course the Application class has subclassed the tkinter.Frame class.

However, in the Python documentation, I see this:

root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()

Is it necessary to explicitly call Tk(), then pass that result as an argument for the Application call? Is it also necessary to call destroy() on the root frame?

It is not necessarily to call Tk explicitly, which i think is a bug
BTW. Sure, for simple scripts you can save one line of code but only
at the expense of explicitness and intuitiveness. Observe

## START CODE ##
import Tkinter as tk

root = tk.Tk()
root.title('Explicit Root')
root.mainloop()

f = tk.Frame(master=None, width=100, height=100, bg='red')
f.pack()
f.mainloop()

b = tk.Button(master=None, text='Sloppy Coder')
b.pack()
b.mainloop()
## END CODE ##

as you can see all three examples work even though the last two don't
explicitly create a master. The master is still there however Tkinter
just created "magically" for you. Talk about laziness!
I tried the above and I got the following error:

Traceback (most recent call last):
  File "C:\Users\John\Desktop\gui.py", line 12, in <module>
    root.destroy()
  File "C:\Python32\lib\tkinter\__init__.py", line 1714, in destroy
    self.tk.call('destroy', self._w)
_tkinter.TclError: can't invoke "destroy" command:  application has been destroyed

So apparently closing the window with the X button (on Windows) implicitly calls the destroy() method of the root frame. If that's the case, why does the documentation explicitly call it?

Because the documentation is FLAWED! Please provide links to this
"documentation" so we can post it on the Wall Of Shame.
Furthermore, I pasted the exact example from the documentation into IDLE and ran it, and I also go the same error, so the example in the documentation doesn't even work.

IDLE uses the same Python as the command line so naturally it will
throw the same error. ;-)
So is it sufficient simply to create an Application instance, use mainloop, and then handle the closing of the window elsewhere in the program (suchas a widget calling the destroy method on-click, or just letting the X button do it)?

Most applications will have both: user destroying, and program
destroying. Again, your example is FLAWED. Here is a simplified
example:

## START CODE ##

from tkMessageBox import askyesnocancel

class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title('Close Me -->')
self.protocol("WM_DELETE_WINDOW", self.onDestroyWindow)

def onDestroyWindow(self):
title = 'Confirm App Exit'
msg = 'Save changes before exiting?'
result = askyesnocancel(title, msg, default='cancel')
if result is None:
return
elif result is True:
print 'saving changes'
elif result is False:
print 'dont save changes'
self.destroy()

if __name__ == '__main__':
app = App()
app.mainloop()

## END CODE ##
 
T

Terry Reedy

I do not know if tk has changed since the example was written or if it
was buggy from the beginning. I opened an issue to fix it.

http://bugs.python.org/issue14163
Most applications will have both: user destroying, and program
destroying.
from tkMessageBox import askyesnocancel

from tkinter.messagebox in 3.x
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title('Close Me -->')
self.protocol("WM_DELETE_WINDOW", self.onDestroyWindow)

def onDestroyWindow(self):
title = 'Confirm App Exit'
msg = 'Save changes before exiting?'
result = askyesnocancel(title, msg, default='cancel')
if result is None:
return
elif result is True:
print 'saving changes'
elif result is False:
print 'dont save changes'
self.destroy()

if __name__ == '__main__':
app = App()
app.mainloop()

This works as adjusted for 3.x. I presume that a quit button or menu
entry should also call onDestroyWindow so the effect is the same as
clicking the outer [X] button.

I tried the same approach to fix the doc example, but unlike your class
App(Tk), class App(Frame) does not a .protocol attribute. See the
tracker issue for all my comments on the example.

I considered removing both the quit button and 'root.destroy' to get a
beginning example that works properly, but as you said, having both is
common so I would like both if the solution is not too esoteric.
 
R

Rick Johnson

On 2/29/2012 9:24 AM, Rick Johnson wrote:

 >> implicitly calls the destroy() method of the root frame.
 >> If that's the case, why does the documentation explicitly call it?

I do not know if tk has changed since the example was written or if it
was buggy from the beginning. I opened an issue to fix it.

http://bugs.python.org/issue14163

"protocol" is ONLY a method of Tkinter.Tk and Tkinter.Toplevel.
Actually Toplevel and Tk are exactly the same object but Tk has an TCL
interpretor attached. Tkinter.Tk is meant to be the parent of ALL
widgets within your Tkinter GUI. Tkinter.Frame is nothing more than a
box to stuff widgets into. Tkinter.Frame IS NOT a window and therefor
it DOES NOT have window methods.

HOWEVER!

Most people start falsely believing that a Tkinter.Frame AND
Tkinter.Toplevel are the same thing; since Tkinter will "auto-
magically" pack your frame into a default Tkinter.Tk window (psst:
that's just a fancy Toplevel widget!) if you don't explicitly create
the Tk instance yourself.
This inconsistency also rears it's ugly head in the dialog modules:
tkFileDialog and tkMessageBox. Both of which do not require a parent
argument to their convenience functions. Instead they allow the parent
to be OPTIONALLY passed.

Inquisitive Ivan mused: """ What's wrong with that Rick, the dialog
will still display whether a parent argument is passed or not. Heck,
even if no viable parent exists, Tkinter will create one! Tkinter is a
smart module!"""

True Ivan. However, if you dig a little deeper you will see that the
dialog created WITHOUT a parent does not function in a manner
consistent to modal dialogs; that is, owning the focus and becoming a
transient of another Toplevel window.

Also, about your second point, noobs get confused when that default
root window pops up. Then they come here and ask the same question
over and over. Can't you see the design flaw that is directly in front
of your face NOR smell the pungent odors that reeking from this
module!
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Some might argue that this implicit root window creation is beneficial
for toy GUIs -- and i agree! HOWEVER, you will due enormous damage to
a neophyte's learning process. I say just force the extra line of code
and be consistent. Actually i believe SO strongly in "explicit root
window creation" that i edited the source code of my Tkinter version
to NOT allow ANY widget's master to be None.

I had written a more exhaustive "expo-say" some time back but i
cannot remember the title.
from tkinter.messagebox in 3.x

Yes, Tkinter has changed a bit in Python>=3.0, thanks for pointing
this out.
[...snip code...]
This works as adjusted for 3.x. I presume that a quit button or menu
entry should also call onDestroyWindow so the effect is the same as
clicking the outer [X] button.

Yes, but i think the REAL problem is faulty code logic. Remove the
last line "root.destroy()" and the problem is solved. Obviously the
author does not have an in-depth knowledge of Tkinter.
I tried the same approach to fix the doc example, but unlike your class
App(Tk), class App(Frame) does not a .protocol attribute. See the
tracker issue for all my comments on the example.

see above comments about Tkinter.Frame, Tkinter.Toplevel, and
Tkinter.Tk ^^^
I considered removing both the quit button and 'root.destroy' to get a
beginning example that works properly, but as you said, having both is
common so I would like both if the solution is not too esoteric.

If you want to keep things simple, i would:

1. Create the root window explicitly!
2. Bind the command of the button to root.destroy
(command=root.destroy)

I would offer better advice if i could but i have no idea where this
"book the OP is learning" is located? No one ever provided a link to
the code in question?

PS: I would highly suggest against using the "from Tkinter import *".
Instead, use "import Tkinter as tk" and prefix all module contents
with "tk.". Also, use "from Tkconstants import X, Y, X"
 
J

John Salerno

It is not necessarily to call Tk explicitly, which i think is a bug
BTW. Sure, for simple scripts you can save one line of code but only
at the expense of explicitness and intuitiveness. Observe

## START CODE ##
import Tkinter as tk

root = tk.Tk()
root.title('Explicit Root')
root.mainloop()

f = tk.Frame(master=None, width=100, height=100, bg='red')
f.pack()
f.mainloop()

b = tk.Button(master=None, text='Sloppy Coder')
b.pack()
b.mainloop()
## END CODE ##

as you can see all three examples work even though the last two don't
explicitly create a master. The master is still there however Tkinter
just created "magically" for you. Talk about laziness!

I'm not sure I understand which method you are advocating. It sounded like you said calling Tk() explicitly is a bug. I certainly would never create widgets without first creating a master frame, but is creating a Frame object enough, or should I create a Tk object and *then* a Frame object?

Also, at what point do you include the destroy method in your program, assuming you do not have a widget that will close the window? If you only want the Windows "X" button to close the window, then is it okay to leave out any call to destroy()? Or should always explicitly destroy it just as I explicitly created a Tk instance? If the latter, then where in the code do you put the call to destroy so it won't conflict with the user closing the window with the X button?
 
T

Terry Reedy

On 2/29/2012 10:22 PM, Rick Johnson wrote:
I do not know what book the OP is referring to,
but the current doc example is
http://docs.python.org/py3k/library/tkinter.html#a-simple-hello-world-program
My current replacement (see below) can be downloaded from the tracker:
http://bugs.python.org/issue14163
If you want to keep things simple, i would:

1. Create the root window explicitly!

It already does that.
2. Bind the command of the button to root.destroy
(command=root.destroy)

That works great. My current replacement example is uploaded to the
issue http://bugs.python.org/issue14163
PS: I would highly suggest against using the "from Tkinter import *".
Instead, use "import Tkinter as tk" and prefix all module contents
with "tk.".

I have changed the example to do that. I also showed the alternate to
initialize a widget. Here is the current version, tested on Windows 3.2.2.

import tkinter as tk

class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()

def createWidgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Hello_World\n(click_me)",
self.hi_there["command"] = self.say_hi
self.hi_there.pack({"side": "top"})

self.QUIT = tk.Button(self, text = "QUIT", fg = "red", command
= root.destroy)
self.QUIT.pack({"side": "bottom"})

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

root = tk.Tk()
app = Application(master=root)
app.mainloop()

There is a minor problem left. The hi_there Button text has underscores
because if I use spaces instead, tk surrounds the text with {bra ces}.
This seems bizarre. Is there any way to have Button text with spaces and
no braces?
 
J

John Salerno

Yes, but i think the REAL problem is faulty code logic. Remove the
last line "root.destroy()" and the problem is solved. Obviously the
author does not have an in-depth knowledge of Tkinter.

The faulty code is not my own, which is part of the reason I asked the question. The book I'm reading (The Quick Python Book) does not use it, but I saw in the Python docs that it is there, under "tkinter" in the Global Module Docs, "24.1.2.2. A Simple Hello World Program":


from tkinter import *

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

def createWidgets(self):
self.QUIT = Button(self)
self.QUIT["text"] = "QUIT"
self.QUIT["fg"] = "red"
self.QUIT["command"] = self.quit

self.QUIT.pack({"side": "left"})

self.hi_there = Button(self)
self.hi_there["text"] = "Hello",
self.hi_there["command"] = self.say_hi

self.hi_there.pack({"side": "left"})

def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()

root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()
 
T

Terry Reedy

window? If you only want the Windows "X" button to close the window,
then is it okay to leave out any call to destroy()?

Yes. You must leave it out.
the latter, then where in the code do you put the call to destroy so
it won't conflict with the user closing the window with the X
button?

See my other post of a few minutes ago for an example that now works.
 
J

John Salerno

Yes. You must leave it out.


See my other post of a few minutes ago for an example that now works.

When you suggested I create the root frame explicitly, you mean create a Tk object explicitly, as in your example, and then pass that as an argument to the Frame instance?

What exactly is the purpose of doing that? Does Tk do some extra work that a simple call to Frame won't do?
 
J

John Salerno

Yes. You must leave it out.


See my other post of a few minutes ago for an example that now works.

When you suggested I create the root frame explicitly, you mean create a Tk object explicitly, as in your example, and then pass that as an argument to the Frame instance?

What exactly is the purpose of doing that? Does Tk do some extra work that a simple call to Frame won't do?
 
J

John Salerno

What exactly is the purpose of doing that? Does Tk do some extra work that a simple call to Frame won't do?

More specifically, what is the benefit of doing:

root = tk.Tk()
app = Application(master=root)
app.mainloop()

as opposed to:

app = Application()
app.mainloop()

Also, in the first example, what is the difference between calling app.mainloop() and root.mainloop()? They both seemed to work when I ran them.
 
J

John Salerno

What exactly is the purpose of doing that? Does Tk do some extra work that a simple call to Frame won't do?

More specifically, what is the benefit of doing:

root = tk.Tk()
app = Application(master=root)
app.mainloop()

as opposed to:

app = Application()
app.mainloop()

Also, in the first example, what is the difference between calling app.mainloop() and root.mainloop()? They both seemed to work when I ran them.
 
J

John Salerno

Yes. You must leave it out.

Now I'm reading a Tkinter reference at
http://infohost.nmt.edu/tcc/help/pubs/tkinter/minimal-app.html
and it has this example:


#!/usr/local/bin/python
from Tkinter import *

class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.grid()
self.createWidgets()

def createWidgets(self):
self.quitButton = Button ( self, text='Quit',
command=self.quit )
self.quitButton.grid()

app = Application()
app.master.title("Sample application")
app.mainloop()


Is this just outdated? I don't understand why it uses quit() instead of destroy(). When I try this in IDLE, quit() just causes the application to hang(I assume because it ends the mainloop without actually closing the application). Or is this just a problem when using IDLE?

If the latter, which is preferable, quit or destroy?
 
J

John Salerno

Yes. You must leave it out.

Now I'm reading a Tkinter reference at
http://infohost.nmt.edu/tcc/help/pubs/tkinter/minimal-app.html
and it has this example:


#!/usr/local/bin/python
from Tkinter import *

class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.grid()
self.createWidgets()

def createWidgets(self):
self.quitButton = Button ( self, text='Quit',
command=self.quit )
self.quitButton.grid()

app = Application()
app.master.title("Sample application")
app.mainloop()


Is this just outdated? I don't understand why it uses quit() instead of destroy(). When I try this in IDLE, quit() just causes the application to hang(I assume because it ends the mainloop without actually closing the application). Or is this just a problem when using IDLE?

If the latter, which is preferable, quit or destroy?
 
J

John Salerno

Now I'm reading a Tkinter reference at
http://infohost.nmt.edu/tcc/help/pubs/tkinter/minimal-app.html and it
has this example:
[...]

Could you please stop posting the same message twice? It's very annoying.
There is no need to C.C. python-list because comp.lang.python is the
mirror of the mailing list.


Thank you.

I'm sorry, it's unintentional. The new interface for Google Groups keeps doing weird things and makes double and triple posts. I deleted them from my end, but I guess they are still showing up for everyone else.
 
R

Rick Johnson

I'm not sure I understand which method you are advocating. It
sounded like you said calling Tk() explicitly is a bug.

I am saying just the opposite: Allowing the module "Tkinter" to
implicitly create a root window IF the programmer was too lazy to
create a root window himself is ridiculous, confusing, and
inconsistent; therefor it's a BUG! Observe:

INCORRECT:
import Tkinter as tk
b = tk.Button(master=None, text='Sloppy Coder')
b.pack()
b.mainloop()

CORRECT:
import Tkinter as tk
root = tk.Tk()
b = tk.Button(root, text='Smart Coder')
b.pack()
root.mainloop()

IMO, only the widget "Tkinter.Tk" should have the method mainloop! If
you call w.mainloop() and "w" is NOT an instance of Tkinter.Tk, then
an error should be raised; I decided to raise a LazyCoderError in MY
version of the Tkinter module just to drive the point home.
I certainly would never create widgets without first creating a
master frame,

You really have no choice otherwise. Each and every widget requires a
parent argument (or parent=None, which will then create and use the
Tkinter._default_root; BUG!). The fact that Tkinter "magically"
creates the root window for you does not negate the fact that every
widget requires a parent. This is why i think the official version of
Tkinter is flawed. And the fix is very simple actually.
but is creating a Frame object enough, or should I create a Tk
object and *then* a Frame object?

No matter what, you MUST create an instance of Tkinter.Tk to be the
"root" window of your GUI application. After that, you can nest as
many frames, toplevels, and blah widgets under that root window as you
so desire. Actually you don't even need a "frame", you can pack
widgets directly into a Toplevel or Tk widget.

EXAMPLE 1: (this works, but is flawed!)
root = tk.Tk()
b = tk.Button(master=None, text='Sloppy Coder')
b.pack()
root.mainloop()

EXAMPLE 2: (This is how to write code!)
root = tk.Tk()
widgetframe = tk.Frame(root)
b = tk.Button(master=None, text='Sloppy Coder')
b.pack()
root.mainloop()

EXAMPLE 3: (OOP style)
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# something should happen here to justify using OOP
# or here

class AppFrame(tk.Frame):
def __init__(self, master, **kw):
tk.Frame.__init__(self, master, **kw)
self.createWidgets()

def createWidgets(self):
b = tk.Button(master=None, text='Push Me')
b.pack()

if __name__ == '__main__':
app = App()
frame = AppFrame(app)
frame.pack()
app.mainloop()

Also, at what point do you include the destroy method in your
program, assuming you do not have a widget that will close the
window? If you only want the Windows "X" button to close the window,
then is it okay to leave out any call to destroy()?

Yes. GUI destruction should (almost always) be controlled by the user.
In certain circumstances you will want to force destruction, or
intercept a users request for destruction so you can do something
first (cleanup, sanity checks, etc...).
Or should always explicitly destroy it just as I explicitly created
a Tk instance?

NO, you should create the root and then allow the user to decide when
to destroy the GUI. Always remember the phrase: "User Driven Events"
when coding a GUI!
If the latter, then where in the code do you put the call to destroy
so it won't conflict with the user closing the window with the X
button?

In the "very special" cases where you need to destory the GUI, make
sure no code can run AFTER you destroy the root window. Whoever wrote
that example was obviously too lazy to run the code and check for
errors or subtle bugs.
 
R

Rick Johnson

On 2/29/2012 10:22 PM, Rick Johnson wrote:

I have changed the example to do that. I also showed the alternate to
initialize a widget. Here is the current version, tested on Windows 3.2.2..

import tkinter as tk

class Application(tk.Frame):
     def __init__(self, master=None):
         tk.Frame.__init__(self, master)
         self.pack()

With all due respect, I would also recommend against "self packing" a
widget. And i can speak from experience on this issue. There was a
time when i was self-packing lots of custom compund widgets; then i
realized later the shortcomings of such action; what if you need to
use the grid or place geometry mangers instead? So remove the
self.pack line and add a line to the bottom:
root = tk.Tk()
app = Application(master=root)
app.pack() # <-- added this line
app.mainloop()

There is a minor problem left. The hi_there Button text has underscores
because if I use spaces instead, tk surrounds the text with {bra ces}.
This seems bizarre. Is there any way to have Button text with spaces and
no braces?

Not sure what is happening on your end, but i don't see any braces. In
any event, here is a slightly modified version of your code that
follows PEP8 and removes some inconsistencies.

## START CODE ##
# Python < 3.0
import Tkinter as tk
from Tkconstants import TOP, BOTTOM
from tkMessageBox import showinfo

class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.createWidgets()

def createWidgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Hello_World\n(click_me)"
self.hi_there["command"] = self.say_hi
self.hi_there.pack() # !!!
self.qbutton = tk.Button(self, text="Close Application",
fg="red", command=root.destroy)
self.qbutton.pack() # !!!

def say_hi(self):
showinfo('Modal Dialog', "hi there, everyone!", parent=self)
print("hi there, everyone!")

if __name__ == '__main__':
root = tk.Tk()
app = Application(master=root)
app.pack()
app.mainloop()
## END CODE ##
 
R

Rick Johnson

The faulty code is not my own, which is part of the reason I asked
the question. The book I'm reading (The Quick Python Book) does not
use it, but I saw in the Python docs that it is there, under
"tkinter" in the Global Module Docs, "24.1.2.2. A Simple Hello World
Program":

Book authors and Doc authors are not always the most well informed; as
we have witnessed by this very thread!
from tkinter import *

don't do that!
class Application(Frame):
    def say_hi(self):
        print("hi there, everyone!")

    def createWidgets(self):
        self.QUIT = Button(self)
        self.QUIT["text"] = "QUIT"
        self.QUIT["fg"] = "red"
        self.QUIT["command"] = self.quit

        self.QUIT.pack({"side": "left"})

don't do that either! Widgets take optional keyword arguments now so
stop passing in dictionaries.

self.quit.pack(side=tk.LEFT)

Obviously these tutorials are more like: "What NOT to do when coding
Tkinter GUIs!" No wonder everyone hates Tkinter. :)
 
R

Rick Johnson

More specifically, what is the benefit of doing:

root = tk.Tk()
app = Application(master=root)
app.mainloop()

as opposed to:

app = Application()
app.mainloop()

Also, in the first example, what is the difference between calling app.mainloop() and root.mainloop()? They both seemed to work when I ran them.

I tell you what. Just keep coding Tkinter GUIs like these broken docs
tell you to, than after you become proficient you will understand. You
will say: "That Rick really knew what he was talking about after all!"
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,781
Messages
2,569,615
Members
45,297
Latest member
EngineerD

Latest Threads

Top