Een plug-in in Python bouwen (QGIS3)

Plug-ins zijn een fantastische manier om de functionaliteit van QGIS uit te breiden. U kunt plug-ins schrijven met behulp van Python die kunnen variëren van het toevoegen van een eenvoudige knop tot gesofisticeerde gereedschappen. Deze handleiding zal een overzicht van het proces geven voor het instellen van uw ontwikkelomgeving, de interface voor een plug-in ontwerpen en code schrijven voor interactie met QGIS. Bekijk de handleiding Beginnen met programmeren in Python (QGIS3) om bekend te raken met de basisbeginselen.

Notitie

Wanneer u een nieuwe plug-in bouwt, raad ik u sterk aan om een plug-in voor Processing te maken in plaats van de plug-in voor de GUI, die wordt beschreven in deze handleiding. Bekijk Een plug-in voor Processing bouwen (QGIS3) voor meer details.

Overzicht van de taak

We zullen een eenvoudige plug-in ontwikkelen, genaamd Attributen opslaan die gebruikers in staat zal stellen een vectorlaag uit te zoeken en de attributen daarvan weg te schrijven naar een CSV-bestand.

De gereedschappen ophalen

Qt Creator

Qt is een framewerk voor softwareontwikkeling dat wordt gebruikt om toepassingen te ontwikkelen die kunnen worden uitgevoerd op Windows, Mac, Linux als ook op verschillende mobiele besturingssystemen. QGIS zelf is geschreven met behulp van het framewerk Qt. Voor de ontwikkeling van plug-ins zullen we een toepassing gebruiken, genaamd Qt Creator om de interface voor onze plug-in te ontwerpen.

Download en installeer het installatieprogramma voor Qt Creator vanaf Qt Offline Installers. Zorg er voor dat u Qt Creator selecteert op de pagina voor downloaden. Merk op dat u een gratis account voor Qt moet maken om het pakket te kunnen installeren.

Notitie

Het installatieprogramma OSGeo4w voor QGIS op Windows bevat een kopie van het programma Qt Designer dat een lichtgewicht versie is van Qt Creator en perfect geschikt voor het bouwen van plug-ins. U zou het downloaden van Qt Creator kunnen overslaan en het in plaats daarvan gebruiken vanuit C:\OSGeo4W64\bin\qgis-designer.

../../_images/setup1.png

Python Bindings voor Qt

Omdat we de plug-in ontwikkelen in Python, moeten we de Python bindings voor Qt installeren. De methode voor het installeren hiervan is afhankelijk van het platform dat u gebruikt. Voor het bouwen van plug-ins hebben we het gereedschap voor de opdrachtregel pyrcc5 nodig.

Windows

Relevante bindings voor Python zijn opgenomen in de installatie van QGIS op Windows. We dienen het pad aan te geven in de installatie van QGIS om ze uit de map Plugin te kunnen gebruiken.

Maak een Windows Batch-bestand (extensie .bat) met de volgende inhoud en sla dat op uw computer op als compile.bat. We zullen dit bestand later naar de map Plugin kopiëren. Als u QGIS hebt geïnstalleerd in een ander pad, vervang dan C:\OSGeo4W64\bin\ door uw pad.

@echo off
call "C:\OSGeo4W64\bin\o4w_env.bat"
call "C:\OSGeo4W64\bin\qt5_env.bat"
call "C:\OSGeo4W64\bin\py3_env.bat"

@echo on
pyrcc5 -o resources.py resources.qrc
../../_images/setup2.png

Mac

Installeer de Homebrew pakketbeheerder. Installeer het pakket PyQt door de volgende opdracht uit te voeren:

brew install pyqt
Linux

Afhankelijk van uw distributie, zoek en installeer het pakket python-qt5. Op Ubuntu en Debian-gebaseerde distributies kunt u de volgende opdracht uitvoeren:

sudo apt-get install python-qt5

Notitie

Het zou kunnen zijn dat QGIS dit pakket al heeft geïnstalleerd.

Een tekstbewerker of een Python IDE

Elke soort softwareontwikkeling vereist een goede tekstbewerker. Als u al een favoriete tekstbewerker of een IDE (Integrated Development Environment) heeft, zou u die kunnen gebruiken voor deze handleiding. Anders biedt elk platform een grote variëteit van gratis of betaalde opties voor tekstbewerkers. Kies er een die aan uw wensen voldoet.

Deze handleiding gebruikt de bewerker Notepad++ op Windows.

Windows

Notepad++ is een goede gratis tekstbewerker voor Windows. Download en installeer de tekstbewerker Notepad++.

Notitie

Indien u Notepad++ gebruikt, zorg er dan voor dat u gaat naar :menuselection:`Instellingen --> Voorkeuren --> Tabs`:guilabel: en Omzetten in spaties inschakelt. Python is bijzonder gevoelig voor witruimte en deze instelling zal er voor zorgen dat tabs en spaties op de juiste manier worden behandeld.

plug-in Plugin Builder

Er is een nuttige plug-in voor QGIS, genaamd Plugin Builder die alle noodzakelijke bestanden en onderliggende code voor een plug-in maakt. Zoek en installeer de plug-in Plugin Builder. Bekijk Plug-ins gebruiken voor meer details over hoe plug-ins te installeren.

plug-in Plugins Reloader

Dit is een andere nuttige hulpplug-in die het iteratief ontwikkelen van plug-ins mogelijk maakt. Met behulp van deze plug-in kunt u de code voor uw plug-in wijzigen en die hebben gereflecteerd in QGIS zonder dat u QGIS elke keer opnieuw moet starten. Zoek en installeer de plug-in Plugin Reloader. Bekijk Plug-ins gebruiken voor meer details over hoe plug-ins te installeren.

Notitie

Plugin Reloader is een experimentele plug-in. Zorg er voor dat u ook Ook de experimentele plugins tonen in de instellingen van Plug-ins installeren en beheren te selecteren, als u hem niet kunt vinden.

Procedure

  1. Open QGIS. Ga naar Plug-ins ‣ Plugin Builder ‣ Plugin Builder.

../../_images/120.png
  1. U zult het dialoogvenster zien van QGIS Plugin Builder met een formulier. U kunt het formulier vullen met details met betrekking tot onze plug-in. De Class name zal de naam zijn van de Python Class die de logica van de plug-in bevat. Dit zal ook de naam zijn van de map die alle bestanden van de plug-in bevat. Voer SaveAttributes in als de naam van de klasse. De Plugin name is de naam waarmee uw plug-in zal verschijnen in Plug-ins beheren en installeren. Voer de naam in als Attributen opslaan. Voeg een beschrijving toe in het veld Description. De Module name zal de naam zijn van het belangrijkste Python-bestand voor de plug-in. Voer die in als save_attributes. Laat de versienummers zoals zij zijn en voer uw naam en e-mailadres in in de toepasselijke velden. Klik op Next.

../../_images/218.png
  1. Voer een korte beschrijving van de plug-in in voor het dialoogvenster About en klik op Next.

