4

I have created a plugin, initially for myself (so I didn't have to worry about error handling), but now I need to hand it over to my colleagues and I need to make it fool-proof. I have already filtered my mMapLayerComboBox so the user can only select from a vector PointLayer, which solves half the error trapping. As the plugin only deals with data that is in columns I have specified, I need to limit the user to only being able to select a layer that the plugin can deal with (one that I have created using a different plugin).
My thought was to search the ComboBox selected layer for a certain field name, that way I know it's probably a layer I created. Is it possible to do that? I have looked at QGIS 3.0.2 Check if a field exists in a layer in Python but I'm not sure it helped much.

My Combobox filter looks like this:

self.dlg.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.PointLayer)

What I want to achieve is something equivalent to:

   self.dlg.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.PointLayer + field "Vessel")

The alternative is to check the field is present before executing the main body of the code, but if I could filter down to only usable layers then that would be neater. I've never used the .isValid type code and don't know if this would work here.

1 Answer 1

1

You can use the following approach which is to make use of the setExceptedLayerList() method of QgsMapLayerComboBox and pass a list of layers which will not be shown in the combo box.

The basic logic to create the excepted list which will exclude any layer which is not a point layer and does not contain your specific field is like this:

self.map_layers = QgsProject.instance().mapLayers().values()
self.allow_list = [
                    lyr.id() for lyr in self.map_layers if lyr.type() == QgsMapLayerType.VectorLayer
                    and lyr.geometryType() == QgsWkbTypes.PointGeometry
                    and 'Vessel' in [f.name() for f in lyr.fields()]
                ]
self.except_list = [l for l in self.map_layers if l.id() not in self.allow_list]

self.dlg.mMapLayerComboBox.setExceptedLayerList(self.except_list)

In a plugin, I would place this logic inside a class method and call it when the dialog is shown. I would also connect this method to a signal such as QgsMapCanvas.layersChanged which, in your plugin would look something like:

self.iface.mapCanvas().layersChanged.connect(self.populateComboBox)

This will ensure that the layers shown in the map layer combo box are updated when the layer set changes, even if the plugin dialog is still open. For example when adding/removing layers in the current project or open a new or existing project. You could also add a condition here to check if the dialog is actually open, to avoid unnecessary overhead slowing things down. Just make sure this signal/ slot is only connected once, when the plugin is initialised.

Below you will find a very simple example plugin based on the minimal plugin example from Martin Dobias here.

I have tested this plugin which should show you how to set up the logic I have described above.

from PyQt5.QtWidgets import QDialog, QAction, QVBoxLayout, QPushButton, QMessageBox
from qgis.core import QgsMapLayerProxyModel, QgsProject, QgsMapLayerType, QgsWkbTypes
from qgis.gui import QgsMapLayerComboBox


def classFactory(iface):
    return MinimalPlugin(iface)


class MinimalPlugin:
    def __init__(self, iface):
        self.iface = iface
        self.dlg = testPluginDialog()

    def initGui(self):
        self.action = QAction('Go!', self.iface.mainWindow())
        self.action.triggered.connect(self.showDialog)
        self.iface.addToolBarIcon(self.action)
        self.dlg.btn.clicked.connect(self.run)
        self.iface.mapCanvas().layersChanged.connect(self.populateComboBox)
        
    def unload(self):
        self.iface.removeToolBarIcon(self.action)
        del self.action
        
    def showDialog(self):
        self.dlg.show()
        self.populateComboBox()
        
    def populateComboBox(self):
        if self.dlg.isVisible():
            self.map_layers = QgsProject.instance().mapLayers().values()
            self.allow_list = [
                                lyr.id() for lyr in self.map_layers if lyr.type() == QgsMapLayerType.VectorLayer
                                and lyr.geometryType()== QgsWkbTypes.PointGeometry
                                and 'Vessel' in [f.name() for f in lyr.fields()]
                            ]
            self.except_list = [l for l in self.map_layers if l.id() not in self.allow_list]
            
            self.dlg.mMapLayerComboBox.setExceptedLayerList(self.except_list)
        
    def run(self):
        QMessageBox.information(None, 'Minimal plugin', self.dlg.mMapLayerComboBox.currentLayer().name())


class testPluginDialog(QDialog):
    def __init__(self):
        QDialog.__init__(self)
        self.setGeometry(150, 150, 500, 350)
        self.layout = QVBoxLayout()
        self.mMapLayerComboBox = QgsMapLayerComboBox(self)
        self.btn = QPushButton('Run', self)
        self.layout.addWidget(self.mMapLayerComboBox)
        self.layout.addWidget(self.btn)
        self.setLayout(self.layout)
2
  • 1
    this worked perfectly and does exactly what I needed. I write this next bit as much as a comment to other people answering questions as it is a Thanks to Ben. This sort of answer is really helpful to those of us with limited knowledge: I do appreciate that it takes longer to write . Being able to see either a working script (including imports) using the suggested answer, or at least the required code in context is much more educational than a one liner with 'use this code'. I'm sure in those cases the suggested answer is correct, I just struggle finding out how to implement it.
    – WillH
    Jun 21, 2022 at 14:17
  • 1
    @WillH, that's great! I'm really glad it worked for you and you found it helpful. Thanks for the positive feedback :-)
    – Ben W
    Jun 22, 2022 at 4:29

Your Answer

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

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