pythonqtpyqt5qgispyqgis

Disconnect signals for QGis plugin with dialog created by Qt Designer


I'm working on a plugin for QGis 3.x. The UI was created with Qt Designer and the plugin code boilerplate by Plugin builder.

I do have 3 main files in my project :

I encounter some issues related to duplicate signals when starting/closing the plugin dialog. The most similar discussion on SO is the following : https://gis.stackexchange.com/questions/137160/qgis-plugin-triggers-function-twice

Yet, my problem is that my dialog object is created within the run() method (see below), so I can't access to my UI elements in the initGui() nor unload() methods.

So how can I disconnect signals, or define them so that closing the plugin also disconnects all my signals ?

Here is an excerpt of my_plugin.py content (created by Plugin Builder, except the last line related to connecting the signal and the slot) :

def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/my_plugin/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'Start My Plugin'),
            callback=self.run,
            parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True

def run(self):
        """Run method that performs all the real work"""

        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.first_start = False
            self.dlg = MyPluginDialog()
        self.dlg.push_button_start.clicked.connect(self.on_pb_start) # connect signal to slot

And my_plugin_dialog.py:

FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'small_etl_dialog_base.ui'))

class MyPluginDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super(MyPluginDialog, self).__init__(parent)
        # Set up the user interface from Designer through FORM_CLASS.
        # After self.setupUi() you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

Maybe there is a proper way to access dialog objects inside the initGui() method ?


Solution

  • This is happening because you connect your signal every time the run method is called. However, you only create your dialog once guarded by a Boolean variable.

    The solution is to only connect your signal when you create the dialog, so something like this would work better:

    def run(self):
        ...
        if self.first_start == True:
            self.first_start = False
            self.dlg = MyPluginDialog()
            self.dlg.push_button_start.clicked.connect(self.on_pb_start)
        ...
    

    Also, note that you do not necessarily need the self.first_start boolean guard. You could always check whether self.dlg is not None instead.

    So, you could tidy this up a bit by something like this:

    def run(self):
        ...
        if not self.dlg:
            self.dlg = MyPluginDialog()
            self.dlg.push_button_start.clicked.connect(self.on_pb_start)
        ...