27

If I run the following code from a terminal, I get a helpful error message in the terminal:

import Tkinter as tk

master = tk.Tk()

def callback():
    raise UserWarning("Exception!")

b = tk.Button(master, text="This will raise an exception", command=callback)
b.pack()

tk.mainloop()

However, if I run it without a terminal (say, by double-clicking an icon), the error message is suppressed.

In my real, more complicated Tkinter application, I like that the GUI is a little crash-resistant. I don't like that my users have a hard time giving me useful feedback to fix the resulting unexpected behavior.

How should I handle this? Is there a standard way to expose tracebacks or stderror or whatnot in a Tkinter application? I'm looking for something more elegant than putting try/except everywhere.

EDIT: Jochen Ritzel gave an excellent answer below that pops up a warning box, and mentioned attaching it to a class. Just to make this explicit:

import Tkinter as tk
import traceback, tkMessageBox

class App:
    def __init__(self, master):
        master.report_callback_exception = self.report_callback_exception
        self.frame = tk.Frame(master)
        self.frame.pack()
        b = tk.Button(
            self.frame, text="This will cause an exception",
            command=self.cause_exception)
        b.pack()

    def cause_exception(self):
        a = []
        a.a = 0 #A traceback makes this easy to catch and fix

    def report_callback_exception(self, *args):
        err = traceback.format_exception(*args)
        tkMessageBox.showerror('Exception', err)

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

My remaining confusion: Jochen mentions the possibility of having different exception reporting functions in different frames. I don't yet see how to do that. Is this obvious?

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
Andrew
  • 2,842
  • 5
  • 31
  • 49
  • The exception is still output when you double click the icon. It's just that you're not printing standard out anywhere. – Falmarri Jan 22 '11 at 22:22
  • Agreed! I'm looking for people to recommend an elegant/standard way to expose stdout or stderror to the user. – Andrew Jan 22 '11 at 22:29
  • 2
    The class `App` is a frame, usually derived from `tk.Frame`. If your program had two different frame classes that were used for different things, then each frame class could have its own version of `report_callback_exception()` that displays the error in a different way. – Don Kirkby Aug 24 '15 at 18:42
  • In python 3, `tkMessageBox` has moved to `tkinter.messagebox`, so `import tkinter.messagebox` then in the body of `report_callback` `tkinter.messagebox.showerror(...)`. – PeterK Jun 13 '21 at 13:10

5 Answers5

39

There is report_callback_exception to do this:

import traceback
import tkMessageBox

# You would normally put that on the App class
def show_error(self, *args):
    err = traceback.format_exception(*args)
    tkMessageBox.showerror('Exception',err)
# but this works too
tk.Tk.report_callback_exception = show_error

If you didn't import Tkinter as tk, then do

Tkinter.Tk.report_callback_exception = show_error
martineau
  • 119,623
  • 25
  • 170
  • 301
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • 1
    Excellent, thank you! Would you mind expanding on your comment about putting that on the App class? – Andrew Jan 22 '11 at 23:06
  • 1
    @Andrew: I just meant that usually you would write your application in a subclass that overwrites this method, instead of changing the Tk class itself. Just in case you want different report functions in different frames. – Jochen Ritzel Jan 22 '11 at 23:13
  • Related: [Handling exception in python tkinter](https://stackoverflow.com/a/35073005/3357935) – Stevoisiak Mar 01 '18 at 19:01
  • If you have an App class for `Tk`, you can override the error handling function with `def report_callback_exception(self, exc, val, tb): tkMessageBox.showerror("Exception", message=str(val))` – Stevoisiak Mar 01 '18 at 20:40
  • 1
    Also works in python3, but you need to use `messagebox` from `tkinter` module instead of `tkMessageBox`. – Stanislav Ivanov Oct 11 '18 at 09:00
3

First a followup: Just today, a patch on the CPython tracker for the tkinter.Tk.report_callback_exception docstring made it clear that Jochen's solution is intended. The patch also (and primarily) stopped tk from crashing on callback exceptions when run under pythonw on Windows.

Second: here is a bare-bones beginning of a solution to making stderr function with no console (this should really be a separate SO question).

import sys, tkinter

root = tkinter.Tk()

class Stderr(tkinter.Toplevel):
    def __init__(self):
        self.txt = tkinter.Text(root)
        self.txt.pack()
    def write(self, s):
        self.txt.insert('insert', s)

sys.stderr = Stderr()

1/0 # traceback appears in window

More is needed to keep the popup window hidden until needed and then make it visible.

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
Terry Jan Reedy
  • 18,414
  • 3
  • 40
  • 52
1

Just too add more value in @Jochen Ritzel's answer, You try except from beginning of your program to catch the python error too.

for example to detect index out of range error of for loop, you can use:

# Note import files before the try statement

from tkinter import *
from tkinter import messagebox 
import sys,traceback
def show_error(slef, *args):
    err = traceback.format_exception(*args)
    messagebox.showerror('Exception',err)
try:
    root=Tk()
    Tk.report_callback_exception = show_error
    a=[1,2,3]
    for i in range(10):
        print(a[i])
    exec(input()) # Just used to throw error
    a=Button(text='s',command=lambda: print(8/0)) # Just used to throw error
    a.pack()
    root.mainloop()
except BaseException as e:
    messagebox.showerror('Exception',e)

Faraaz Kurawle
  • 1,085
  • 6
  • 24
1

Here's my 2 cents — cut and paste code that will display a dialog whether or not it occurred when tkinter was executing a callback function.

import sys
import tkinter as tk
from tkinter.messagebox import showerror
import traceback


def excepthook(exctype, excvalue, tb):
    """Display exception in a dialog box."""
    msg = ('An uncaught exception has occurred!\n\n'
           + '\n'.join(traceback.format_exception(exctype, excvalue, tb))
           + '\nTerminating.')
    showerror('Error!', msg)
    sys.exit()

sys.excepthook = excepthook  # Replace default system exception hook.


class MyTk(tk.Tk):
    """Tk subclass with callback exception reporting method that displays the
    exception is a dialog box.
    """
    def report_callback_exception(self, exctype, excvalue, tb):
        excepthook(exctype, excvalue, tb)


def raise_exception():
    """Callback function."""
    x = 1 / 0

def main():
    root = MyTk()
#    y = 1 / 0  # Uncomment to test exception handling in non-callback functions.
    tk.Button(root, text='Do something', command=raise_exception).pack()
    root.mainloop()


if __name__ == '__main__':
    main()

martineau
  • 119,623
  • 25
  • 170
  • 301
0

I found this thread while looking to solve the same problem. I had to make some modifications based on current tkinter version methodologies. I also added a customized error message handler since I figured my user wouldn't know what the Tk error message meant.

In the __init__ of your class include:

parent.report_callback_exception = self.report_callback_exception

then in your Tk class:

    def report_callback_exception(self, exc, val, tb):
        #Handles tkinter exceptions
        err_msg = {'Index 0 out of range': 'None found'}
        try:
            err = err_msg[str(val)]
        except KeyError:
            err = 'Oops, try again'
        messagebox.showerror('Error Found', err)

You could expand the err_msg dictionary to include whatever else you wanted. In my case I am building a searchable database off an Entry object, so if the user has typed in a string that doesn't exist in the database, it is giving the user a plain language error.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Tom
  • 1,003
  • 2
  • 13
  • 25