TransWikia.com

QGIS crash using QThread in a plugin script

Geographic Information Systems Asked on January 25, 2021

I recently wrote a Python script that works with raster data and makes some time-consuming operations. My script uses QThread to update a QList and a QProgressBar and everything works fine if I run the script from PyCharm, the progressbar and the list are updated. Moving the code in a QGIS plugin the gui appears correctly and everything seems to work fine, but if I close and open the plugin again, when I click apply QGIS crashes…

To better understand I wrote a code that reproduce the problem. The gui is a QDialog with a QListWidget, a QLineEdit and a QProgressBar. After writing a word in the QLineEdit and clicking on the apply buttom the result should be that the new thread makes a loop using the string and adds every single letter as item in the QListWidget. In the meantime the progressbar increases and I put a sleep time of 1 second to make it visible. I made use also of a QgsMessageBar to alert the user if no word are edited in the QLineEdit. I hope it is enough clear…

Here the gui code:

from PyQt4.QtCore import *
from PyQt4.QtGui import *

from qgis.gui import QgsMessageBar

import sys, time

class my_worker(QObject):
    def __init__(self, word):
        QObject.__init__(self)
        self.word = word

    def run(self):
        bar_step = 100/len(self.word)
        for i in self.word:
            self.emit(SIGNAL("add_item(QString)"), i)
            self.emit(SIGNAL("increase_bar(int)"), bar_step)
            time.sleep(1)
        self.emit(SIGNAL("finished()"))

class my_ui(QDialog):
    def __init__(self, parent=None):
        """Constructor."""
        super(my_ui, self).__init__(parent)

        self.list = QListWidget()
        self.messagebar = QgsMessageBar()
        input_label = QLabel("Input a word")
        self.inputedit = QLineEdit()
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setValue(0)
        self.button_box = QDialogButtonBox(QDialogButtonBox.Apply | QDialogButtonBox.Close)

        layout = QGridLayout()
        layout.addWidget(self.messagebar, 0, 0, 1, 4)
        layout.addWidget(self.list, 1, 0, 1, 4)
        layout.addWidget(input_label, 2, 0)
        layout.addWidget(self.inputedit, 2, 1, 1, 3)
        layout.addWidget(self.progress_bar, 3, 0, 1, 4)
        layout.addWidget(self.button_box, 4, 2, 1, 2)
        self.setLayout(layout)

        self.connect(self.button_box, SIGNAL("rejected()"), self, SLOT("reject()"))

        self.setWindowTitle("Example thread")


    def start_worker(self, word):
        self.worker = my_worker(word)
        self.thread = QThread()
        self.worker.moveToThread(self.thread)
        self.connect(self.worker, SIGNAL("finished()"), self.workerFinished)
        self.connect(self.worker, SIGNAL("add_item(QString)"), self.add_item)
        self.connect(self.worker, SIGNAL("increase_bar(int)"), self.increase_bar)
        self.thread.started.connect(self.worker.run)
        self.thread.start()

    def workerFinished(self):
        self.progress_bar.setValue(100)
        self.worker.deleteLater()
        self.thread.quit()
        self.thread.wait()
        self.thread.deleteLater()

    def add_item(self, item):
        self.list.addItem(item)
        return

    def increase_bar(self, bar_step):
        self.progress_bar.setValue(self.progress_bar.value() + bar_step)

And here the apply and run methods in the main file of the plugin:

    def apply(self):
    
    self.dlg.list.clear()
    self.dlg.progress_bar.setValue(0)
    if self.dlg.inputedit.text() == "":
        self.dlg.messagebar.pushMessage("Missing parameter", 'Please insert a word!',
                                        level=QgsMessageBar.WARNING, duration=2)
        return

    word = unicode(self.dlg.inputedit.text())
    self.dlg.start_worker(word)

def run(self):
    """Run method that performs all the real work"""        
    self.dlg.list.clear()
    self.dlg.inputedit.clear()
    self.dlg.progress_bar.setValue(0)
    self.dlg.connect(self.dlg.button_box.button(QDialogButtonBox.Apply), SIGNAL("clicked()"), self.apply)

    # show the dialog
    self.dlg.show()
    # Run the dialog event loop
    result = self.dlg.exec_()
    # See if OK was pressed
    if result:
        # Do something useful here - delete the line containing pass and
        # substitute with your code.
        pass

As I said everything works fine but I get two strange behaviours:

  • If I click apply without entering anything in the QLineEdit the QgsMessageBar appears correctly but if I close the QDialog and I do the same the QgsMessageBar has two items (1 more, see picture)

enter image description here

  • If I insert a word in the QLineEdit after opening the plugin for the second time, clicking on apply QGIS crashes.

It seems that closing the Dialog it is not deleted and all the changing done during the first opening are preserved.

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP