J'ai développé des outils de traitement par lots en tant que plugins python pour QGIS 1.8.
J'ai constaté que lorsque mes outils sont en cours d'exécution, l'interface graphique devient non réactive.
La sagesse générale est que le travail doit être effectué sur un thread de travail, avec les informations d'état / d'achèvement retransmises à l'interface graphique sous forme de signaux.
J'ai lu les documents riverains et étudié la source de doGeometry.py (une implémentation fonctionnelle de ftools ).
En utilisant ces sources, j'ai essayé de construire une implémentation simple afin d'explorer cette fonctionnalité avant d'apporter des modifications à une base de code établie.
La structure globale est une entrée dans le menu des plugins, qui ouvre une boîte de dialogue avec des boutons de démarrage et d'arrêt. Les boutons contrôlent un thread qui compte jusqu'à 100, renvoyant un signal à l'interface graphique pour chaque numéro. L'interface graphique reçoit chaque signal et envoie une chaîne contenant le numéro à la fois le journal des messages et le titre de la fenêtre.
Le code de cette implémentation est ici:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *
class ThreadTest:
def __init__(self, iface):
self.iface = iface
def initGui(self):
self.action = QAction( u"ThreadTest", self.iface.mainWindow())
self.action.triggered.connect(self.run)
self.iface.addPluginToMenu(u"&ThreadTest", self.action)
def unload(self):
self.iface.removePluginMenu(u"&ThreadTest",self.action)
def run(self):
BusyDialog(self.iface.mainWindow())
class BusyDialog(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent)
self.parent = parent
self.setLayout(QVBoxLayout())
self.startButton = QPushButton("Start", self)
self.startButton.clicked.connect(self.startButtonHandler)
self.layout().addWidget(self.startButton)
self.stopButton=QPushButton("Stop", self)
self.stopButton.clicked.connect(self.stopButtonHandler)
self.layout().addWidget(self.stopButton)
self.show()
def startButtonHandler(self, toggle):
self.workerThread = WorkerThread(self.parent)
QObject.connect( self.workerThread, SIGNAL( "killThread(PyQt_PyObject)" ), \
self.killThread )
QObject.connect( self.workerThread, SIGNAL( "echoText(PyQt_PyObject)" ), \
self.setText)
self.workerThread.start(QThread.LowestPriority)
QgsMessageLog.logMessage("end: startButtonHandler")
def stopButtonHandler(self, toggle):
self.killThread()
def setText(self, text):
QgsMessageLog.logMessage(str(text))
self.setWindowTitle(text)
def killThread(self):
if self.workerThread.isRunning():
self.workerThread.exit(0)
class WorkerThread(QThread):
def __init__(self, parent):
QThread.__init__(self,parent)
def run(self):
self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: starting work" )
self.doLotsOfWork()
self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: finshed work" )
self.emit( SIGNAL( "killThread(PyQt_PyObject)"), "OK")
def doLotsOfWork(self):
count=0
while count < 100:
self.emit( SIGNAL( "echoText(PyQt_PyObject)" ), "Emit: " + str(count) )
count += 1
# if self.msleep(10):
# return
# QThread.yieldCurrentThread()
Malheureusement, ce n'est pas un travail silencieux comme je l'espérais:
- Le titre de la fenêtre se met à jour "en direct" avec le compteur mais si je clique sur la boîte de dialogue, il ne répond pas.
- Le journal des messages est inactif jusqu'à la fin du compteur, puis présente tous les messages à la fois. Ces messages sont étiquetés avec un horodatage par QgsMessageLog et ces horodatages indiquent qu'ils ont été reçus "en direct" avec le compteur, c'est-à-dire qu'ils ne sont pas mis en file d'attente par le thread de travail ou la boîte de dialogue.
L'ordre des messages dans le journal (extrait suit) indique que startButtonHandler termine l'exécution avant que le thread de travail ne commence à fonctionner, c'est-à-dire que le thread se comporte comme un thread.
end: startButtonHandler Emit: starting work Emit: 0 ... Emit: 99 Emit: finshed work
Il semble que le thread de travail ne partage tout simplement aucune ressource avec le thread GUI. Il y a quelques lignes commentées à la fin de la source ci-dessus où j'ai essayé d'appeler msleep () et yieldCurrentThread (), mais aucune ne semblait aider.
Quelqu'un ayant une expérience avec cela est-il capable de détecter mon erreur? J'espère que c'est une erreur simple mais fondamentale qui est facile à corriger une fois qu'elle est identifiée.