6

I am in the process of writing my two first QGIS plugins. (https://github.com/sickel/qgisSpectre and https://github.com/sickel/qgisstripchart) both of them contains two drop down boxes, one to select a layer, and then the other is populated with the fields from the layer when a layer is selected.

For this, I want to just list the vector layers in the first dropdown. After some fooling around, I ended up doing it like this, which feels like going around in circles:

layers = QgsProject.instance().layerTreeRoot().children()
self.dlg.cbLayer.clear()
for layeritem in layers:
    layername=layeritem.name()
    layers = QgsProject.instance().mapLayersByName(layername) 
    if len(layers)==0: # Invalid layer name
        return
    layer = layers[0] # first layer
    if "fields" in dir(layer):
        self.dlg.cbLayer.addItems([layer.name()])

So I am getting the layer names from layerTreeRoot().children(), then I am using that name to pick up the layer from mapLayersByName.

Is there a way to either Get the layer directly from layerTreeRoot().children() or Iterate over the map layers in another way, e.g. through QgsProject.instance().mapLayers()and get each layer's type and name?

(Another problem my current code has, is that it will not list layers inside a group, I think this may work fine if I can get what I need from QgsProject.instance().mapLayers())

3 Answers 3

8

To expand a little on the answer of @Fran Raga, a very quick way to add all the project vector layers to your layer combo box is to use a list comprehension:

vector_names = [l.name() for l in QgsProject().instance().mapLayers().values() if isinstance(l, QgsVectorLayer)]
self.dlg.cbLayer.addItems(vector_names)

However, I would really recommend looking into using the custom QGIS widgets QgsMapLayerComboBox() and QgsFieldComboBox(). Once you start using them I don't think you will look back. A QgsMapLayerComboBox shows all project layers by default, but it is ridiculously easy to filter to only vector layers, raster layers, layers which have geometry or any geometry type.

QgsFieldComboBox has a method setLayer() which filters the fields shown to fields belonging to that layer. This makes it very simple to connect the setLayer() method to the layerChanged() signal of a QgsMapLayerComboBox object and set the layer for the field combo box to the currentLayer() of the map layer combo box so that whenever the layer combo box selection changes, the fields in the field combo box are updated dynamically.

As an added bonus you also get the nice little raster and vector icons next to the combo box items. You can also show the CRS of each layer.

try out this small snippet in the Python console in a project with a few different layers loaded. You should quickly get the idea of how it works and be able to adapt it into your plugins:

class Dlg(QDialog):
    def __init__(self):
        QDialog.__init__(self)
        self.setGeometry(100, 100, 300, 200)
        self.layout = QVBoxLayout(self)
        self.cb_layer = QgsMapLayerComboBox(self)
        self.layout.addWidget(self.cb_layer)
        self.cb_layer.setFilters(QgsMapLayerProxyModel.VectorLayer)
        self.cb_layer.setShowCrs(True)
        self.cb_fields = QgsFieldComboBox(self)
        self.layout.addWidget(self.cb_fields)
        self.cb_fields.setLayer(self.cb_layer.currentLayer())
        self.cb_layer.layerChanged.connect(lambda: self.cb_fields.setLayer(self.cb_layer.currentLayer()))

Win = Dlg()
Win.show()

The custom widgets should be available in the QtDesigner which comes with QGIS. If you are on Windows & used the OSGeo4W installer you will find, in C:\OSGeo4W64\bin, a batch file called either qgis-designer or qgis-ltr-designer depending on which version or versions you have installed. Double clicking on this batch file should launch QtDesigner with QGIS custom widgets.

If you can't access them through Designer for any reason, a workaround is to create and add them in your plugin_dialog.py file. For example, add an empty layout in Designer, then create the combo box objects in plugin_dialog.py like:

from qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox

Then in __init__() method, after self.setupUi(self) something like:

self.cbLayer = QgsMapLayerComboBox(self)
self.cbFields = QgsFieldComboBox(self)
self.verticalLayout_1.addWidget(self.cbLayer)
self.verticalLayout_1.addWidget(self.cbFields)

You should then be able to access and work with them in the initGui() method of your main plugin.py file e.g.

self.dlg.cbLayer.setFilters(QgsMapLayerProxyModel.VectorLayer)
6

To iterate over the layers of the project and get its geometry and name you can use something like this.

for layer in QgsProject.instance().mapLayers().values():
    print (layer.name())
    if isinstance(layer, QgsVectorLayer):
        layer_type = layer.geometryType()
        if layer_type == QgsWkbTypes.PointGeometry:
            print ("PointGeometry")
    if isinstance(layer, QgsRasterLayer):
        print ("raster type")
0

Another way to list all layers of the project may be using QgsProject().instance().layerTreeRoot().layerOrder(), which returns a list of layers that preserves the order of the layers. It is preferable if the order of the layers is important and QgsProject.instance().mapLayers().values() does not seem to preserve order (as of QGIS 3.22 LTR).

layers = [l for l in QgsProject().instance().layerTreeRoot().layerOrder()
 if isinstance(l, QgsVectorLayer)]
# ...

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