2

I'm having fun writing plugins for QGIS, but when I execute the code QGIS send me this message error: native Qt signal is not callable.

This is the "incriminated" code (is a simple horizontal slider that modifies the zoom):

import os
import sys
import inspect
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QFileDialog    
from qgis.core import QgsProject, Qgis, QgsMapLayer
from .change_style_dialog import ChangeStyleDialog
from qgis.utils import iface
from qgis.gui import QgsRubberBand
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QSlider, 
QVBoxLayout, QApplication)

# other stuff

def run(self):
  self.dlg.show()

  result = self.dlg.exec_()

  self.dlg.horizontalSlider.setMinimum(10000000)
  self.dlg.horizontalSlider.setMaximum(1000000000)

  if result:
     if self.dlg.horizontalSlider.sliderReleased():
        scale = self.dlg.horizontalSlider.sliderPosition()
        iface.mapCanvas().zoomScale(scale)

This is dlg code:

  class ChangeStyleDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
    super(ChangeStyleDialog, self).__init__(parent)
    self.setupUi(self)
4
  • Can you paste the dlg code, seems to come from its signals ? – J. Monticolo Nov 4 '19 at 15:30
  • if self.dlg.horizontalSlider.sliderReleased() seems to be the signal, that couldn't be used here. What do you want to achieve here? Try it without the second if. – Andreas Müller Nov 4 '19 at 15:42
  • Thankyou, without 'if self.dlg.horizontalSlider.sliderReleased()' the plugin seems to work. I'm trying to learn how to use signals, but maybe this is the wrong case to use them. – Lorenzo Nov 4 '19 at 15:54
  • It does not work really, there are no error messages, but the slider value is always 10000000 (the minimum value) and it does not change when I move the slider. – Lorenzo Nov 4 '19 at 16:29
5

The problem is how you are trying to interact with the signal. Instead of checking for a signal with a conditional 'if:' statement, you should listen for it and connect it to a slot. the basic idea (in pseudocode) is:

object.signalEmitted.connect(slot)

The slot can be a Public slot belonging to an available class, or you can simply define a new class method and connect a signal to it.

Also, I would strongly recommend removing most of your logic from the run() method especially signal/slot connections. Unless you include logic to disconnect them when you close the plugin dialog, having signal/slot connections in the run() method is a bad idea because another connection is created every time you open your plugin dialog. You won't notice the first time you run your plugin, but the second time, whatever slot you have connected to the signal will be called twice when the signal is emitted, then 3 times on the 3rd run etc. until you re-load your plugin or restart QGIS.

There are a few effective ways to deal with this issue; my personal preference is usually to set up signal/slot connections in the initGui() method so that they are only established once when the plugin is loaded at the start of the current QGIS session.

Another problem which can arise from inadvertently creating multiple duplicate connections is empty Python wrappers for deleted C++ objects, causing frustrating runtime errors. I think for your case, all you really need to do in the run() method is show the dialog, and (a small addition which might be useful)- set the slider to the current canvas scale as a starting point for adjustment.

If I was you I would do something like this:

Create a new method in your main plugin.py file. This will be your slot function which you connect to the signal emitted when the slider is released. This would look like the following (I have also included the run() method for reference):

def run(self):
    self.dlg.horizontalSlider.setValue(self.iface.mapCanvas().scale())
    self.dlg.show()
    result = self.dlg.exec_()
    if result:
        pass

def set_scale(self):
    scale = self.dlg.horizontalSlider.value()
    self.iface.mapCanvas().zoomScale(float(scale))

Then, add these lines at the end of the initGui() method:

def initGui(self):
    # other stuff
    self.dlg.horizontalSlider.setMinimum(100000)
    self.dlg.horizontalSlider.setMaximum(250000000)
    self.dlg.horizontalSlider.sliderReleased.connect(self.set_scale)
3
  • Thanks for explaining why having signal/slot connections in run() is a bad idea. Although I am.afraid it will proliferate as a quite highly ranked QGIS plugin tutorial does it that way... – MortenSickel Nov 6 '19 at 6:33
  • @MortenSickel, if I know the one you mean... that tutorial deals with the issue effectively by setting a variable self.first_start = True, in the initGui() method, and adding a check in run() to only connect a signal/ slot if self.first_start = True and then setting it to False, which would also work fine. – Ben W Nov 6 '19 at 8:20
  • Thank you very much for your complete answer. – Lorenzo Nov 8 '19 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.