2012-11-07 11 views
10

Przy użyciu następującego kodu aplikacja zawiesza się po kilku sekundach. I przez stragany mam na myśli zwisy. Dostaję okno z Windows mówiące, że czekam lub siłą zamykam.Python QT ProgressBar

dodam, że dzieje się tak dopiero po kliknięciu zarówno wewnątrz okna pasek postępu lub po kliknięciu poza nim, dlatego traci ostrość. Jeśli zacznę przykład i niczego nie dotknę, działa tak, jak powinien.

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) 

Używanie tego tak:

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

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

Dzięki za wszelką pomoc.

Odpowiedz

12

Musisz zezwolić zdarzenia mają być przetwarzane, podczas gdy pętla działa tak, że aplikacja może pozostać elastyczne.

Co ważniejsze, w przypadku długotrwałych zadań należy zapewnić użytkownikowi możliwość zatrzymania pętli po jej uruchomieniu.

Jednym z prostych sposobów jest uruchomienie pętli z timerem, a następnie okresowe wywoływanie qApp.processEvents podczas pracy pętli.

Oto skrypt demo że robi:

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

Zakładając, że używasz realizację C Pythona (tj CPython), rozwiązanie tego problemu zależy całkowicie od charakteru zadania (ów), które muszą działać jednocześnie z GUI. Bardziej fundamentalnie, jest to określane przez CPython mające Global Interpreter Lock (GIL).

Nie zamierzam próbować żadnego wyjaśnienia GIL dla CPython: zamiast tego, po prostu polecam oglądanie tego wspaniałego PyCon video przez Dave'a Beazley'a i zostawię to.


Generalnie, gdy próbuje uruchomić GUI jednocześnie z zadania w tle, pierwsze pytanie brzmi: Czy zadanie IO związana, lub CPU-bound?

Jeśli jest to związane z IO (np. Dostęp do lokalnego systemu plików, pobieranie z Internetu itp.), To rozwiązanie jest zwykle dość proste, ponieważ CPython zawsze zwalnia operacje GIL dla operacji we/wy. Zadanie tła można wykonać po prostu asynchronously lub wykonać przez worker thread i nic specjalnego nie musi być zrobione, aby zachować responsywność interfejsu GUI.

Główne problemy pojawiają się w zadaniach związanych z procesorem, gdy pojawia się drugie pytanie do zadawania: Czy zadanie można podzielić na kilka małych kroków?

Jeśli to możliwe, rozwiązaniem jest okresowe wysyłanie żądań do wątku GUI w celu przetworzenia bieżącego zestawu oczekujących zdarzeń. Powyższy skrypt pokazowy jest prostym przykładem tej techniki. Zazwyczaj zadanie będzie wykonywane w osobnym wątku roboczym, który będzie emitował sygnał aktualizacji GUI po zakończeniu każdego kroku zadania. (Uwaga: ważne jest, aby wątek roboczy nigdy nie próbował żadnych operacji związanych z GUI).

Ale jeśli zadanie nie może być podzielone na małe etapy na, żadne ze zwykłych rozwiązań typu threading nie zadziała. GUI będzie po prostu zamarzać, dopóki zadanie nie zostanie ukończone, niezależnie od tego, czy wątki są używane, czy nie.

W tym ostatecznym scenariuszu jedynym rozwiązaniem jest użycie oddzielnego procesu , zamiast oddzielnego wątku - to znaczy użycie modułu multiprocessing. Oczywiście to rozwiązanie będzie skuteczne tylko wtedy, gdy system docelowy ma wiele rdzeni procesora dostępnych. Jeśli istnieje tylko jeden rdzeń procesora, z którym można się bawić, to w zasadzie nic nie można zrobić, aby mu pomóc (poza przejściem na inną implementację języka Python lub na inny język).

+0

Jeśli uruchomię to okno GUI również przestaje działać z błędem "Brak reakcji, zamknij siłę". Jednak jeśli poczekam, aż wszystkie moje zadania zostaną zakończone, normalna aplikacja będzie kontynuowana. – Tuim

+1

@Tuim. Mój skrypt to tylko proste demo oparte na kodzie w pytaniu. To nie jest uniwersalne rozwiązanie, które będzie działać we wszystkich sytuacjach. Musisz zaktualizować swoje pytanie z odpowiednim wyjaśnieniem tego, co próbujesz zrobić. Jakie są te "zadania", o których wspominasz? Czy są związane z CPU, czy IO-bound? Czy aplikacja, która wykonuje zadania, które sam napisałeś, może zmienić? W jakim języku się pisze? Itd., Itp. – ekhumoro

+0

Te zadania to instalacje np. Rozpakowywanie plików zip, instalowanie pakietów msi/deb takowych. Ale nie jest to bardzo istotne dla sprawy. Aplikacja jest napisana w języku Python oraz w pełni konfigurowalna. Również nie oczekuję odpowiedzi na kopiuj-wklej! Spodziewam się wskazania we właściwym kierunku, a ten, który miałeś, nie wydawał mi się właściwym kierunkiem, próbowałem. Bez obrazy. – Tuim

0

Kiedy programujesz gui, musisz użyć jakiejś formy wielowątkowości, masz wątek roboczy i taki, który aktualizuje GUI i reaguje na zdarzenia myszy i klawiatury. Jest na to kilka sposobów. Polecam przyjrzeć się tej QProgressBar tutorial że ma doskonały przykład roboczą jak używać QProgressBar.

W poradniku są przy użyciu QBasicTimer który jest sposób, uzyskując kontrolę z powrotem do głównego wątku, tak, że może reagować na zdarzenia GUI. Używając w kodzie time.sleep, blokujesz przez jedną sekundę jedyny działający wątek.

+0

Jestem tego świadomy. Ale nie używają więcej wątków, jak ja w tym przykładzie. – Tuim

+1

@Tuim: Tak, są, nie używaj 'time.sleep()'. – Junuxx

+0

@Tuim Zaktualizowałem odpowiedź, aby wyjaśnić, dlaczego twój kod i samouczki nie robią tego samego. –

Powiązane problemy