Обработка необработанного исключения в графическом интерфейсе

В основном я пишу небольшие инструменты для тех, кто подкованных людей, например. программисты, инженеры и т.д. Поскольку эти инструменты, как правило, быстро взламываются со временем, я знаю, что будут необработанные исключения, и пользователям это не понравится. Я хотел бы, чтобы пользователь мог отправить мне трассировку, чтобы я мог проверить, что произошло и возможно улучшить приложение.

Я обычно программирую wxPython, но недавно я сделал некоторую Java. Я подключил класс TaskDialog к Thread.UncaughtExceptionHandler(), и я вполне доволен результатом. Особенно, что он может захватывать и обрабатывать исключения из любого потока:

enter image description here

enter image description here

Я делал что-то подобное в wxPython в течение длительного времени. Однако:

  • Мне пришлось написать украшатель-хак, чтобы хорошо печатать исключения из другого потока.
  • Даже при функциональности результат довольно уродлив.

enter image description here

Вот код для Java и wxPython, чтобы вы могли видеть, что я сделал:

Java:

import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.JButton;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import com.ezware.dialog.task.TaskDialogs;

public class SwingExceptionTest {

    private JFrame frame;

    public static void main(String[] args) {

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (ClassNotFoundException e) {
        }
        catch (InstantiationException e) {
        }
        catch (IllegalAccessException e) {
        }
        catch (UnsupportedLookAndFeelException e) {
        }

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            public void uncaughtException(Thread t, Throwable e) {
                TaskDialogs.showException(e);
            }
        });

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    SwingExceptionTest window = new SwingExceptionTest();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public SwingExceptionTest() {
        initialize();
    }

    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 600, 400);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GridBagLayout gridBagLayout = new GridBagLayout();
        gridBagLayout.columnWidths = new int[]{0, 0};
        gridBagLayout.rowHeights = new int[]{0, 0};
        gridBagLayout.columnWeights = new double[]{0.0, Double.MIN_VALUE};
        gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
        frame.getContentPane().setLayout(gridBagLayout);

        JButton btnNewButton = new JButton("Throw!");
        btnNewButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                onButton();
            }
        });
        GridBagConstraints gbc_btnNewButton = new GridBagConstraints();
        gbc_btnNewButton.gridx = 0;
        gbc_btnNewButton.gridy = 0;
        frame.getContentPane().add(btnNewButton, gbc_btnNewButton);
    }

    protected void onButton(){
        Thread worker = new Thread() {
            public void run() { 
                throw new RuntimeException("Exception!");
            }
        };
        worker.start();
    }

}

WxPython:

import StringIO
import sys
import traceback
import wx
from wx.lib.delayedresult import startWorker


def thread_guard(f):
    def thread_guard_wrapper(*args, **kwargs) :
        try:
            r = f(*args, **kwargs)
            return r
        except Exception:
            exc = sys.exc_info()
            output = StringIO.StringIO()
            traceback.print_exception(exc[0], exc[1], exc[2], file=output)
            raise Exception("<THREAD GUARD>\n\n" + output.getvalue())
    return thread_guard_wrapper

@thread_guard
def thread_func():
    return 1 / 0

def thread_done(result):
    r = result.get()
    print r


class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.panel = wx.Panel(self)
        self.button = wx.Button(self.panel, label="Throw!")
        self.button.Bind(wx.EVT_BUTTON, self.OnButton)

        self.sizer = wx.BoxSizer()
        self.sizer.Add(self.button)

        self.panel.SetSizerAndFit(self.sizer)  
        self.Show()

    def OnButton(self, e):
        startWorker(thread_done, thread_func)

app = wx.App(True)
win = MainWindow(None, size=(600, 400))
app.MainLoop()

Теперь вопрос:

Можно ли легко сделать что-то похожее на решение Java в wxPython? Или, может быть, есть ли лучший способ в Java или wxPython?

Ответ 1

В Python вы можете установить sys.execpthook на функцию, которую вы хотите вызвать для неперехваченных исключений. Тогда вам не понадобятся декораторы, вы можете просто справиться с исключениями централизованно в своей функции hook.

Вместо того, чтобы печатать текст трассировки исключений и позволять отображать его в окне stdout, вы можете сделать с ним что-то более интеллектуальное, например, используя диалог для отображения текста и элементы управления, которые позволяют пользователю отправлять сообщение об ошибке информация обратно разработчику, игнорировать будущие ошибки, перезапустить приложение или все, что вы хотите.

Ответ 2

В Java, если TaskDialog недоступен, вы можете использовать JOptionPane, как показано . Из потока, отличного от потока отправки событий, заверните вызов, используя EventQueue.invokeLater(), как предложено здесь. Также рассмотрите возможность добавления необязательного условия для вызова Desktop#mail().

image