2015-06-20 10 views
6

Questo accade solo su Linux (anche possibile sistema operativo X, non può testare atm), funziona bene su Windows.wx.ProgressDialog che causa l'errore di seg e/o l'errore GTK_IS_WINDOW quando viene distrutto

Ho un wx.ProgressDialog generato con il thread principale. Invio il lavoro a un altro thread e periodicamente richiama una funzione di callback nel thread principale che aggiornerà il ProgressDialog o, alla fine del lavoro, lo distruggerà. Tuttavia, ricevo un messaggio di interessante su Linux quando questo accade:

(python:12728): Gtk-CRITICAL **: IA__gtk_window_set_modal: assertion 'GTK_IS_WINDOW (window)' failed

La finestra di dialogo non si chiude, ma se provo a deporre le uova di nuovo sembra che è già quasi finito. A volte un errore seg seguirà anche questo messaggio.

Ho cercato di simulare con una versione ridotta qui:.

import wxversion 
wxversion.select("2.8") 
import wx 
import sys 
import threading 

MAX_COUNT = 100 

## This class is in a different area of the codebase and 
class WorkerThread(threading.Thread): 
    def __init__(self, callback): 
     threading.Thread.__init__(self) 
     self.callback = callback 

    def run(self): 
     # simulate work done. IRL, this calls another function in another 
     # area of the codebase. This function would generate an XML document, 
     # which loops through a list of items and creates a set of elements for 
     # each item, calling back after each item. Here, we simply set up a for 
     # loop and simulate work with wx.MilliSleep 
     for i in xrange(MAX_COUNT): 
      print i 
      wx.MilliSleep(30) 
      wx.CallAfter(self.callback, i) 

     # Send done signal to GUI 
     wx.CallAfter(self.callback, -1) 

class Frame(wx.Frame): 
    def __init__(self, title): 
     wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(350,200)) 

     panel = wx.Panel(self) 
     box = wx.BoxSizer(wx.VERTICAL) 

     m_btn = wx.Button(panel, wx.ID_ANY, "Run Stuff") 
     m_btn.Bind(wx.EVT_BUTTON, self.OnRunButton) 
     box.Add(m_btn, 0, wx.ALL, 10) 

     panel.SetSizer(box) 
     panel.Layout() 

    def OnRunButton(self, event): 
     self.progressDialog = wx.ProgressDialog("Doing work", 
          "Doing Work", 
          maximum=MAX_COUNT, parent=self, 
          style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME) 
     self.worker(self.threadCallback) 
     self.progressDialog.ShowModal() 

    def worker(self, callback): 
     # This bit is in another part of the codebase originally. In the test, 
     # I could have added it to OnRunButton, but I wanted function calls to 
     # be similar between test and actual code 
     thread = WorkerThread(callback) 
     thread.start() 

    def threadCallback(self, info): 
     # We update based on position, or destroy if we get a -1 
     if info == -1: 
      self.progressDialog.Destroy() 
     else: 
      self.progressDialog.Update(info) 

app = wx.App(redirect=False) 
top = Frame("ProgressDialog Test") 
top.Show() 
app.MainLoop() 

(selezioniamo 2.8, ma idealmente qualsiasi correzione deve lavorare sia in 2.8 e 3.0 io in realtà non sono stati in grado di testarlo in 3.0 a linux a causa di una versione 3.0 non corretta)

Questo è un buon lavoro nel rappresentare il problema: funziona bene in Windows, ma sega errore quando tenta di distruggere la finestra di dialogo di avanzamento. Tuttavia, non posso ottenere l'esempio per mostrare GTK_IS_WINDOW

Ive ha provato a cercare soluzioni. Ho letto che potrebbe essere dovuto al fatto che il thread di lavoro termina troppo velocemente, e quindi lascia la GUI con alcuni messaggi nella sua coda. Non sono sicuro di averlo capito completamente (non ho mai capito il rendimento di Rendimenti e messaggi, ecc.), Ma quello che credo significhi è che quando il lavoratore è al 100%, il ProgressDialog (essendo più lento), potrebbe essere solo 75% e ha ancora il 25% in più di messaggi da utilizzare per "aggiornare" la GUI, ma viene distrutto.

Vorrei qualche chiarimento se sto capendo che correttamente o no.

