Szukaj…


Wprowadzenie

Paski postępu są integralną częścią doświadczenia użytkownika i pomagają użytkownikom zorientować się, ile czasu pozostało do wykonania danego procesu uruchomionego w interfejsie GUI. W tym temacie omówiono podstawy wdrażania paska postępu we własnej aplikacji.

Temat ten delikatnie dotknie QThread i nowego mechanizmu sygnałów / gniazd. Od czytelników oczekuje się także podstawowej wiedzy na temat widżetów PyQt5.

Podczas dodawania przykładów używaj tylko wbudowanych PyQt5 i Python, aby zademonstrować funkcjonalność.

Tylko PyQt5

Uwagi

Eksperymentowanie z tymi przykładami jest najlepszym sposobem na rozpoczęcie nauki.

Podstawowy pasek postępu PyQt

Jest to bardzo podstawowy pasek postępu, który wykorzystuje tylko to, co jest potrzebne, na minimum.

Mądrze byłoby przeczytać cały ten przykład do końca.

import sys
import time

from PyQt5.QtWidgets import (QApplication, QDialog,
                             QProgressBar, QPushButton)

TIME_LIMIT = 100

class Actions(QDialog):
    """
    Simple dialog that consists of a Progress Bar and a Button.
    Clicking on the button results in the start of a timer and
    updates the progress bar.
    """
    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle('Progress Bar')
        self.progress = QProgressBar(self)
        self.progress.setGeometry(0, 0, 300, 25)
        self.progress.setMaximum(100)
        self.button = QPushButton('Start', self)
        self.button.move(0, 30)
        self.show()

        self.button.clicked.connect(self.onButtonClick)

    def onButtonClick(self):
        count = 0
        while count < TIME_LIMIT:
            count += 1
            time.sleep(1)
            self.progress.setValue(count)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Actions()
    sys.exit(app.exec_())

Pasek postępu jest najpierw importowany tak jak from PyQt5.QtWidgets import QProgressBar

Następnie jest inicjowany jak każdy inny widget w QtWidgets

Metoda liniowa self.progress.setGeometry(0, 0, 300, 25) definiuje pozycje x,y w oknie dialogowym oraz szerokość i wysokość paska postępu.

Następnie przesuwamy przycisk za pomocą .move() o 30 30px dół, aby między dwoma 5px była przerwa 5 5px .

Tutaj self.progress.setValue(count) służy do aktualizacji postępu. Ustawienie maksymalnej wartości za pomocą .setMaximum() spowoduje również automatyczne obliczenie wartości. Na przykład, jeśli maksymalna wartość jest ustawiona na 50, to ponieważ TIME_LIMIT wynosi 100, przeskakuje od 0 do 2 do 4 procent zamiast 0 do 1 do 2 co sekundę. Możesz także ustawić minimalną wartość za pomocą .setMinimum() zmuszając pasek postępu do uruchomienia od określonej wartości.

Uruchomienie tego programu spowoduje utworzenie GUI podobnego do tego.

Okno dialogowe paska postępu nie odpowiada

Jak widać, GUI z całą pewnością zawiesi się i nie będzie odpowiadać, dopóki licznik nie spełni warunku TIME_LIMIT . Wynika to z faktu, że time.sleep powoduje, że system operacyjny wierzy, że program utknął w nieskończonej pętli.

QThread

Jak więc rozwiązać ten problem? Możemy użyć klasy wątków zapewnianej przez PyQt5.

import sys
import time

from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
                             QProgressBar, QPushButton)

TIME_LIMIT = 100

class External(QThread):
    """
    Runs a counter thread.
    """
    countChanged = pyqtSignal(int)

    def run(self):
        count = 0
        while count < TIME_LIMIT:
            count +=1
            time.sleep(1)
            self.countChanged.emit(count)

class Actions(QDialog):
    """
    Simple dialog that consists of a Progress Bar and a Button.
    Clicking on the button results in the start of a timer and
    updates the progress bar.
    """
    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle('Progress Bar')
        self.progress = QProgressBar(self)
        self.progress.setGeometry(0, 0, 300, 25)
        self.progress.setMaximum(100)
        self.button = QPushButton('Start', self)
        self.button.move(0, 30)
        self.show()

        self.button.clicked.connect(self.onButtonClick)

    def onButtonClick(self):
        self.calc = External()
        self.calc.countChanged.connect(self.onCountChanged)
        self.calc.start()

    def onCountChanged(self, value):
        self.progress.setValue(value)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Actions()
    sys.exit(app.exec_())

Podzielmy te modyfikacje.

from PyQt5.QtCore import QThread, pyqtSignal

Ta linia importuje Qthread który jest implementacją PyQt5 celu podzielenia i uruchomienia niektórych części (np. Funkcji, klas) programu w tle (znanego również jako wielowątkowość). Te części są również nazywane wątkami. Wszystkie programy PyQt5 domyślnie mają główny wątek, a pozostałe (wątki PyQt5 ) są wykorzystywane do odciążania dodatkowego czasu i przetwarzania zadań w tle, przy jednoczesnym utrzymaniu działania głównego programu.

Drugi import pyqtSignal służy do wysyłania danych (sygnałów) między pyqtSignal a głównym. W tym przypadku użyjemy go, aby poinformować główny wątek o aktualizacji paska postępu.

Teraz przenieśliśmy pętlę while licznika do osobnej klasy o nazwie External .

class External(QThread):
    """
    Runs a counter thread.
    """
    countChanged = pyqtSignal(int)

    def run(self):
        count = 0
        while count < TIME_LIMIT:
            count +=1
            time.sleep(1)
            self.countChanged.emit(count)

QThread zasadniczo konwertujemy External na klasę, którą można uruchomić w osobnym wątku. Wątki można również uruchamiać i zatrzymywać w dowolnym momencie, zwiększając ich zalety.

Tutaj countChanged to bieżący postęp, a pyqtSignal(int) informuje wątek roboczy, że wysyłany sygnał jest typu int . Chociaż self.countChanged.emit(count) po prostu wysyła sygnał do dowolnych połączeń w głównym wątku (zwykle może być używany do komunikacji z innymi wątkami self.countChanged.emit(count) ).

def onButtonClick(self):
        self.calc = External()
        self.calc.countChanged.connect(self.onCountChanged)
        self.calc.start()

def onCountChanged(self, value):
    self.progress.setValue(value)

Po kliknięciu przycisku uruchomi się self.onButtonClick , a także rozpocznie się wątek. Wątek jest uruchamiany za pomocą .start() . Należy również zauważyć, że podłączyliśmy utworzony wcześniej sygnał self.calc.countChanged do metody użytej do aktualizacji wartości paska postępu. Za każdym razem, gdy External::run::count jest aktualizowany, wartość int jest również wysyłana do onCountChanged .

Tak może wyglądać GUI po wprowadzeniu tych zmian.

Pasek postępu wątku

Powinien także czuć się bardziej responsywny i nie zawiesza się.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow