Arka plan işlemleri sırasında meşgul göstergesi oluşturma

Pyqt uygulamamda QProgressDialog kullanarak bir meşgul göstergesi oluşturmak istiyorum. Yani arka planda main thread de kod yürütülürken ve bir takım işlemler yapılırken kullanıcı bilgilendirmesi için ekranda gözüksün.


sorun şu ki min ve max değerilerini sıfır yaparak şu şekilde oluşturdukran sonra

 self.progressPreview = Progress("İşlem bilgisi", "İşleniyor...", 0, 0, self)

barın gözükmesi için bir olay döngüsü istiyor. işlemlerin başında ve sonunda yayılması için iki sinyal oluşturdum ama bu sinyalleri kullanarak içine soktuğum olay döngüsünü kıramıyorum. Program main thread üzerinden akıyor galiba.

    def createProgressDialog(self): # işleme başlandığı an QProgressDialog oluşturur.
        self.progressPreview = Progress("İşlem bilgisi", "İşleniyor...", 0, 0, self) # QProgressDialog oluşturur
        self.isProcess = True # olay döngüsü işleme bool
        self.workThread = WorkThread(self.busyIndicator(self.isProcess)) # olay döngüsü bir thread e gönderiliyor
        self.workThread.start() # QThread başlat
        print("QProgressDialog oluştu...")

    def busyIndicator(self,isProcess): # QProgressDialog olay döngüsü. bitirme sinyali yayılınca döngüden çıkılmalı
        while True:
            if isProcess:
                QApplication.processEvents() # olay dönüsü yenileme
            else:
                break

    def finishProgressDialog(self): # bitirme sinyali bu metodu çalıştırır.
        self.busyIndicator(False) # olay dönüsü bitir
        self.workThread.terminate() # thread sonlandır.
        self.progressPreview.close() # QProgressDialog pencere kapat
        print("QProgressDialog sonlandı.")

görüleceği gibi QThread e busyIndicator metodunu geçirmek ve bunu run fonksiyonu üzerinde çalıştırmak istiyorum ama olmuyor

from PyQt5.QtCore import QThread

class WorkThread(QThread):
    def __int__(self,work): # work metodu yapıcıya iletildi.
        super(WorkThread, self).__int__()
        self.work = work()

    def run(self) -> None:
        try:
            print("run çalıştı")
            self.work() # çalıştırılacak metod
        except Exception as e:
            print(e)

oluşturduğum thread sınıfının çalışmadığını da gözlemliyorum. Yabancı forumlarda QThread ile çalışırken sinyal ve yuva şeklinde ayarlayın demişler ama bu sinyal işini nasıl kuracağımı da bilmiyorum.

başka bir yol olarak

loop = QEventLoop(self)
QTimer.singleShot(1000, loop.quit)
loop.exec_()

denedim başka bir yerde yüzdelik bar olarak. Çalıştı. Bir for döngüsü içindeydi. bu olay döngüsü için yeterli oldu. Ama aynı şeyi meşgul göstergesi için yapınca haliyle singleShot 1 sn sonra olay döngüsüü kırdı çünkü for döngüsü içinde değildi. Bende loop.quit yuvasını harici bir sinyal bağlamam gerektiğini düşündüm ama tabi kodun main thread üzerinden akışına devam etmesi ve bu işin harici bir thread üzerinden yapılması gerektiği noktasına geldim. Bir şekilde yapılan işlerin sonuna geldiğinde bitirme sinyali yayılmalı ve önce olay döngüsü kırılmalı, sonra thread sonra da pencere kapanmalı.


kafamdakini ifade eden bir diyagram bu şekildedir.

vakit ayırıp zihin mesaisi yapacaklara şimdiden müteşekkirim.

Oncelikle: Butun GUI islemleri ana thread uzerinde olmali. Diyagram dahil her sey ters cevrilmeli yani.

Soru icin zaman harcandigi belli. Bir dahaki sefer icin zaman kazandiracak bir tuyo vermek istiyorum: Calisan kod.

Mesela su uc fonksiyonluk kod parcasi birkac satir eklenerek anlatilmaya calisilan problemi dogrudan gosterebilen bir kod parcasina donusebilirdi. QEventLoop da keza.

Hatta calisan ornek kod yazmaya calisilirken asil kodda alakasiz bir hata oldugu ve butun problemin buradan kaynaklandigi fark edilip butun soru cope atilabiliyor! (Mesela: Slota signal yollanmasi unutulmus)

Soru cope atilmadigi takdirde ise insanlara zamanlarini bosa harcatmis olabiliyoruz. (“Arkadaslar, pardon, program takilmiyormus, print etmeyi unutmusum!”) Bu soru icin gecerli oldugunu zannetmiyorum ama referans olsun diye yazdim.

çalışan kodu atmayı düşündüm ama projem bir iki sınıftan değil 5-6 sınıftan olışuyor, diğer kaynaklarda var tabi. Yine de ana thread herhangi bir işlem yapan bir örnek benim için aydınlatıcı olacaktır. Çünkü istediğim şekilde bir tane nette bulamadım. Eğer olacak farklı bir şekil de varsa duymak isterim tabi.

bunu açar mısınız? Benim istediğim arka planda işlemler yapılırken ön tarafta meşgul göstergesi görünmesi. Kullanıcı programın donduğunu düşünmemesi için. Bu da bariz yeni bir thread gerektirir. download ekranlarında olan bir durumdaki gibi entegre etmeye çalışabilirim belki bir örnek olursa

