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 :
my_plugin.py
with the main MyPlugin
class and initGui()
, unload()
and run()
methodsmy_plugin_dialog.py
with a simple MyPluginDialog
class, that inherits from QtWidgets.QDialog
and the FORM_CLASS
based on my .ui
designer file. The class only contains __init__()
method, itself calling self.setupUi()
my_plugin_dialog_base.ui
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 ?
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)
...