0

I am creating a QGIS Plugin, in which the user can rename columns to specfied names depending on a certain policy. In the image you can see, that the user selects a layer in QGIS which field names will then populate a mFieldCombobox ('actual name') and in a second Combobox the user is provided with a list of names, defined in the plugin (and depending on the chosen policy).

However, the amount of columns in the layer, which should be renamed, depends on the needs of the user and so I want him/her to be able to add as much comboboxes ('actual name' and 'rename to') as needed. For this I added the '+'-button, which, when pressed, should generate a new 'actual name'-mFieldCombobox and a new 'rename to'-combobox.

Is this possible, and if so, how? Any help would be very welcome!

My code so far:

    def run(self):
        """Run method that performs all the real work"""

        # Create the dialog with elements (after translation) and keep reference
        # Only create GUI ONCE in callback, so that it will only load when the plugin is started
        if self.first_start == True:
            self.first_start = False
            self.dlg = ReformatToLKNamesDialog()

            # Populate Richtlinien Combobox mit Richtliniennamen
            self.populateRichtinie()

            # Get value from Richtlinie
            self.selected_RL = self.dlg.Richtlinie.currentText()
            self.dlg.Richtlinie.currentTextChanged.connect(self.populateRLS19)

            # Update the combobox with the field names
            self.dlg.LayerName.layerChanged.connect(self.updateFields)

        # show the dialog
        self.dlg.show()

        # Run the dialog event loop
        result = self.dlg.exec_()
        print(type(result))
        # See if OK was pressed
        if result:
            # Write here your code which should run when you hit the button 'OK'
            pass

    def printRL(self):
        self.selected_RL = self.dlg.Richtlinie.currentText()
        if self.selected_RL:
            print(self.dlg.Richtlinie.currentText())

    def updateFields(self):
        self.dlg.mFieldComboBox.clear()
        selectedLayer = self.dlg.LayerName.currentLayer()
        if selectedLayer:
            self.dlg.mFieldComboBox.setLayer(selectedLayer)
   
    def populateRichtinie(self):
        richtlinien = [''] # may be added: 'All'
        print(richtlinien)
        self.dlg.Richtlinie.addItems([item for item in richtlinien])


    def populateRLS19(self):
        self.dlg.LKName.clear()
        selectedRichtlinie = self.dlg.Richtlinie.currentText()
        rlkurz = {'st':'sr', 'b':'ra', 'ls':'la'}
        selctedRL_kurzel = rlkurz[selectedRichtlinie]
        print(selctedRL_kurzel)
        rl_kuerzel = [
'list', 'of', 'names']

        rl_f = [selctedRL_kurzel+kuerzel for kuerzel in rl_kuerzel]
        self.dlg.LKName.addItems([item for item in rl_f])

enter image description here

4
  • 2
    Maybe consider using a QTableWidget to hold the QComboBox. Something like stackoverflow.com/questions/46079378/…
    – user2856
    Commented Aug 15 at 1:56
  • Thank you, I will have a look into it!
    – i.i.k.
    Commented Aug 15 at 7:24
  • By the way, if renaming fields is the only function you need, you can use the Refactor field tool. Ceating a plugin for this is overkill. docs.qgis.org/testing/en/docs/user_manual/processing_algs/qgis/… Commented Aug 21 at 8:36
  • Thanks for the input @ComradeChe ! However, the names for renaming are predefined, so chosen from a list ... and the PlugIn exists already :) Further, it was a good exercise as I will need to create more PlugIns.
    – i.i.k.
    Commented Aug 21 at 9:25

1 Answer 1

2

Here is a simple example of using a QTableWidget in a QGIS Python plugin, a button to add rows to the table and using the setCellWidget() method to insert and populate QgsFieldComboBoxes and QComboboxes into the table cells.

This is a trivial dummy plugin I created to roughly mimic your use case. I hope it will also show you an alternative plugin structure to that created by Plugin Builder. This example is based more on a minimal plugin structure. Incidentally, if you haven't seen this Minimalist Plugin Skeleton GitHub repo of Martin Dobias, I urge you to have a look. It will show you just how simple a plugin can be and may de-mystify the structure a bit, especially if you are struggling to understand the code generated by Plugin Builder.

This is my .ui file in Qt Designer:

ui file in Qt Designer

You can see I have added an empty QTableWidget as well as a QgsMapLayerComboBox and a couple of buttons.

Now we add most of the gui logic into the _dialog.py file. Here you can also set icons to buttons etc. The PyQGIS Icon Cheatsheet is a great resource for finding existing icons available in all QGIS installations which you can use in your plugins.

import os

from qgis.PyQt import uic

