I want to add an input parameter in a a QGIS processing plugin that let the user choose among all the values in a layer field (or at least provide autocomplete). However, I could not find any QgsProcessingParameterDefinition
that can do this.
I am thinking in something like:
self.addParameter(
QgsProcessingParameter<value>(
'wwtp_id',
self.tr('Id of the node containing the WWTP'),
parentLayerParameterName=self.NODES,
allowMultiple=False
)
)
Also, it should be type agnostic because the id can be an integer or a string, depending on the NODES layer provided by the user.
1 Answer 1
You could create a custom PyQt widget and use it your processing script with a processing widget wrapper.
In the example below, I have created a custom widget class which includes a QgsMapLayerComboBox
, a QgsFieldComboBox
, plus a generic QComboBox
, which displays all the values in the current selected field. You can filter the layer and field combo boxes to show only certain layer/field types.
If you have a potentially very large number of values, you can make the combo box editable and use a QCompleter
so that you can filter the items by typing into the editable combo box.
from processing.gui.wrappers import WidgetWrapper
from qgis.PyQt.QtCore import (QCoreApplication,
QVariant,
Qt)
from qgis.PyQt.QtWidgets import (QLabel,
QWidget,
QGridLayout,
QComboBox,
QCompleter)
from qgis.core import (QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingParameterMatrix,
QgsMapLayerProxyModel,
QgsFieldProxyModel)
from qgis.gui import (QgsMapLayerComboBox,
QgsFieldComboBox)
class AddLayoutTable(QgsProcessingAlgorithm):
INPUT_PARAMS = 'INPUT_PARAMS'
def __init__(self):
super().__init__()
def name(self):
return "widgetwrapperdemo"
def displayName(self):
return "Widget Wrapper Demo"
def group(self):
return "General"
def groupId(self):
return "general"
def shortHelpString(self):
return "Example of using a custom widget wrapper."
def helpUrl(self):
return "https://qgis.org"
def createInstance(self):
return type(self)()
def initAlgorithm(self, config=None):
test_param = QgsProcessingParameterMatrix(self.INPUT_PARAMS, 'Input Parameters')
test_param.setMetadata({'widget_wrapper': {'class': CustomParametersWidget}})
self.addParameter(test_param)
def processAlgorithm(self, parameters, context, feedback):
# Retrieve the list of parameters returned by the custom widget wrapper
input_params_list = self.parameterAsMatrix(parameters, 'INPUT_PARAMS', context)
# Access the list items to retrieve each parameter object
node_lyr = input_params_list[0]
node_fld = input_params_list[1]
# Check whether Type of input field is String or Integer
if node_lyr.fields()[node_lyr.fields().lookupField(node_fld)].type() == QVariant.String:
node_id = input_params_list[2]
elif node_lyr.fields()[node_lyr.fields().lookupField(node_fld)].isNumeric():
node_id = int(input_params_list[2])
else:
node_id = 'None'
node_feats = [ft for ft in node_lyr.getFeatures() if ft[node_fld] == node_id]
for feat in node_feats:
continue
# Do something with feat
# Just for demonstration we can return the input parameters to check their values...
return {'Input Node Layer': node_lyr,
'Node Field': node_fld,
'Node ID': node_id,
'Node Features': node_feats}
# Widget Wrapper class
class CustomParametersWidget(WidgetWrapper):
def createWidget(self):
self.cpw = CustomWidget()
return self.cpw
def value(self):
# This method gets the parameter values and returns them in a list...
# which will be retrieved and parsed in the processAlgorithm() method
self.lyr = self.cpw.getLayer()
self.fld = self.cpw.getField()
self.node_id = self.cpw.getFieldValue()
return [self.lyr, self.fld, self.node_id]
# Custom Widget class
class CustomWidget(QWidget):
def __init__(self):
super(CustomWidget, self).__init__()
self.setGeometry(500, 300, 500, 300)
self.lyr_lbl = QLabel('Input Nodes Layer', self)
self.lyr_cb = QgsMapLayerComboBox(self)
self.fld_lbl = QLabel('Node ID Field')
self.fld_cb = QgsFieldComboBox(self)
self.id_lbl = QLabel('Node ID')
self.id_cb = QComboBox(self)
# Make combo box editable
self.id_cb.setEditable(True)
self.layout = QGridLayout(self)
self.layout.addWidget(self.lyr_lbl, 0, 0, 1, 1, Qt.AlignRight)
self.layout.addWidget(self.lyr_cb, 0, 1, 1, 2)
self.layout.addWidget(self.fld_lbl, 1, 0, 1, 1, Qt.AlignRight)
self.layout.addWidget(self.fld_cb, 1, 1, 1, 2)
self.layout.addWidget(self.id_lbl, 2, 0, 1, 1, Qt.AlignRight)
self.layout.addWidget(self.id_cb, 2, 1, 1, 2)
# Set filter on the map layer combobox (here we show only point layers)
self.lyr_cb.setFilters(QgsMapLayerProxyModel.PointLayer)
self.fld_cb.setLayer(self.lyr_cb.currentLayer())
# Set filters on field combobox (here we show only string and integer fields)
self.fld_cb.setFilters(QgsFieldProxyModel.Int | QgsFieldProxyModel.LongLong | QgsFieldProxyModel.String)
self.lyr_cb.layerChanged.connect(self.layerChanged)
self.completer = QCompleter([])
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
# Default is Qt.MatchStartsWith... (change if you want)
self.completer.setFilterMode(Qt.MatchContains)
self.id_cb.setCompleter(self.completer)
self.populateComboBox()
self.fld_cb.fieldChanged.connect(self.populateComboBox)
def layerChanged(self):
self.fld_cb.setLayer(self.lyr_cb.currentLayer())
self.updateCompleter()
def populateComboBox(self):
'''Populate the combo box with all unique values in the selected field'''
self.id_cb.clear()
node_lyr = self.lyr_cb.currentLayer()
id_fld = self.fld_cb.currentField()
fld_idx = node_lyr.fields().lookupField(id_fld)
id_vals = node_lyr.uniqueValues(fld_idx)
self.id_cb.addItems([str(val) for val in id_vals])
self.updateCompleter()
def updateCompleter(self):
node_lyr = self.lyr_cb.currentLayer()
id_fld = self.fld_cb.currentField()
fld_idx = node_lyr.fields().lookupField(id_fld)
id_vals = node_lyr.uniqueValues(fld_idx)
self.completer.model().setStringList([str(val) for val in id_vals])
def getLayer(self):
return self.lyr_cb.currentLayer()
def getField(self):
return self.fld_cb.currentField()
def getFieldValue(self):
return self.id_cb.currentText()
The resulting processing script UI looks like this:
You can find a blog post from Faunalia about using custom widgets in processing scripts here:
-
I marked as correct because it works and it is what I asked. However, I was hoping for a more straightforward solution. Also for text input with autocomplete rather than a list (a normal layer can have 15 thousands nodes).Josep Pueyo– Josep Pueyo2023年07月05日 12:34:15 +00:00Commented Jul 5, 2023 at 12:34
-
1@Josep Pueyo, you can use
QCompleter
with aQComboBox
. I will update answer tomorrow.Ben W– Ben W2023年07月05日 13:31:33 +00:00Commented Jul 5, 2023 at 13:31 -
1@Josep Pueyo, answer updated to include completion/filtering of combo box items.Ben W– Ben W2023年07月05日 23:22:44 +00:00Commented Jul 5, 2023 at 23:22
Explore related questions
See similar questions with these tags.