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?

asked Feb 18, 2020 at 17:11
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. Commented 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 ... 
answered Feb 18, 2020 at 18:02
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? Commented 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. Commented 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. Commented 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. Commented Feb 18, 2020 at 21:05

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.