2013-04-04 20 views
10

Przeważnie piszę małe narzędzia dla doświadczonych technologicznie ludzi, np. programiści, inżynierowie itp. Ponieważ te narzędzia są zazwyczaj szybkimi ulepszeniami z czasem, wiem, że będą nieobsługiwane wyjątki, a użytkownicy nie będą mieli nic przeciwko. Chciałbym, aby użytkownik mógł przesłać mi informacje zwrotne, abym mógł sprawdzić, co się stało i ewentualnie poprawić aplikację.Obsługa nieobsługiwanego wyjątku w GUI

Zwykle programuję wxPython, ale ostatnio zrobiłem trochę Javy. Podłączyłem klasę TaskDialog do Thread.UncaughtExceptionHandler() i jestem całkiem zadowolony z wyniku. Zwłaszcza, że ​​można go złapać i obsługi wyjątków z dowolnego wątku:

enter image description here

enter image description here

robiłem coś podobnego w wxPython przez długi czas. Jednak:

  1. Musiałem napisać dekorator-hak, aby móc drukować wyjątki z innego wątku.
  2. Nawet gdy funkcjonalny, wynik jest dość brzydki.

enter image description here

Oto kod dla obu Java i wxPython, dzięki czemu można zobaczyć, co mam zrobić:

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() 

Teraz pytanie:

Czy mogę łatwo zrobić coś podobnego do rozwiązania Java w wxPythonie? A może jest lepszy sposób na Java lub wxPython?

+0

Dziękuję Fenisko. :) –

+0

Nie ma za co. Nie dlatego, że jestem pewien, dlaczego podziękowania :-). – Fenikso

+2

Tylko nieliczni ludzie myślą o problemach programistów :) –

Odpowiedz

3

W języku Python można ustawić sys.execpthook dla funkcji, która ma być wywołana dla niezakłóconych wyjątków. Wtedy nie będziesz potrzebował dekoratorów, możesz zamiast tego zajmować się wyjątkami centralnie w funkcji przechwytywania.

Zamiast zwykłego drukowania tekstu wyjątku traceback i umożliwienia jego wyświetlania w oknie standardowym, można zrobić coś bardziej inteligentnego, np. Za pomocą okna dialogowego, aby wyświetlić tekst i mieć elementy sterujące, które umożliwiają użytkownikowi wysłanie informacje o błędach z powrotem do programisty, zignorować przyszłe błędy, ponownie uruchomić aplikację lub cokolwiek chcesz.

+0

Używanie 'sys.execpthook' jest świetne. Dzięki! Jednak nadal potrzebuję dekoratora '@ thread_guard' dla' wx.lib.wątek wątku delayedresult. Wyjątek jest wyrzucany z linii 'r = result.get()' zamiast 'return 1/0' i jeśli nie użyję dekoratora do zapisania informacji z oryginalnego wyjątku, stracę istotne dane debugowania. – Fenikso

3

W języku Java, jeśli TaskDialog jest niedostępny, można użyć JOptionPane, jak pokazano here. Z wątku innego niż event dispatch thread zawiń wywołanie za pomocą EventQueue.invokeLater(), zgodnie z sugestią here. Rozważ także dodanie opcjonalnego przepisu do wywołania Desktop#mail().

image

+0

Jestem nowy w Javie, więc prawdopodobnie nie podążam. 1) Rozumiem, że mogę używać 'JOptionPane' zamiast' TaskDialog'. Ale w jakich okolicznościach 'TaskDialog' może stać się" niedostępne "? Mam go zainstalowany na moim systemie i planuję utworzyć plik wykonywalny JAR. – Fenikso

+0

2) Rozumiem, że jeśli chciałbym zrobić cokolwiek dla GUI w innym wątku, powinienem użyć 'EventQueue.invokeLater()'. Ale do obsługi nieobsługiwanych wyjątków za pomocą Thread.setDefaultUncaughtExceptionHandler' wydaje się działać dobrze z dowolnego wątku. Czy czegoś brakuje? Czy powinienem zawijać 'TaskDialogs.showException (e);' do 'EventQueue.invokeLater()' nawet tam? – Fenikso

+0

1) Miałem na myśli _nie dostępny_ w tym sensie, że zdecydowałeś się go nie używać z jakiegoś powodu. 2) Wynikowy niejawny wyścig danych może pozostać [utajony] (http://stackoverflow.com/a/13411868/230513). EDT uruchomi się ponownie automatycznie, co może spowodować mylące ślady stosu bez kolejności dostarczanej przez 'invokeLater()'. – trashgod