tkinter python: catching exceptions

2019-05-09 06:31发布

问题:

Starting to program in python I felt at home with its error reporting. Now that I'm programming with Tkinter instead, I'm finding that it often happens that there are errors in my program that I do not notice even if they generate an exception: I catch them (sometimes) just because I go debugging Step by Step (I use wingIDE) and e.g. at a given line I see the exception reported. But what annoys me is that the program doesn't stop, yet this happens even in blocks not inside try/error.

If what I said makes any sense, do you know about some overall approach to at least display the errors? Being in Tkinter I could create an error window, and fill it with any exception is being generated, when it happens.

回答1:

See the answers to How can I make silent exceptions louder in tkinter which show how to hook a callback into tkinter.Tk.report_callback_exception.



回答2:

As @jochen-ritzel said (Should I make silent exceptions louder in tkinter?), there is tk.TK.report_callback_exception() that you can override:

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


回答3:

I prefer explicit extending Toplevel widget of Tk which represents mostly the main window of an application instead of injecting a hack:

import tkinter as tk
from tkinter import messagebox

class FaultTolerantTk(tk.Tk):
    def report_callback_exception(self, exc, val, tb):
        self.destroy_unmapped_children(self)
        messagebox.showerror('Error!', val)

    # NOTE: It's an optional method. Add one if you have multiple windows to open
    def destroy_unmapped_children(self, parent):
        """
        Destroys unmapped windows (empty gray ones which got an error during initialization)
        recursively from bottom (root window) to top (last opened window).
        """
        children = parent.children.copy()
        for index, child in children.items():
            if not child.winfo_ismapped():
                parent.children.pop(index).destroy()
            else:
                self.destroy_unmapped_children(child)

def main():
    root = FaultTolerantTk()
    ...
    root.mainloop()


if __name__ == '__main__':
    main()

IMHO it looks like a right way.