../../_images/316.png
  1. Selecteer Tool button with dialog uit de Template selector`. De waarde Text for menu item is hoe gebruikers uw plug-in zullen zien in het menu van QGIS. Voer het in als Attributen opslaan als CSV. Het veld Menu zal bepalen waar uw plug-in zal worden toegevoegd in QGIS. Selecteer Vector omdat onze plug-in bestemd is voor vectorgegevens. Klik op Next.

../../_images/45.png
  1. Plugin builder zal u vragen naar het type van de te genereren bestanden. Behoud de standaard selectie en klik op Next.

../../_images/55.png
  1. U kunt, omdat we niet van plan zijn om onze plug-in te publiceren, de waarden van Bug tracker, Repository en Home page op de standaard laten staan. Selecteer het vak Flag the plugin as experimental aan de onderzijde en klik op Next.

../../_images/65.png
  1. U zult worden gevraagd een map op te geven voor uw plug-in. Sla die, voor nu, op in een map die u gemakkelijk kunt vinden op uw computer en klik op Generate.

../../_images/75.png
  1. Druk vervolgens op de knop generate. U zult een dialoogvenster ter bevestiging zien als de sjabloon voor uw plug-in eenmaal is gemaakt.

../../_images/85.png

Notitie

U zou een bericht kunnen krijgen dat zegt dat pyrcc5 niet in het pad is gevonden. U kunt dit bericht negeren.

  1. Voordat we de nieuw gemaakte plug-in kunnen gebruiken, dienen we het bestand resources.qrc te compileren dat werd gemaakt door Plugin Builder. Dit bestand is deel van het Qt Resource System wat verwijst naar alle binaire bestanden die in de plug-in worden gebruikt. Voor deze plug-in, zal het alleen het p;ictogram voor de plug-in hebben. Compileren van dit bestand genereert code voor de toepassing die kan worden gebruikt in de plug-in, onafhankelijk van op welk platform de plug-in wordt uitgevoerd. Volg de platformspecifieke instructie voor deze stap.

Windows

U kunt nu het bestand compile.bat kopiëren (gemaakt in het gedeelte Python Bindings voor Qt in het begin) naar de map Plugin. Eenmaal gekopieerd, dubbelklik op het bestand om het uit te voeren. Als de uitvoering met succes werd voltooid zult u nu een nieuw bestand, genaamd resources.py, in de map zien staan.

../../_images/95.png

Notitie

Als deze stap mislukt kunt u cmd.exe starten en bladeren naar de map Plugin met behulp van de opdracht cd. Voer het Batch-bestand uit door compile.bat uit te voeren om de fout te kunnen zien.

Mac en Linux

U zult eerst het gereedschap pb_tool moeten installeren. Open een Terminal en installeer het via pip.

sudo pip3 install pb_tool

Open een Terminal en ga naar de plugin directory en typ pb_tool compile. Dit zal de opdracht pyrcc5 uitvoeren die we hadden geïnstalleerd als deel van het gedeelte Python Bindings voor Qt.

pb_tool compile
  1. Plugins worden in QGIS opgeslagen in een speciale map. We moeten onze plugin directory naar die map kopiëren voor we die kunnen gebruiken. Zoek, in QGIS, de map voor uw huidige profiel door te gaan naar Extra ‣ Gebruikersprofielen ‣ Actieve profielmap openen.

../../_images/108.png
  1. Kopieer, in de profielmap, de map Plugin naar de submap python ‣ plugins.

../../_images/1111.png
  1. Nu zijn we klaar om een eerste blik te werpen op de brandnieuwe plug-in die we hebben gemaakt. Sluit QGIS en start het opnieuw. Ga naar Plug-ins ‣ Plug-ins beheren en installeren en schakel de plug-in Attributen opslaan in op de tab Geïnstalleerd.

../../_images/128.png
  1. U zult zien dat er een nieuw pictogram op de werkbalk staat en een nieuw menuitem onder Vector ‣ Attributen opslaan ‣ Attributen opslaan als CSV`. Selecteer die om het dialoogvenster voor de plug-in te starten.

../../_images/137.png
  1. U zult een nieuw blanco dialoogvenster zien, genaamd Attributen opslaan. Sluit dit dialoogvenster.

../../_images/147.png
  1. We zullen nu ons nieuw dialoogvenster ontwerpen en enkele elementen voor de gebruikersinterface er aan toevoegen. Open het programma Qt Creator en ga naar :menuselection::File –> Open File or Project.

../../_images/157.png
  1. Blader naar de map met de plug-in en selecteer het bestand save_attributes_dialog_base.ui file. Klik op Openen.

../../_images/16.gif

Notitie

Windows verbergt de map AppData, dus u zou die niet hoeven te zien in het dialoogvenster voor het selecteren van bestanden. U kunt AppData invoeren bij de prompt voor File name vanuit de map erboven om het te openen.

  1. U zult een blanco dialoogvenster uit de plug-in zien. U kunt met slepen en neerzetten elementen verplaatsen vanuit het paneel aan de linkerkant naar het dialoogvenster. We zullen een type Combo Box van Input Widgets toevoegen. Sleep het naar het dialoogvenster van de plug-in.

../../_images/177.png
  1. Maak het combinatievak op maat en pas de grootte aan. Sleep nu een type Label van Display Widget in het dialoogvenster.

../../_images/185.png
  1. Klik op de labeltekst en voer in Selecteer een laag.

../../_images/195.png
  1. Sla dit bestand op door te gaan naar File ‣ Save save_attributes_dialog_base.ui. Onthoud dat de naam van het object combinatievak comboBox is. We zullen er met die naam naar moeten verwijzen om interacties uit te kunnen voeren met behulp van code voor Python.

../../_images/205.png
  1. Laten we onze plug-in opnieuw laden zodat we de wijzigingen in het venster van het dialoogvenster kunnen zien. Ga naar Plug-ins ‣ Plugin Reloader ‣ Choose a plugin to be reloaded. Selecteer SaveAttributes in het dialoogvenster Configure Plugin reloader.

../../_images/219.png
  1. Klik op de knop Reload plugin om de laatste versie van de plug-in te laden. Klik nu op de knop Attributen opslaan als CSV. U zult het nieuw ontworpen dialoogvenster zien.

