2012-11-07 11 views
10

Quando si utilizza il seguente codice, la mia applicazione si blocca dopo un paio di secondi. E per bancarelle intendo pende. Ottengo una finestra da Windows dicendo wait o force close.Python QT ProgressBar

Potrei aggiungere che ciò accade solo quando clicco all'interno della finestra della barra di avanzamento o quando clicco al di fuori di esso in modo che perda lo stato attivo. Se avvio l'esempio e non tocco nulla, funziona come dovrebbe.

from PyQt4 import QtCore 
from PyQt4 import QtGui 


class ProgressBar(QtGui.QWidget): 
    def __init__(self, parent=None, total=20): 
     super(ProgressBar, self).__init__(parent) 
     self.name_line = QtGui.QLineEdit() 

     self.progressbar = QtGui.QProgressBar() 
     self.progressbar.setMinimum(1) 
     self.progressbar.setMaximum(total) 

     main_layout = QtGui.QGridLayout() 
     main_layout.addWidget(self.progressbar, 0, 0) 

     self.setLayout(main_layout) 
     self.setWindowTitle("Progress") 

    def update_progressbar(self, val): 
     self.progressbar.setValue(val) 

Usando questo modo:

app = QtGui.QApplication(sys.argv) 
bar = ProgressBar(total=101) 
bar.show() 

for i in range(2,100): 
    bar.update_progressbar(i) 
    time.sleep(1) 

Grazie per qualsiasi aiuto.

risposta

12

È necessario consentire che gli eventi vengano elaborati mentre il ciclo è in esecuzione in modo che l'applicazione possa rimanere reattiva.

Ancora più importante, per le attività a esecuzione prolungata, è necessario fornire un modo per l'utente di interrompere il ciclo una volta avviato.

Un modo semplice per eseguire questa operazione è avviare il ciclo con un timer, quindi chiamare periodicamente lo qApp.processEvents mentre il ciclo è in esecuzione.

Ecco uno script demo che fa questo:

import sys, time 
from PyQt4 import QtGui, QtCore 

class ProgressBar(QtGui.QWidget): 
    def __init__(self, parent=None, total=20): 
     super(ProgressBar, self).__init__(parent) 
     self.progressbar = QtGui.QProgressBar() 
     self.progressbar.setMinimum(1) 
     self.progressbar.setMaximum(total) 
     self.button = QtGui.QPushButton('Start') 
     self.button.clicked.connect(self.handleButton) 
     main_layout = QtGui.QGridLayout() 
     main_layout.addWidget(self.button, 0, 0) 
     main_layout.addWidget(self.progressbar, 0, 1) 
     self.setLayout(main_layout) 
     self.setWindowTitle('Progress') 
     self._active = False 

    def handleButton(self): 
     if not self._active: 
      self._active = True 
      self.button.setText('Stop') 
      if self.progressbar.value() == self.progressbar.maximum(): 
       self.progressbar.reset() 
      QtCore.QTimer.singleShot(0, self.startLoop) 
     else: 
      self._active = False 

    def closeEvent(self, event): 
     self._active = False 

    def startLoop(self): 
     while True: 
      time.sleep(0.05) 
      value = self.progressbar.value() + 1 
      self.progressbar.setValue(value) 
      QtGui.qApp.processEvents() 
      if (not self._active or 
       value >= self.progressbar.maximum()): 
       break 
     self.button.setText('Start') 
     self._active = False 

app = QtGui.QApplication(sys.argv) 
bar = ProgressBar(total=101) 
bar.show() 
sys.exit(app.exec_()) 

UPDATE

Supponendo che si sta utilizzando l'implementazione in C di Python (cioè CPython), la soluzione a questo problema dipende tutto sulla natura delle attività che devono essere eseguite contemporaneamente alla GUI. Più fondamentalmente, è determinato da CPython che ha un Global Interpreter Lock (GIL).

Non ho intenzione di tentare alcuna spiegazione del GIL di CPython: invece, mi limiterò a raccomandare di guardare questo eccellente PyCon video di Dave Beazley, e lasciarlo a quello.


