Search code examples
pythontkintertkmessagebox

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


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?


Solution

  • 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.