4

In addition to my previous question, I am struggling to implement the possibility to choose an attribute from a layer within QgsPropertyOverrideButton.

I have a combobox where one can choose a layer to use. Getting attributes and geometries from this selected layer works just fine, but it is not recognized by QgsPropertyOverrideButton when I use .setVectorLayer() as described in docs. For example self.dlg.Isochrones_WalkSpeed_Override.setVectorLayer(selectedLayer)

enter image description here

A reduced code snipped:

layers = QgsProject.instance().layerTreeRoot().children() # Fetch the currently loaded layers
selectedLayerIndex = self.dlg.Isochrones_SelectInputLayer.currentIndex() # Fetch the selected layer in combobox
selectedLayer = layers[selectedLayerIndex].layer() # Use the in combobox selected layer
fieldnames = [field.name() for field in selectedLayer.fields()] # Receive fieldnames from selected layer
features = selectedLayer.getFeatures() # <-- Works just fine

self.dlg.Isochrones_WalkSpeed_Override.setVectorLayer(selectedLayer) # <-- Seems to not work
self.dlg.Isochrones_WalkSpeed_Override.updateFieldLists()   # <-- Seems to not work

# Do some stuff here     

for feature in features:
    # retrieve every feature with its geometry and attributes
    print("Feature ID: ", feature.id())
    # fetch geometry
    # show some information about the feature geometry
    geom = feature.geometry()
    pointgeom = geom.asPoint() #Read Point geometry
    x = pointgeom.x() #Read X-Value
    y = pointgeom.y() #Read Y-Value
    print("PointX: ", x, " | PointY: ", y)

    # fetch attributes
    attrs = feature.attributes()
    # attrs is a list. It contains all the attribute values of this feature
    print(attrs)

    #Check where to gather attributes from: GUI or Layer?
    #WalkSpeed
    if self.dlg.Isochrones_WalkSpeed_Override.isActive() == True:
        Isochrones_WalkSpeed, IrrelevantSuccessStorage = self.dlg.Isochrones_WalkSpeed_Override.toProperty().value(QgsExpressionContext()) #Receiving Value from GUI: DataDefinedOverride
    else:
        Isochrones_WalkSpeed = self.dlg.Isochrones_WalkSpeed.value() #Receiving Value from GUI: SpinBox

    # Do more stuff here

Why does .getFeatures() recognize the layer but .setVectorLayer() doesnt? And how would I need to write my code to make .setVectorLayer() recognizing the selected layer, so users are able to choose an attribute from the selected layer within QgsPropertyOverrideButton?

2 Answers 2

4

You need several things to correctly use your QgsPropertyOverrideButton.

Initializing the button

  • First, you may want to register an expression context generator, which will allow the use of global, project, and layer variables. QgsVectorLayer is a QgsExpressionContextGenerator, so you just have to call
self.dlg.overrideButton.registerExpressionContextGenerator(selectedLayer)
  • Then, you need to tell the button which kind of property it expects. This is done by calling the init function of the button. This function expects a QgsPropertyDefinition, which you can create like this:
definition = QgsPropertyDefinition("walkSpeed", "Walk Speed km/h", QgsPropertyDefinition.DoublePositive) 
self.dlg.overrideButton.init(0, QgsProperty(), definition, layer, False)

This tells the button to accept any value that can be cast to a positive floating number. It also removes the need to call setVectorLayer. For other type of definitions, see the official documentation.

The QgsPropertyOverrideButton is now all set.


Resolving the expression

Next, when you evaluate the property for each feature, you have to correctly initialize the QgsExpressionContext. If you don't, you will be able to evaluate simple numerical expressions, but nothing which involves layer variables or feature fields.

QGIS provides a QgsExpressionContextUtils class which can be used to generate expression contexts. The following context will be able to evaluate global, project, and layer variables:

ctx = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(selectedLayer))

Aditionnaly, you need to set the context's feature before each call to toProperty, which will allow the button to use the correct feature when evaluating the expression.

ctx = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(selectedLayer))
for feature in selectedLayer.getFeatures():
    ctx.setFeature(feature)
    walkSpeed, ok = self.dlg.overrideButton.toProperty().value(ctx)  # Receiving value from DataDefinedOverride
    if not ok:
        walkSpeed = self.dlg.Isochrones_WalkSpeed.value()  # Receiving value from spinBox

1

You could use the currentText() method to fetch the selected layer in the combo box.

layers = project.layerTreeRoot().children()
selectedLayerName = self.dlg.Isochrones_SelectInputLayer.currentText()
selectedLayer = [l.layer() for l in layers if l.name() == selectedLayerName][0]
self.dlg.Isochrones_WalkSpeed_Override.setVectorLayer(selectedLayer)

Having said that (and you probably don't want to start reworking your plugin), but I would strongly recommend using a QgsMapLayerComboBox custom widget for selecting input layers rather than a generic QComboBox. Once you start playing around with them they are much easier to work with!

I wrote about them in an answer here:

Listing of vector layers

1
  • Sorry, but I couldn't make it work like this. But could be just me... +1 for the great tip about QgsMapLayerComboBox, helped a lot.
    – MrXsquared
    Commented Feb 18, 2020 at 13:58

Your Answer

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

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