../../_images/225.png
  1. Laten we enige logica toevoegen aan de plug-in die het combinatievak zal vullen met de in QGIS geladen lagen. Ga naar de map van de plug-in en laadt het bestand save_attributes.py in een tekstbewerker. Als eerste, voeg aan het begin van het bestand in met de andere imports:

    from qgis.core import QgsProject
    

    Scroll dan naar beneden en zoek naar de methode run(self). Deze methode zal worden aangeroepen wanneer u op de knop van de werkbalk klikt of het menuitem van de plug-in selecteert. Voeg de volgende code toe aan het begin van de methode. Deze code haalt de geladen lagen in QGIS op en voegt die toe aan het object comboBox van het dialoogvenster van de plug-in.

    # Fetch the currently loaded layers
    layers = QgsProject.instance().layerTreeRoot().children()
    # Clear the contents of the comboBox from previous runs
    self.dlg.comboBox.clear()
    # Populate the comboBox with names of all the loaded layers
    self.dlg.comboBox.addItems([layer.name() for layer in layers])
    
../../_images/23a.png ../../_images/23b.png
  1. Laadt, terug in het hoofdvenster van QGIS, de plug-in opnieuw door te klikken op de knop Reload plugin. We moeten enkele lagen in QGIS laden om deze functionaliteit te testen. Nadat u enkele lagen hebt geladen, start de plug-in door te gaan naar Vector ‣ Attributen opslaan ‣ Attributen oposlaan als CSV. U zult nu zien dat ons combinatievak is gevuld met de laagnamen die zijn geladen in QGIS.

../../_images/245.png
  1. Laten we de resterende elementen van de gebruikersinterface toevoegen. Schakel terug naar Qt Creator en laadt het bestand save_attributes_dialog_base.ui. Voeg een Label Display Widget toe en wijzig de tekst naar Uitvoerbestand selecteren. Voeg een type LineEdit van Input Widget toe dat het pad naar het uitvoerbestand laat zien dat de gebruiker heeft gekozen. Voeg vervolgens een type Push Button van Button toe en wijzig het label van de knop naar .... Onthoud de objectnamen van de widgets die we moeten gebruiken om er interacties mee uit te kunnen voeren. Sla het bestand op.

../../_images/255.png
  1. We zullen nu code voor Python toevoegen om een dialoogvenster voor bestandsselectie te openen als de gebruiker op de knop ... drukt en het geselecteerde pad weergeven in het widget line edit. Open het bestand save_attributes.py in een tekstbewerker. Voeg QFileDialog toe aan de lijst QtWidgets voor import aan het begin van het bestand.

../../_images/264.png
  1. Voeg een nieuwe methode, genaamd select_output_file toe, met de volgende code. Deze code zal een dialoogvenster voor bestandsselectie openen en het widget line edit vullen met het pad van het bestand dat de gebruiker heeft gekozen. Merk op hoe getSaveFileName een tuple teruggeeft met de bestandsnaam en het gebruikte filter.

def select_output_file(self):
  filename, _filter = QFileDialog.getSaveFileName(
    self.dlg, "Select   output file ","", '*.csv')
  self.dlg.lineEdit.setText(filename)
../../_images/275.png
  1. Nu moeten we code toevoegen zodat, als op de knop wordt geklikt, de methode select_output_file wordt aangeroepen. Scroll naar beneden naar de methode run en voeg de volgende regel toe in het blok waar het dialoogvenster wordt geïnitialiseerd. Deze code zal de methode select_output_file verbinden met het signal clicked van de knop van het widget.

self.dlg.pushButton.clicked.connect(self.select_output_file)
../../_images/285.png
  1. Laadt, terug in QGIS, de plug-in opnieuw en voer die uit. Als alles goed ging kunt u nu op de knop ... drukken en een uitvoer tekstbestand op uw schijf selecteren.

