Fiecare aplicație care conține o Interfață Grafică (GUI) este însoțită de un Fir de Execuție Principal (Main Thread), acest fir de execuție numit și „GUI Thread” trebuie să rămâna în permanență disponibil în timpul executării sarcinilor complexe ce necesită un timp îndelungat pentru finalizarea lor și să răspundă cererilor utilizatorului.
De exemplu utilizatorul s-ar putea răzgândi, dorind astfel anularea sarcinii.
De aceea sarcinile ce necesită un timp îndelungat pentru terminarea lor vor fi executate în alte fire de execuție și nu în contextul firului de execuție principal care se ocupă de interfața grafică.
În Qt acest lucru este realizat prin utilizarea Semnalelor și Sloturilor (Signals & Slots) ca metodă de comunicare între elementele interfeței grafice cu care utilizatorul poate interacționa (un buton, o etichetă, etc.) și în cazul de față un alt fir de execuție (thread) separat.
Semnalul este transmis de fiecare dată când s-a produs un eveniment specific acestuia, către un slot care este o funcție apelată ca răspuns pentru semnalul transmis.
Desi Qt vine cu obiectele sale care au câteva semnale predefinite cum ar fi semnalul „clicked()” al QPushButton noi putem adăuga și alte semnale acestor obiecte
creând o clasă care să derive din obiectul căruia vrem să îi adăugam semnale noi.
De exemplu am putea dori să afișăm ora exactă în momentul apăsarii butonului nostru (știu foarte folositor, trebuie doar să vă imaginați că posibilitățile sunt infinite), acest lucru este posibil in felul următor:
# _*_ coding: utf-8 _*_ """ Python 3.3 Signal Slots Multithreading Exemplu Aplicație Autor: Blaga Florentin Gabriel worldit.info """ import sys import datetime from PyQt4 import QtGui, QtCore class ButonPersonalizat(QtGui.QPushButton): sigTimePressed = QtCore.pyqtSignal(str) def __init__(self, name, parent=None): super(ButonPersonalizat, self).__init__(name, parent) self.clicked.connect(self.on_button_press) # capturăm semnalul "clicked()" pentru a putea emite semnalul nostru def on_button_press(self): self.sigTimePressed.emit(str(datetime.datetime.now().time())) # emitem semnalul nostru class Window(QtGui.QMainWindow): def __init__(self): super(Window, self).__init__() self.time_label = QtGui.QLabel("Apasă Butonul", self) self.check_label = QtGui.QLabel("", self) self.btn_personalizat = ButonPersonalizat('Apasă-mă', self) # obiect #semnal #slot self.btn_personalizat.sigTimePressed[str].connect(self.show_time_slot) self.btn_personalizat.clicked.connect(self.on_click_slot) self.init_ui() def init_ui(self): # Inițializare interfată grafică # aranjăm elementele grafice self.btn_personalizat.move(50, 50) self.check_label.move(20, 20) self.resize(250, 150) self.setWindowTitle("Exemplu") # afișăm fereastra self.show() def show_time_slot(self, time): # slotul care va primi semnalul "sigTimePressed()" care este adăugat de noi clasei QPushButton. self.time_label.setText(time) def on_click_slot(self): # slotul care va primi semnalul "clicked()" al butonului "btn_personalizat" self.check_label.setText("Buton Apăsat") def main(): app = QtGui.QApplication(sys.argv) wn = Window() sys.exit(app.exec_()) if __name__ == '__main__': main()
Cred că v-am plictisit destul cu semnalele și sloturile, revenind la oile noastre această bucată de cod este exemplul perfect pentru categoria „Așa nu.”, deoarece o dată cu apăsarea butonului „Pornire buclă” interfața grafică va îngheța fiindcă firul de execuție principal nu va mai avea timp să actualizeze și GUI-ul, fiind blocat în acea buclă.
# _*_ coding: utf-8 _*_ """ Python 3.3 Signal Slots Multithreading Exemplu Aplicație Autor: Blaga Florentin Gabriel worldit.info """ import sys, time from PyQt4 import QtGui class Window(QtGui.QWidget): def __init__(self): super(Window, self).__init__() self.btn_close = QtGui.QPushButton('închidere', self) self.btn_loop = QtGui.QPushButton("Pornire Buclă", self) # obiect #semnal #slot self.btn_close.clicked.connect(self.btn_close_slot) self.btn_loop.clicked.connect(self.btn_loop_slot) self.init_ui() def init_ui(self): # Inițializare interfată grafică # aranjăm elementele grafice self.resize(250, 150) self.setWindowTitle("Exemplu") self.btn_close.move(50, 50) self.btn_loop.move(150, 50) # afișăm fereastra self.show() def btn_close_slot(self): # Slot buton închidere self.close() def btn_loop_slot(self): # Slot buton buclă i = 0 while True: i = i + 1 if i > 4: break time.sleep(1) def main(): app = QtGui.QApplication(sys.argv) wn = Window() sys.exit(app.exec_()) if __name__ == '__main__': main()
Pentru a permite interfeței grafice să se actualizeze și să raspundă din nou la comenzile utilizatorului, în timp ce o alta funcție execută operații, va trebui să o mutăm din contextul firului de executie al interfeței grafice deblocând astfel firul de execuție principal. Imaginea de mai jos reprezintă modul în care programul nostru își va desfășura execuția după apăsarea butonului.
# _*_ coding: utf-8 _*_ """ Python 3.3 Signal Slots Multithreading Exemplu Aplicație Autor: Blaga Florentin Gabriel worldit.info """ import sys from PyQt4 import QtGui, QtCore class WorkerThread(QtCore.QThread): sigFinished = QtCore.pyqtSignal(bool) # Definim semnalele precum și ce tip de parametru vor emite (int bool str etc) sigProgress = QtCore.pyqtSignal(int) # Definim semnalele precum și ce tip de parametru vor emite (int bool str etc) def __init__(self, parent=None): super(WorkerThread, self).__init__(parent) def run(self): i = 0 while True: if i > 10: self.sigFinished.emit(True) # Emitem semnalul break else: i = i + 1 self.sigProgress.emit(i) # Emitem semnalul self.sleep(1) # Stopăm firul de executie pentru o secunda pentru observarea progresului class Window(QtGui.QMainWindow): def __init__(self): super(Window, self).__init__() self.loop = WorkerThread() #obiect #semnal #slot self.loop.sigFinished[bool].connect(self.loop_finished) self.loop.sigProgress[int].connect(self.worker_progress) self.progress_label = QtGui.QLabel("Buclă Inactivă", self) self.btn_close = QtGui.QPushButton('închidere', self) self.btn_loop = QtGui.QPushButton("Pornire Buclă", self) # obiect #semnal #slot self.btn_close.clicked.connect(self.btn_close_slot) self.btn_loop.clicked.connect(self.btn_loop_slot) self.init_ui() def init_ui(self): # Inițializare interfață grafică self.resize(250, 150) self.setWindowTitle("Exemplu") # aranjăm elementele grafice self.btn_close.move(50, 50) self.btn_loop.move(150, 50) self.progress_label.move(80, 80) # afișăm fereastra self.show() def btn_close_slot(self): # Slot buton închidere self.close() def btn_loop_slot(self): # Slot buton buclă if not self.loop.isRunning(): self.loop.start() def worker_progress(self, progr): # Slotul care se ocupă cu afișarea progresului self.progress_label.setText(str(progr)) def loop_finished(self, finished): # Slotul care se ocupă cu informarea utilizatorului că sarcina a fost terminată if finished: QtGui.QMessageBox.information(self, 'Exemplu', 'Sarcină îndeplinită') def main(): app = QtGui.QApplication(sys.argv) wn = Window() sys.exit(app.exec_()) if __name__ == '__main__': main()
în concluzie pentru a avea o interfață grafică care să nu îngețe la fiecare operație ce necesita un timp mai îndelungat până la finalizarea acesteia stăpânirea modului în care firele de execuție (threads) și mecanismul de semnale și sloturi funcționează sunt absolut necesare.
Pentru cei ce doresc să aprofundeze subiectul la un nivel mai înalt îi sfătuiesc să citească și următoarele materiale disponibile din păcate doar în engleză de asemenea vă stau la dispozitie pentru întrebări sau orice fel de nelămurire în secțiunea de comentarii a postării.
Nașpa cu greșeli de tipar, e inadmisibil așa ceva !!!
” o interfață grafică care să nu îngețe la fiecare operație”
Pe langa asta e un articol foarte bun care explica de ce unele programe ingheta, cu totii stimm cat de frustrant este acest lucru cand le vedem la alte program si la programele noastre.Bravo gabi !
Multumesc pentru critica fie ea constructiva (sau nu), chiar si asa banuiesc ca esti si tu constient cat de greu este sa concepi un articol care sa contina cat mai putini termeni in engleza, si cat de greu este sa imbini termenii tradusi astfel incat si alte persoane care nu stau foarte bine cu engleza sa poata sa inteleaga ceva din el.
O mica recomandare din partea mea fiindca ne cunoastem, ar fi sa ai si tu grija cu greselile gramaticale.
Stii vorba aia „Fii schimbarea care vrei sa o vezi la ceilalti!”.