Inoltre, credo che .Hide() funzioni come un aggiramento, ma mi piacerebbe distruggerlo invece perché è la cosa giusta da fare.

Indipendentemente da ciò, qualsiasi aiuto sarebbe molto apprezzato. =)

risposta

1

Ho provato il codice, anche molte modifiche sono state tentate per risolvere questo problema, ma non ci sono riuscito. Ad ogni modo, ho creato il seguente script wxPython per raggiungere il vostro scopo, vedere sotto:

import wxversion 
wxversion.select("2.8") # version 3.0 works, too. 
import wx 
import sys 
import threading 
import time 

MAX_COUNT = 200 

class WorkerThread(threading.Thread): 
    def __init__(self, target, countNum): 
     threading.Thread.__init__(self, target = target) 
     self.setDaemon(True) 
     self.cnt = countNum 
     self.target = target 
     self.pb = self.target.pb 

    def run(self): 
     for i in xrange(self.cnt): 
      print i+1 
      wx.MilliSleep(50) 
      wx.CallAfter(self.pb.SetValue, i+1) 

     wx.CallAfter(self.target.MakeModal, False) 
     wx.CallAfter(self.target.Close) 

class ProgressBarFrame(wx.Frame): 
    def __init__(self, parent, title, range = 100) : 
     wx.Frame.__init__(self, parent = parent, title = title) 
     self.range = range 
     self.createProgressbar() 
     self.SetMinSize((400, 10)) 
     self.Centre() 
     self.Show() 
     self.t0 = time.time() 
     self.elapsed_time_timer.Start(1000) 

    def createProgressbar(self): 
     self.pb  = wx.Gauge(self) 
     self.pb.SetRange(range = self.range) 

     self.elapsed_time_st = wx.StaticText(self, label = 'Elapsed Time:') 
     self.elapsed_time_val = wx.StaticText(self, label = '00:00:00') 

     vbox_main = wx.BoxSizer(wx.VERTICAL) 
     hbox_time = wx.BoxSizer(wx.HORIZONTAL) 
     hbox_time.Add(self.elapsed_time_st, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.ALL, 5) 
     hbox_time.Add(self.elapsed_time_val, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.ALL, 5) 
     vbox_main.Add(self.pb, 0, wx.EXPAND | wx.ALL, 5) 
     vbox_main.Add(hbox_time, 0, wx.EXPAND | wx.ALL, 5) 

     self.SetSizerAndFit(vbox_main) 

     self.elapsed_time_timer = wx.Timer(self) 
     self.Bind(wx.EVT_TIMER, self.onTickTimer, self.elapsed_time_timer) 

    def onTickTimer(self, event): 
     fmt='%H:%M:%S' 
     self.elapsed_time_val.SetLabel(time.strftime(fmt, time.gmtime(time.time()-self.t0))) 

class Frame(wx.Frame): 
    def __init__(self, title): 
     wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(350,200)) 

     panel = wx.Panel(self) 
     box = wx.BoxSizer(wx.VERTICAL) 

     m_btn = wx.Button(panel, wx.ID_ANY, "Run Stuff") 
     self.Bind(wx.EVT_BUTTON, self.OnRunButton, m_btn) 
     box.Add(m_btn, 0, wx.ALL, 10) 

     panel.SetSizer(box) 

    def OnRunButton(self, event): 
     self.progressbar = ProgressBarFrame(self, 'Working Processing', MAX_COUNT) 
     self.progressbar.MakeModal(True) 
     worker = WorkerThread(self.progressbar, MAX_COUNT) 
     worker.start() 

app = wx.App(redirect=False) 
top = Frame("ProgressDialog Test") 
top.Show() 
app.MainLoop() 

sto usando wx.Gauge di fare ciò wx.ProgressDialog fa, così come un ulteriore wx.Timer per mostrare il tempo trascorso. Il metodo MakeModal() viene utilizzato per simulare l'effetto ShowModal, che è lo stile predefinito mostrato da Dialog, non dimenticare di rilasciare lo stato Modale per MakeModal(False) o il frame sarebbe stato bloccato. Puoi aggiungere più cose nella classe ProgressBarFrame.

Sto pensando che l'errore di errore di segmento potrebbe derivare dagli eventi che chiamano, in particolare quando è coinvolto il problema multithreading, forse con attenzione la classe wx.ProgressDialog mostrerebbe qualche indizio.

Screenshot of progressbar demo