In genere, quando si cerca di eseguire una GUI in concomitanza con attività in background, la prima domanda da porsi è: è il compito IO-bound, o CPU-bound?

Se è legato all'IO (ad esempio, l'accesso al file system locale, il download da Internet, ecc.), La soluzione è in genere piuttosto semplice, poiché CPython rilascia sempre il GIL per le operazioni di I/O. L'attività in background può essere semplicemente eseguita asynchronously o eseguita da worker thread e non è necessario eseguire alcuna operazione speciale per mantenere la GUI reattiva.

Le principali difficoltà si verificano con i task associati alla CPU, quando c'è una seconda domanda da porsi: l'attività può essere suddivisa in una serie di piccoli passaggi?

Se possibile, la soluzione è inviare periodicamente richieste al thread della GUI per elaborare lo stack corrente di eventi in sospeso. Lo script demo qui sopra è un esempio grezzo di questa tecnica. Più di solito, l'attività verrebbe eseguita in un thread di lavoro separato, che emetterebbe un segnale di aggiornamento gui al completamento di ogni fase dell'attività. (NB: è importante assicurarsi che il thread di lavoro non tenti mai alcuna operazione relativa alla GUI).

Ma se l'attività non può essere suddivisa in piccoli passaggi, quindi nessuna delle solite soluzioni di tipo threading funzionerà. La GUI si bloccherà solo fino al completamento dell'attività, indipendentemente dal fatto che i thread vengano utilizzati o meno.

Per questo ultimo scenario, l'unica soluzione è quella di utilizzare un separato processo, piuttosto che un thread separato - cioè fare uso del modulo multiprocessing. Naturalmente, questa soluzione sarà efficace solo se il sistema di destinazione ha più core CPU disponibili. Se c'è un solo core della CPU con cui giocare, non c'è praticamente nulla che possa essere fatto per aiutare (oltre a passare a una diversa implementazione di Python, o ad una lingua completamente diversa).

+0

Se eseguo questo, la finestra della GUI si blocca anche con l'errore "Non risponde, forza chiudi". Tuttavia, se aspetto che tutti i miei compiti siano terminati, la normale applicazione continua. – Tuim

+1

@Tuim. Il mio script è solo una semplice demo basata sul codice della tua domanda. Non è una soluzione universale che funzionerà in tutte le situazioni. Devi aggiornare la tua domanda con una spiegazione adeguata di ciò che stai cercando di fare. Quali sono questi "compiti" che menzioni? Sono vincolati alla CPU o legati all'IO? L'applicazione che svolge i compiti che hai scritto tu stesso può quindi essere modificata? In che lingua è scritto? Ecc. Ecc. – ekhumoro

+0

Queste attività sono installazioni, ad esempio decompressione di file zip, installazione di pacchetti msi/deb in cose del genere. Ma questo non è molto rilevante per il caso. L'applicazione è scritta in Python e completamente personalizzabile. Anche io non mi aspetto una risposta in grado di copiare e incollare! Mi aspetto un suggerimento nella giusta direzione e quello che hai non sembra essere la giusta direzione per me, ci ho provato. Senza offesa. – Tuim

0

quando non programmazione GUI è necessario utilizzare una qualche forma di multi-threading, si avrebbe un thread di lavoro e uno che aggiorna l'interfaccia grafica e risponde agli eventi del mouse e della tastiera. Ci sono diversi modi per farlo. Vi consiglio di dare un'occhiata a questo QProgressBar tutorial che ha un eccellente esempio di come usare QProgressBar.

Nel tutorial che stanno utilizzando QBasicTimer che è modo di cedere il controllo al filo principale in modo da poter rispondere agli eventi dell'interfaccia grafica. Utilizzando time.sleep nel codice si blocca per un secondo l'unico thread in esecuzione.

+0

Ne sono consapevole. Ma non stanno facendo più uso del threading come sono nell'esempio. – Tuim

+1

@Tuim: Sì, lo sono, non utilizzare 'time.sleep()'. – Junuxx

+0

@Tuim Ho aggiornato la risposta per spiegare ulteriormente perché il tuo codice e le esercitazioni non stanno facendo la stessa cosa. –