PyQGIS – How to Pass Variables to Functions Upon Button Click in PyQGIS Plugins

pyqgispyqgis-3qgis-plugins

I want the user to make a selection of a layer from a QgsMapLayerComboBox and setting up their settings they wish to use. After a click on a button, a function should be called which performs the work. There are different buttons to call different functions.

I actually have a working code below, but it seems quite ridiculous to me to catch the selectedLayer more than once. I think I am doing something completely wrong here and there must be an efficient and proper way of passing a variable from run to a function when a button has been clicked.

I actually managed to do it as explained here, but not as expected. (I must have obviously misuderstood the answer). When using self.dlg.Isochrones_RequestIsochrones.clicked.connect(self.Isochrones_RequestIsochrones(selectedLayer)) it seems to pass the selectedLayer to the function, but the function is beeing executed right on startup of the plugin. There is no chance for the user to make their settings.

class OpenTripPlannerPlugin:
    # other stuff here

    def Isochrones_RequestIsochrones(self, selectedLayer): 
        # I am actually doing this twice because I am not able to pass the selected layer from run properly
        layers = QgsProject.instance().layerTreeRoot().children()
        selectedLayerName = self.dlg.Isochrones_SelectInputLayer.currentText()
        selectedLayer = [l.layer() for l in layers if l.name() == selectedLayerName][0]

        # doing stuff with the selected Layer here

    def run(self):
        if self.first_start == True:
            self.first_start = False
            self.dlg = OpenTripPlannerPluginDialog()

        # Using QgsMapLayerComboBox to make a layer selection    
        vector_names = [l.name() for l in QgsProject().instance().mapLayers().values() if isinstance(l, QgsVectorLayer)] # Fetch vector layer names
        self.dlg.Isochrones_SelectInputLayer.addItems(vector_names) # Fill with layers
        self.dlg.Isochrones_SelectInputLayer.setFilters(QgsMapLayerProxyModel.PointLayer) # Filter out all layers except Point layers 

        # Getting selectedLayer and hopefully passing it to functions 
        layers = QgsProject.instance().layerTreeRoot().children()
        selectedLayerName = self.dlg.Isochrones_SelectInputLayer.currentText()
        selectedLayer = [l.layer() for l in layers if l.name() == selectedLayerName][0]  <-- I want to make this selected layer available in other functions I call

        # Doing some stuff with selectedLayer like setting up QgsOverrideButton
        self.dlg.Isochrones_WalkSpeed_Override.registerExpressionContextGenerator(selectedLayer)
        self.dlg.Isochrones_WalkSpeed_Override.init(0, QgsProperty(), QgsPropertyDefinition("walkSpeed", "Walk Speed km/h", QgsPropertyDefinition.DoublePositive), selectedLayer, False)

        # Doing more stuff...

        # Calling Functions on button click
        self.dlg.Isochrones_RequestIsochrones.clicked.connect(self.Isochrones_RequestIsochrones) # <-- Using ...(self.Isochrones_RequestIsochrones(selectedLayer)) seems to work, but the function is executed on startup of the plugin with no chance to make individual settings

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            #Isochrones_RequestIsochrones()  
            print("test!")  

How do I properly pass the selectedLayer to a function after a button has been clicked?

Best Answer

Solution 1:

When connecting a method to an object event (in this case click event) in PyQt, for passing an argument to that method, you can use lambda function.

self.dlg.Isochrones_RequestIsochrones.clicked.connect(lambda: self.Isochrones_RequestIsochrones(selectedLayer))

After doing that, you don't need three lines in Isochrones_RequestIsochrones method. Because you passed the selected layer to the method. But in this case, you can't use selectedLayer in other functions except Isochrones_RequestIsochrones.

class OpenTripPlannerPlugin:
    # other stuff here

    def Isochrones_RequestIsochrones(self, selectedLayer): 

        # YOU DON'T NEED THESE LINES ####################
        # layers = QgsProject.instance().layerTreeRoot().children()
        # selectedLayerName = self.dlg.Isochrones_SelectInputLayer.currentText()
        # selectedLayer = [l.layer() for l in layers if l.name() == selectedLayerName][0]

        # doing stuff with the selected Layer here

Solution 2:

If you want to make selectedLayer available in other functions in the class, convert selectedLayer from Local Variable to Instance Variable by adding self keyword before selectedLayer.

After doing that, you don't need to pass selectedLayer as an argument, because you can access this variable in every instance methods (for example Isochrones_RequestIsochrones) defined in the class. In this case, no need lambda function, because no need passing selectedLayer as argument.

Also, no need those three lines in Isochrones_RequestIsochrones method anymore.

class OpenTripPlannerPlugin:
    # other stuff here

    def Isochrones_RequestIsochrones(self, selectedLayer): 

        # YOU DON'T NEED THESE LINES ####################
        # layers = QgsProject.instance().layerTreeRoot().children()
        # selectedLayerName = self.dlg.Isochrones_SelectInputLayer.currentText()
        # selectedLayer = [l.layer() for l in layers if l.name() == selectedLayerName][0]

        # doing stuff with the selected Layer here

        ### YOU CAN ACCESS selected layer using 'self.selectedLayer' ###


    def run(self):

        # Doing stuff...

        ### ADDED self ###
        self.selectedLayer = [l.layer() for l in layers if l.name() == selectedLayerName][0]

        # Doing more stuff...

        # Calling Function on button click
        self.dlg.Isochrones_RequestIsochrones.clicked.connect(self.Isochrones_RequestIsochrones)

        # Other stuff ...