11

I have a Python script which uses tkinter.messagebox to display an error message with traceback details if an unexpected exception occurs.

import tkinter.messagebox as tm
import traceback

try:
    1/0
except Exception as error:
    tm.showerror(title="Error",
                 message="An error has occurred: '" + str(error) + "'.",
                 detail=traceback.format_exc())

Standard tkinter error

Displaying tracebacks this way has a few drawbacks.

Instead of displaying error details by default, I would like to add a "show details" button which would display more information in a read-only text field.

Detailed error for "division by zero"

How can I add a "show details" button to a tkinter messagebox?

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
  • 3
    You can create your own `messagebox`-like dialog window. Here's a little [documentation](http://effbot.org/tkinterbook/tkinter-dialog-windows.htm). Note particularly the `tkSimpleDialog.py` support class. – martineau Mar 02 '18 at 16:52
  • I would use `Toplevel()` to create my own. – Mike - SMT Jun 01 '18 at 19:39

1 Answers1

12

I would use a Toplevel() window to build my own customer error box.

I think using ttk buttons here would be a good idea and with a combination of frames and weights we can get the window to look decent enough.

Keeping the window from being resized by the user I also had to set up a way to toggle the details textbox. With a tracking variable and the use of a if/else statement that was easy enough to set up.

Finally, we can disable the textbox with .config(state="disabled")

import tkinter as tk
import tkinter.ttk as ttk
import traceback


class MyApp(tk.Tk):
    def __init__(self):
        super().__init__()
        tk.Button(self, text='test error', command=self.run_bad_math).pack()

    @staticmethod
    def run_bad_math():
        try:
            1/0
        except Exception as error:
            title = 'Traceback Error'
            message = "An error has occurred: '{}'.".format(error)
            detail = traceback.format_exc()
            TopErrorWindow(title, message, detail)


class TopErrorWindow(tk.Toplevel):
    def __init__(self, title, message, detail):
        tk.Toplevel.__init__(self)
        self.details_expanded = False
        self.title(title)
        self.geometry('350x75')
        self.minsize(350, 75)
        self.maxsize(425, 250)
        self.rowconfigure(0, weight=0)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)

        button_frame = tk.Frame(self)
        button_frame.grid(row=0, column=0, sticky='nsew')
        button_frame.columnconfigure(0, weight=1)
        button_frame.columnconfigure(1, weight=1)

        text_frame = tk.Frame(self)
        text_frame.grid(row=1, column=0, padx=(7, 7), pady=(7, 7), sticky='nsew')
        text_frame.rowconfigure(0, weight=1)
        text_frame.columnconfigure(0, weight=1)

        ttk.Label(button_frame, text=message).grid(row=0, column=0, columnspan=2, pady=(7, 7))
        ttk.Button(button_frame, text='OK', command=self.destroy).grid(row=1, column=0, sticky='e')
        ttk.Button(button_frame, text='Details', command=self.toggle_details).grid(row=1, column=1, sticky='w')

        self.textbox = tk.Text(text_frame, height=6)
        self.textbox.insert('1.0', detail)
        self.textbox.config(state='disabled')
        self.scrollb = tk.Scrollbar(text_frame, command=self.textbox.yview)
        self.textbox.config(yscrollcommand=self.scrollb.set)

    def toggle_details(self):
        if self.details_expanded:
            self.textbox.grid_forget()
            self.scrollb.grid_forget()
            self.geometry('350x75')
            self.details_expanded = False
        else:
            self.textbox.grid(row=0, column=0, sticky='nsew')
            self.scrollb.grid(row=0, column=1, sticky='nsew')
            self.geometry('350x160')
            self.details_expanded = True


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

Results:

enter image description here

enter image description here

Now with resizing :D

enter image description here

Update:

In response to your statement below:

The error window will not display if a Tk instance hasn't been initialized first.

If we set up the class as its own Tk() instance it can be used as a stand alone error pop-up. I have also added some alignment changes and some resizing control to make this class a bit more conformative to the standard error messages you mention in the comments.

See below code.

import tkinter as tk
import tkinter.ttk as ttk


class TopErrorWindow(tk.Tk):
    def __init__(self, title, message, detail):
        super().__init__()
        self.details_expanded = False
        self.title(title)
        self.geometry('350x75')
        self.minsize(350, 75)
        self.maxsize(425, 250)
        self.resizable(False, False)
        self.rowconfigure(0, weight=0)
        self.rowconfigure(1, weight=1)
        self.columnconfigure(0, weight=1)

        button_frame = tk.Frame(self)
        button_frame.grid(row=0, column=0, sticky='nsew')
        button_frame.columnconfigure(0, weight=1)
        button_frame.columnconfigure(1, weight=1)

        text_frame = tk.Frame(self)
        text_frame.grid(row=1, column=0, padx=(7, 7), pady=(7, 7), sticky='nsew')
        text_frame.rowconfigure(0, weight=1)
        text_frame.columnconfigure(0, weight=1)

        ttk.Label(button_frame, text=message).grid(row=0, column=0, columnspan=3, pady=(7, 7), padx=(7, 7), sticky='w')
        ttk.Button(button_frame, text='OK', command=self.destroy).grid(row=1, column=1, sticky='e')
        ttk.Button(button_frame, text='Details',
                   command=self.toggle_details).grid(row=1, column=2, padx=(7, 7), sticky='e')

        self.textbox = tk.Text(text_frame, height=6)
        self.textbox.insert('1.0', detail)
        self.textbox.config(state='disabled')
        self.scrollb = tk.Scrollbar(text_frame, command=self.textbox.yview)
        self.textbox.config(yscrollcommand=self.scrollb.set)
        self.mainloop()

    def toggle_details(self):
        if self.details_expanded:
            self.textbox.grid_forget()
            self.scrollb.grid_forget()
            self.resizable(False, False)
            self.geometry('350x75')
            self.details_expanded = False
        else:
            self.textbox.grid(row=0, column=0, sticky='nsew')
            self.scrollb.grid(row=0, column=1, sticky='nsew')
            self.resizable(True, True)
            self.geometry('350x160')
            self.details_expanded = True

Results:

enter image description here

enter image description here

You can add an image as well using canvas with the type of error image you want.

Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/172430/discussion-between-steven-vascellaro-and-mike-smt). – Stevoisiak Jun 04 '18 at 14:55
  • Your answer worked well. I am planning on awarding you the bounty during the grace period. – Stevoisiak Jun 07 '18 at 15:25
  • @StevenVascellaro Ah. I was not aware of the grace period. Guess it helps to read up on the entire bounty page :D – Mike - SMT Jun 07 '18 at 15:29
  • A few things of note: 1) The error window will not display if a `Tk` instance hasn't been initialized first. 2) Message prompts typically align text & buttons to the left or right side, rather than centering it. 3) "Traceback Error" is misspelled as "Teackback Error". – Stevoisiak Jun 07 '18 at 15:34
  • On most operating systems, message prompts traditionally left-align messages and right-align buttons. ([Windows](https://msdn.microsoft.com/en-us/library/windows/desktop/dn742471(v=vs.85).aspx), [Mac](http://cdn.osxdaily.com/wp-content/uploads/2016/12/application-is-not-open-anymore.jpg), [Linux](https://i.stack.imgur.com/N1iKf.png)) – Stevoisiak Jun 07 '18 at 15:48
  • @StevenVascellaro I have added a standalone example that should address all your points in your comment above. Let me know if I am missing anything. – Mike - SMT Jun 07 '18 at 15:59