5

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?

1
  • 1
    Hi @MrXsquared, Kadir Şahbaz has fully addressed your question about passing a layer object to a method, so just adding a couple of tips that may not be intuitive: Since you are using a QgsMapLayerComboBox, you don't need to populate it with layer names like a regular QComboBox. One of it's main advantages is that it uses a model to dynamically show all project layers by default. All need to do is set any filters you need. Then, retrieving the selected layer is as easy as: selectedLayer = self.dlg.Isochrones_SelectInputLayer.currentLayer() which will return a QgsVectorLayer object.
    – Ben W
    Feb 19, 2020 at 10:13

1 Answer 1

6

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 ... 
4
  • The first one somehow calls my function 5 times (2 layers with total of 5 points??). However the second one basically works just fine. But because I dont refetch the selectedLayer in my function any longer, changes in the selection wont be recognized. Do I have to place the layer selection somewhere else than in in run?
    – MrXsquared
    Feb 18, 2020 at 18:34
  • I think I solved this issue by outsourcing layerselection to another function as explained here: gis.stackexchange.com/a/225659/107424 and just calling this function on first startup and every time the QgsMapLayerComboBox has been used.
    – MrXsquared
    Feb 18, 2020 at 20:03
  • I couldn't review your code in detail. This was a quick answer. I've reviewed your script now. Let me edit the answer. If it doesn't fit your solution, please feel free to edit the answer or to add your answer as a new answer. Feb 18, 2020 at 20:15
  • 1
    @MrXsquared I've added some explanation. I hope I could explain clearly enough. It's hard to me to explain in detail in English. Feb 18, 2020 at 21:05

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.