from qgis.PyQt.QtWidgets import (QDialog, QComboBox, QTableWidgetItem)
from qgis.PyQt.QtGui import (QIcon)

from qgis.core import QgsMapLayerProxyModel
from qgis.gui import QgsFieldComboBox


FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'table_widget_plugin_dialog_base.ui'))

class TableWidgetPluginDialog(QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        super(TableWidgetPluginDialog, self).__init__(parent)
        self.setupUi(self)
        self.setMinimumWidth(500)
        #########################################################
        self.name_options = ['Name_1', 'Name_2', 'Name_3', 'Name_4', 'Name_5']
        self.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.VectorLayer)
        self.tableWidget.setColumnCount(2)
        self.tableWidget.setHorizontalHeaderLabels(['Field Name', 'Rename To'])
        self.tableWidget.horizontalHeader().setMinimumSectionSize(200)
        
        self.addButton.setText('')
        self.addButton.setIcon(QIcon(":images/themes/default/symbologyAdd.svg"))
        
        self.add_table_row()
        self.addButton.clicked.connect(self.add_table_row)
        self.resetButton.clicked.connect(self.reset_table)
        self.mMapLayerComboBox.layerChanged.connect(self.reset_table)
        
    def reset_table(self):
        self.tableWidget.setRowCount(0)
        self.add_table_row()
        
    def add_table_row(self):
        i = self.tableWidget.rowCount()
        self.tableWidget.setRowCount(i+1)
        fcb = QgsFieldComboBox(self.tableWidget)
        fcb.setLayer(self.mMapLayerComboBox.currentLayer())
        ncb = QComboBox(self.tableWidget)
        ncb.addItems(self.name_options)
        self.tableWidget.setItem(i, 0, QTableWidgetItem())
        self.tableWidget.setItem(i, 1, QTableWidgetItem())
        self.tableWidget.setCellWidget(i, 0, fcb)
        self.tableWidget.setCellWidget(i, 1, ncb)
        self.tableWidget.resizeColumnsToContents()

Here is how the _plugin.py file looks. Again it is fairly simple. Note that the instance of the dialog class is defined in the constructor, and any signal/slot connections are set up inside the initGui() method. This way, there is no need to bother with creating and manipulating any self.first_start attribute (which is created by Plugin Builder).

from qgis.PyQt.QtWidgets import QToolBar, QAction
from qgis.PyQt.QtGui import QIcon

from .table_widget_plugin_dialog import TableWidgetPluginDialog


class TableWidgetPlugin:

    def __init__(self, iface):
        self.iface = iface
        self.main_window = self.iface.mainWindow()
        self.dlg = TableWidgetPluginDialog()
        self.toolbar = self.main_window.findChild(QToolBar, 'mPluginToolBar')
        self.action = QAction(QIcon(":images/themes/default/mActionEditTable.svg"), '', self.main_window)
        
    def initGui(self):
        '''This method is where we add the plugin action to the plugin toolbar.
        This is also where we connect any signals and slots
       such as Push Buttons to our class methods which contain our plugin logic.'''
        self.action.setObjectName('mActionTestPlugin')
        self.action.setToolTip('Rename layer fields')
        self.toolbar.addAction(self.action)
        # Show the plugin dialog when the toolbar action is triggered
        self.action.triggered.connect(lambda: self.dlg.show())
        # Connect the accepted signal of the button box (OK button clicked) to a method
        self.dlg.button_box.accepted.connect(self.retrieve_field_names)
        # Close the dialog when the Cancel button is clicked
        self.dlg.button_box.rejected.connect(lambda: self.dlg.close())
    
    def retrieve_field_names(self):
        '''Retrieve the values from the dialog widgets for further processing'''
        field_name_map = {}
        layer = self.dlg.mMapLayerComboBox.currentLayer()
        for i in range(self.dlg.tableWidget.rowCount()):
            field_name = self.dlg.tableWidget.cellWidget(i, 0).currentField()
            new_name = self.dlg.tableWidget.cellWidget(i, 1).currentText()
            field_name_map[field_name] = new_name
        self.iface.messageBar().pushMessage(repr(field_name_map))
            
    def unload(self):
        self.toolbar.removeAction(self.action)
        del self.action

Short screencast showing adding rows with cell widget comboboxes to the table:

enter image description here

If you want to install this plugin to have a play around with it you can download the zip file from the GitHub repo.

2
  • Thank you so much! Also the tip with Github Repo from Martin Dobias is helpful, I will definively look into it. For now, I chose to no interactive comboboxes, but instead create a lis of all fields. However, as I will need to create more plugIns I will possibly try out the solution!
    – i.i.k.
    Commented Aug 21 at 10:00
  • 1
    Fantastic answer
    – user2856
    Commented Aug 23 at 6:25

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.