../../_images/294.png
  1. Als u op OK klikt in het dialoogvenster van de plug-in, gebeurt er niets. Dat komt omdat we de logica om de informatie over de attributen uit de laag op te halen en weg te schrijven naar het tekstbestand nog niet hebben toegevoegd. We hebben nu alle stukken op hun plaats om nu precies dat te gaan doen. Zoek naar de plek in de methode run waar pass staat. Vervang dat door de code die hieronder staat. De verklaring van deze code kan worden gevonden in Beginnen met programmeren in Python (QGIS3).

    filename = self.dlg.lineEdit.text()
    with open(filename, 'w') as output_file:
      selectedLayerIndex = self.dlg.comboBox.currentIndex()
      selectedLayer = layers[selectedLayerIndex].layer()
      fieldnames = [field.name() for field in selectedLayer.fields()]
      # write header
      line = ','.join(name for name in fieldnames) + '\n'
      output_file.write(line)
      # write feature attributes
      for f in selectedLayer.getFeatures():
        line = ','.join(str(f[name]) for name in fieldnames) + '\n'
        output_file.write(line)
    
    ../../_images/304.png
  2. We moeten nog één laatste ding toevoegen. Als de bewerking met succes wordt uitgevoerd, zouden we hetzelfde moeten aangeven aan de gebruiker. De voorkeur voor het geven van notificaties aan de gebruiker in QGIS is via de methode self.iface.messageBar().pushMessage(). Voeg Qgis toe aan de lijst van imports qgis.core aan het begin van het bestand en voeg de hieronder staande code toe aan het einde van de methode run.

self.iface.messageBar().pushMessage(
  "Success", "Output file written at " + filename,
  level=Qgis.Success, duration=3)
../../_images/31a.png ../../_images/31b.png
  1. Nu is onze plug-in klaar. Herlaad de plug-in en probeer hem uit. U zult zien dat het uitvoer tekstbestand dat u kiest de attributen uit de vectorlaag zal bevatten.

../../_images/323.png
  1. U kunt de map voor de plug-in zippen en delen met uw gebruikers. Zij kunnen de inhoud uitpakken naar hun eigen map voor de plug-in en uw plug-in uitproberen. Als dit een echte plug-in zou zijn zou u die uploaden naar de QGIS Plugin Repository zodat alle gebruikers van QGIS hem kunnen vinden en uw plug-in kunnen downloaden.

Notitie

Deze plug-in is alleen ter demonstratie. Publiceer deze plug-in niet en upload hem ook niet naar de opslagplaats van plug-ins voor QGIS.

Hieronder staat, als referentie, het volledige bestand save_attributes.py.

# -*- coding: utf-8 -*-
"""
/***************************************************************************
 SaveAttributes
                                 A QGIS plugin
 This plugin saves the attributes of the selected vector layer as a CSV file.
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2019-03-28
        git sha              : $Format:%H$
        copyright            : (C) 2019 by Ujaval Gandhi
        email                : ujaval@spatialthoughts.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from PyQt5.QtCore import QSettings, QTranslator, qVersion, QCoreApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QFileDialog
from qgis.core import QgsProject, Qgis

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .save_attributes_dialog import SaveAttributesDialog
import os.path


class SaveAttributes:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'SaveAttributes_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)

            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Save Attributes')

        # Check if plugin was started the first time in current QGIS session
        # Must be set in initGui() to survive plugin reloads
        self.first_start = None

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('SaveAttributes', message)


    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToVectorMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/save_attributes/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'Save Attributes as CSV'),
            callback=self.run,
            parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True


    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginVectorMenu(
                self.tr(u'&Save Attributes'),
                action)
            self.iface.removeToolBarIcon(action)

    def select_output_file(self):
      filename, _filter = QFileDialog.getSaveFileName(
        self.dlg, "Select   output file ","", '*.csv')
      self.dlg.lineEdit.setText(filename)
      
    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 = SaveAttributesDialog()
            self.dlg.pushButton.clicked.connect(self.select_output_file)

        
        # Fetch the currently loaded layers
        layers = QgsProject.instance().layerTreeRoot().children()
        # Clear the contents of the comboBox and lineEdit from previous runs
        self.dlg.comboBox.clear()
        self.dlg.lineEdit.clear()

        # Populate the comboBox with names of all the loaded layers
        self.dlg.comboBox.addItems([layer.name() for layer in layers])
        
        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
          filename = self.dlg.lineEdit.text()
          with open(filename, 'w') as output_file:
            selectedLayerIndex = self.dlg.comboBox.currentIndex()
            selectedLayer = layers[selectedLayerIndex].layer()
            fieldnames = [field.name() for field in selectedLayer.fields()]
            # write header
            line = ','.join(name for name in fieldnames) + '\n'
            output_file.write(line)
            # write feature attributes
            for f in selectedLayer.getFeatures():
              line = ','.join(str(f[name]) for name in fieldnames) + '\n'
              output_file.write(line)
          self.iface.messageBar().pushMessage(
            "Success", "Output file written at " + filename,
            level=Qgis.Success, duration=3)

If you want to give feedback or share your experience with this tutorial, please comment below. (requires GitHub account)