Geriye kalan 3-5 sinifin problemle alakasi yok ama?
O zaman problemi yasayan, gosterebilen ufak bir ornek lazim: http://sscce.org/

GUI aplikasyonlarinda bir adet “ana thread” var. QtApplication.exec_'i (miydi) calistiran, event loop’a bakan. Butun GUI islemleri bu thread uzerinde yapilmali. “Arka planda” yapilan islemler baska, threading veya QtThread ile yaratilmis bir thread uzerinde olmali. QProgressDialog ve benzeri GUI elementlerini gosteren, degistiren islemler diyagramdaki “Main Thread” uzerinde olmali.

Durumu simüle eden ufak bir örnek hazırladım.

class MainWindow(QMainWindow):
    createProgressSignal = pyqtSignal()
    finishProgressSignal = pyqtSignal()
    
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")
        self.setFixedSize(300,100)

        layout = QVBoxLayout()
        
        button= QPushButton("do some works")

        layout.addWidget(button)

        button.clicked.connect(self.doSomethingWorks)

        self.createProgressSignal.connect(self.createProgress)
        self.finishProgressSignal.connect(self.finishProgress)
      
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

    def doSomethingWorks(self):
        # işlem öncesinde qprogressdialog çağırma simyali yay
        self.createProgressSignal.emit()

        # Yapılacak bir işlemi simüle eden döngü. 
        # Main thread bu şekilde bir işlem yaparken QProgressDialog çağırılır 
        for i in range(20):
            print(i)
            time.sleep(0.5)
        print("işlem bitti")

        # işlem sonrasında qprogressdialog sonlandırma sinayali yay
        self.finishProgressSignal.emit()

    def createProgress(self):# progress dialog oluşturme ve çalıştırma slotu
        self.progress =  ProgressDialog("İşlem Ekrani","İşlem Yapılıyor...",0,0,self) # Progress dialog tanımlama
        
        # event loop gerekiyor. Bu haliyle boş pencere döndürüyor. 


    def finishProgress(self): # progress dialog kapatma slotu
        self.progress.close()



app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

burada for döngüsü main thread de arka planda yapılacak bir takım işleri simüle ediyor.
bu da progress dialog oluşturan sınıf:

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QProgressDialog


class ProgressDialog(QProgressDialog):
    def __init__(self,win_label,text,min,max=0,parent=None):
        super(ProgressDialog,self).__init__(parent)

        self.setWindowTitle(win_label)
        self.setLabelText(text)
        self.setFixedSize(400,100)

        self.setRange(min,max)
        self.setCancelButton(None)

        self.setWindowModality(Qt.WindowModal)

        self.show()

burada bariz bir şekilde progress dialog-un tekrar çizilmesi için bir event loop istiyor ama main thread üzerinde tanımlanan bir event loop oluşturunca da onda sonsuz döngüye giriyor. thread ile bir şeyler yapmaya çalışsam da bu kez yine işlemin main thread üzerinde olması gerektiği için (thread sadece başla/bitir sinyali yayacağından) yine bir şey çıkmıyor. Sanırım yaklaşımım tamamen yanlış ya da başka mantık gerektiriyor.

Lutfen calisan kod atmaya ozen gosterin.

Dedigim gibi, ana thread’de yapilan islemleri yeni thread’e almak lazim:

### import'lar ###

class MainWindow(QMainWindow):
    createProgressSignal = pyqtSignal()
    finishProgressSignal = pyqtSignal()
    
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")
        self.setFixedSize(300,100)

        layout = QVBoxLayout()
        
        button= QPushButton("do some works")

        layout.addWidget(button)

        button.clicked.connect(self.doSomethingWorks)

        self.createProgressSignal.connect(self.createProgress)
        self.finishProgressSignal.connect(self.finishProgress)
      
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

    def doSomethingWorks(self):
        # işlem öncesinde qprogressdialog çağırma simyali yay
        self.createProgressSignal.emit()
        threading.Thread(target=self.threadMain, args=(self.finishProgressSignal,)).start()

    def threadMain(self, finishSignal):
        # Yapılacak bir işlemi simüle eden döngü. 
        # Main thread bu şekilde bir işlem yaparken QProgressDialog çağırılır 
        for i in range(20):
            print(i)
            time.sleep(0.5)
        print("işlem bitti")

        # işlem sonrasında qprogressdialog sonlandırma sinayali yay
        finishSignal.emit()

    def createProgress(self):# progress dialog oluşturme ve çalıştırma slotu
        self.progress =  ProgressDialog("İşlem Ekrani","İşlem Yapılıyor...",0,0,self) # Progress dialog tanımlama
        
        # event loop gerekiyor. Bu haliyle boş pencere döndürüyor. 


    def finishProgress(self): # progress dialog kapatma slotu
        self.progress.close()

class ProgressDialog(QProgressDialog):
    def __init__(self,win_label,text,min,max=0,parent=None):
        super(ProgressDialog,self).__init__(parent)

        self.setWindowTitle(win_label)
        self.setLabelText(text)
        self.setFixedSize(400,100)

        self.setRange(min,max)
        self.setCancelButton(None)

        self.setWindowModality(Qt.WindowModal)

        self.show()



app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
2 Beğeni

Teşekürler. Yardımcı oldu.