PyQGIS · Material de apoyo para la clase de Programación en Ambientes de SIG (PASIG)

Ejercicio 9: Mi primer complemento de QGIS

En los ejercicios anteriores creaste herramientas de procesamiento (un solo archivo .py). Este ejercicio es distinto: es un complemento (plugin) completo, una carpeta con varios archivos que agrega un botón y un menú a QGIS y abre una ventana propia. El complemento se llama "Complemento de prueba" y fue generado con Plugin Builder siguiendo los pasos de la página Fundamentos de complementos.

Plugin Builder escribe casi todo el código por ti. Por eso, lo más importante al estudiar este ejercicio es distinguir qué es código generado automáticamente y qué partes personalizó el creador. En los bloques de código de esta página esas partes aparecen destacadas:

  • Franja amarilla · Personalizado por el creador. Datos que el creador escribió en el formulario de Plugin Builder: nombres, descripción, autoría, texto del menú, etc.
  • Franja azul · Modificado después de generar la plantilla. Cambios hechos a mano sobre los archivos que Plugin Builder produjo (por ejemplo, un elemento agregado en Qt Designer).

Nota: las franjas se identificaron comparando los archivos con la plantilla estándar que genera Plugin Builder (tipo Tool button with dialog). Todo lo que no tiene franja es código de la plantilla que no fue alterado.

Conceptos clave de POO, PyQGIS y PyQt usados en el ejercicio

  • Una clase que no hereda de una clase de QGIS: class ComplementoPrueba: no tiene nada entre paréntesis. QGIS no exige herencia aquí; solo exige que la clase tenga los métodos initGui y unload. Es un ejemplo de que en POO una clase puede cumplir un "contrato" sin heredar de otra.
  • Herencia múltiple: class ComplementoPruebaDialog(QtWidgets.QDialog, FORM_CLASS) hereda de dos clases a la vez: QDialog (una ventana de diálogo de Qt) y FORM_CLASS (una clase generada en el momento a partir del archivo .ui).
  • Constructor y super(): el método __init__ prepara el objeto al crearlo, y super().__init__(parent) ejecuta primero el constructor de la clase padre.
  • Atributos de instancia: self.iface, self.actions, self.menu, self.first_start y self.dlg son datos que cada objeto del complemento guarda para usarlos en sus distintos métodos.
  • Señales y callbacks: action.triggered.connect(callback) conecta la señal triggered (el usuario presionó el botón) con una función que se ejecutará en ese momento. Se pasa la función sin paréntesis, como referencia.
  • El objeto iface: es la conexión con la ventana de QGIS. Con él el complemento agrega y quita su botón y su menú.
  • Interfaz diseñada de forma visual: la ventana no se programa línea por línea; se dibuja en Qt Designer y queda guardada en un archivo .ui (XML) que Python carga con uic.loadUiType.

Los archivos del complemento

La carpeta complemento_pruebas contiene muchos archivos, pero solo estos 5 forman el funcionamiento esencial y se analizan en esta página:

  • __init__.py: punto de entrada; QGIS lo busca primero.
  • metadata.txt: ficha del complemento (nombre, descripción, versión, autoría...).
  • complemento_pruebas.py: la clase principal con la lógica del complemento.
  • complemento_pruebas_dialog.py: la clase de la ventana.
  • complemento_pruebas_dialog_base.ui: el diseño de la ventana (XML de Qt Designer).

El archivo pb_tool.cfg (configuración para desplegar el complemento) se revisa al final. El resto de la carpeta lo generó Plugin Builder y no se modificó:

  • icon.png: icono del botón.
  • Makefile, pylintrc, scripts/: automatizan compilación, revisión de estilo y traducciones.
  • help/ e i18n/: plantillas para documentación y traducciones.
  • test/: pruebas automáticas de ejemplo.
  • README.txt y README.html: instrucciones de lo que sigue después de generar el complemento.

Análisis y explicación de la estructura y lógica del código

Sección 1 · __init__.py: el punto de entrada

Cuando QGIS inicia, revisa la carpeta de complementos y abre el archivo __init__.py de cada uno. Este archivo solo tiene una función, pero es indispensable.

__init__.py
# -*- coding: utf-8 -*-
"""
/***************************************************************************
 ComplementoPrueba
                                 A QGIS plugin
 Este complemento es para aprender como crear mi primer complemento
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2026-09-11
        copyright            : (C) 2026 by UAEMéx
        email                : complementos_qgis@uaemex.mx
        git sha              : $Format:%H$
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
 This script initializes the plugin, making it known to QGIS.
"""


# noinspection PyPep8Naming
def classFactory(iface):  # pylint: disable=invalid-name
    """Load ComplementoPrueba class from file ComplementoPrueba.

    :param iface: A QGIS interface instance.
    :type iface: QgsInterface
    """
    #
    from .complemento_pruebas import ComplementoPrueba
    return ComplementoPrueba(iface)
  • def classFactory(iface):: QGIS busca una función con este nombre exacto y la llama una sola vez al cargar el complemento. "Factory" (fábrica) significa que su trabajo es fabricar el objeto del complemento. Recibe iface, la conexión con QGIS.
  • from .complemento_pruebas import ComplementoPrueba: importa la clase principal. El punto antes del nombre (.complemento_pruebas) es una importación relativa: significa "el archivo complemento_pruebas.py de esta misma carpeta". La importación está dentro de la función para que el código del complemento solo se cargue cuando QGIS lo pide.
  • return ComplementoPrueba(iface): crea el objeto del complemento, le entrega iface y lo devuelve a QGIS.
  • Personalizado: el nombre de la clase (ComplementoPrueba) y el del módulo (complemento_pruebas) son los valores Class name y Module name que el creador escribió en el formulario de Plugin Builder, igual que la descripción y la autoría del encabezado.

Sección 2 · metadata.txt: la ficha del complemento

No es código Python: es un archivo de texto con pares clave=valor. QGIS lo lee para mostrar el complemento en el Administrador de complementos, sin ejecutar nada de Python todavía.

metadata.txt
# This file contains metadata for your plugin.

# This file should be included when you package your plugin.# Mandatory items:

[general]
name=Complemento de prueba
qgisMinimumVersion=3.0
description=Este complemento es para aprender como crear mi primer complemento
version=0.1
author=UAEMéx
email=complementos_qgis@uaemex.mx

about=El propósito de este complemento es aprender a usar el Plugin Builder.

tracker=https://www.uaemex.mx
repository=https://www.uaemex.mx
# End of mandatory metadata

# Recommended items:

hasProcessingProvider=no
# Uncomment the following line and add your changelog:
# changelog=

# Tags are comma separated with spaces allowed
tags=analysis, raster

homepage=https://www.uaemex.mx
category=Plugins
icon=icon.png
# experimental flag
experimental=True

# deprecated flag (applies to the whole plugin, not just a single version)
deprecated=False

# Since QGIS 3.8, a comma separated list of plugins to be installed
# (or upgraded) can be specified.
# Check the documentation for more information.
# plugin_dependencies=

Category of the plugin: Raster, Vector, Database or Web
# category=

# If the plugin can run on QGIS Server.
server=False
  • [general]: sección del archivo. Todos los datos están dentro de ella.
  • name, description, about: nombre, resumen y explicación larga que se ven en el Administrador de complementos. Personalizados por el creador.
  • author, email: datos de autoría. Personalizados.
  • tracker, repository, homepage: direcciones web (reporte de errores, código fuente y página del proyecto). Personalizadas.
  • tags: palabras clave (analysis, raster) que ayudan a encontrar el complemento en las búsquedas. Personalizadas.
  • qgisMinimumVersion=3.0: versión mínima de QGIS compatible. Viene de la plantilla.
  • version=0.1: versión del complemento. Valor inicial de la plantilla.
  • hasProcessingProvider=no: indica que el complemento no agrega herramientas a la Caja de herramientas de procesos (a diferencia de los ejercicios 1 a 8).
  • icon=icon.png: archivo que se usa como icono.
  • experimental=True: marca el complemento como experimental.
  • deprecated=False, server=False: no está obsoleto y no está pensado para QGIS Server.
  • Las líneas que inician con # son comentarios con instrucciones de la plantilla; QGIS las ignora.

Sección 3 · complemento_pruebas.py: la clase principal

Este es el archivo con la clase del complemento. Primero se muestra completo y después se explica por partes: importaciones, constructor y cada método.

complemento_pruebas.py
# -*- coding: utf-8 -*-
"""
/***************************************************************************
 ComplementoPrueba
                                 A QGIS plugin
 Este complemento es para aprender como crear mi primer complemento
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2026-09-11
        git sha              : $Format:%H$
        copyright            : (C) 2026 by UAEMéx
        email                : complementos_qgis@uaemex.mx
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 qgis.PyQt.QtCore import QLocale, QTranslator, QCoreApplication
from qgis.core import QgsSettings
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction

# Import the code for the dialog
from .complemento_pruebas_dialog import ComplementoPruebaDialog
import os.path


class ComplementoPrueba:
    """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 = QgsSettings().value('locale/userLocale', QLocale().name())[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            '{}.qm'.format(locale))

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

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Complemento de prueba')

        # 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('ComplementoPrueba', 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.addPluginToMenu(
                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 = os.path.join(os.path.dirname(__file__), 'icon.png')
        self.add_action(
            icon_path,
            text=self.tr(u'Mi complemento'),
            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.removePluginMenu(
                self.tr(u'&Complemento de prueba'),
                action)
            self.iface.removeToolBarIcon(action)


    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 = ComplementoPruebaDialog()

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            # Do something useful here - delete the line containing pass and
            # substitute with your code.
            pass

3.1 · Para qué sirve cada biblioteca importada

complemento_pruebas.py (fragmento)
from qgis.PyQt.QtCore import QLocale, QTranslator, QCoreApplication
from qgis.core import QgsSettings
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction

# Import the code for the dialog
from .complemento_pruebas_dialog import ComplementoPruebaDialog
import os.path
  • from qgis.PyQt.QtCore import QLocale, QTranslator, QCoreApplication: tres clases de Qt (qgis.PyQt es el "puente" por el que QGIS ofrece PyQt a los complementos).
    • QLocale: conoce el idioma y la región del sistema (por ejemplo, es_MX).
    • QTranslator: carga un archivo de traducciones (.qm) para mostrar los textos en otro idioma.
    • QCoreApplication: representa la aplicación en ejecución; sirve para instalar el traductor y para traducir textos con translate().
  • from qgis.core import QgsSettings: permite leer la configuración del usuario guardada por QGIS; aquí, el idioma elegido en las opciones del programa.
  • from qgis.PyQt.QtGui import QIcon: clase para cargar un icono desde un archivo de imagen.
  • from qgis.PyQt.QtWidgets import QAction: una acción de Qt: un elemento que puede aparecer como botón de una barra de herramientas y como opción de un menú, y que avisa cuando el usuario lo presiona.
  • from .complemento_pruebas_dialog import ComplementoPruebaDialog: importa, con importación relativa, la clase de la ventana que se define en el archivo complemento_pruebas_dialog.py. Personalizado: el nombre sale de lo que se escribió en el formulario.
  • import os.path: funciones para trabajar con rutas de archivos (unir carpetas, saber si un archivo existe, etc.).

3.2 · La clase y su constructor __init__

complemento_pruebas.py (fragmento)
class ComplementoPrueba:
    """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 = QgsSettings().value('locale/userLocale', QLocale().name())[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            '{}.qm'.format(locale))

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

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Complemento de prueba')

        # 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
  • class ComplementoPrueba:: declara la clase del complemento. Personalizado: el nombre es el Class name del formulario.
  • def __init__(self, iface):: constructor; se ejecuta cuando classFactory crea el objeto. Recibe iface.
  • self.iface = iface: guarda la conexión con QGIS como atributo para usarla en los demás métodos.
  • self.plugin_dir = os.path.dirname(__file__): guarda la carpeta donde está el complemento. __file__ es la ruta de este archivo.
  • locale = QgsSettings().value('locale/userLocale', QLocale().name())[0:2]: lee el idioma del usuario (si no hay uno configurado, usa el del sistema) y se queda con sus dos primeras letras, por ejemplo es.
  • locale_path = os.path.join(self.plugin_dir, 'i18n', '{}.qm'.format(locale)): arma la ruta del archivo de traducción, por ejemplo .../i18n/es.qm.
  • if os.path.exists(locale_path):: solo si ese archivo existe se crea un QTranslator, se carga y se instala. En este ejercicio no existe es.qm (la carpeta i18n solo tiene un archivo de ejemplo), así que el complemento se muestra en español sin traducir nada.
  • self.actions = []: lista vacía donde se guardarán las acciones creadas, para poder quitarlas después en unload.
  • self.menu = self.tr(u'&Complemento de prueba'): nombre del menú del complemento. El & marca la letra que funciona como atajo de teclado. Personalizado: es el nombre del complemento escrito en el formulario.
  • self.first_start = None: bandera que indica si es la primera vez que se usa el complemento en la sesión. Se pone en True en initGui y se usa en run.

3.3 · Para qué sirve el método tr

complemento_pruebas.py (fragmento)
    # 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('ComplementoPrueba', message)
  • def tr(self, message):: devuelve la traducción de un texto. Si hay un traductor instalado y el texto está traducido, devuelve la versión traducida; si no, devuelve el texto original.
  • La clase implementa su propio tr porque no hereda de QObject (que ya trae uno). Por eso llama a QCoreApplication.translate('ComplementoPrueba', message), indicando el "contexto" (el nombre de la clase) en el que se busca la traducción.
  • Todos los textos visibles se escriben dentro de self.tr(...) para que el complemento pueda traducirse en el futuro.

3.4 · Para qué sirve el método add_action

complemento_pruebas.py (fragmento)
    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):
complemento_pruebas.py (fragmento)
        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.addPluginToMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action
  • def add_action(self, icon_path, text, callback, ...): método auxiliar que crea un botón/opción de menú y lo agrega a QGIS. Tiene parámetros con valor por defecto (enabled_flag=True, add_to_menu=True...), por lo que al llamarlo solo se indican los indispensables. El docstring que omitimos aquí describe cada parámetro.
  • icon = QIcon(icon_path): carga la imagen del botón.
  • action = QAction(icon, text, parent): crea la acción con su icono, su texto y su ventana "padre".
  • action.triggered.connect(callback): conecta la señal con la función. Al presionar el botón se ejecuta callback (en este complemento, el método run).
  • action.setEnabled(enabled_flag): activa o desactiva la acción.
  • action.setStatusTip(...) y action.setWhatsThis(...): textos de ayuda opcionales; solo se asignan si se proporcionaron (is not None).
  • self.iface.addToolBarIcon(action): agrega el botón a la barra de herramientas de complementos de QGIS.
  • self.iface.addPluginToMenu(self.menu, action): agrega la opción al menú Complementos, dentro del submenú con el nombre guardado en self.menu.
  • self.actions.append(action) y return action: guarda la acción en la lista (para quitarla después) y la devuelve.

3.5 · Para qué sirve el método initGui

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

        icon_path = os.path.join(os.path.dirname(__file__), 'icon.png')
        self.add_action(
            icon_path,
            text=self.tr(u'Mi complemento'),
            callback=self.run,
            parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True
  • def initGui(self):: QGIS lo llama automáticamente al activar el complemento. Su trabajo es construir los elementos visibles del complemento en la interfaz de QGIS.
  • icon_path = os.path.join(os.path.dirname(__file__), 'icon.png'): ruta del icono, que está en la misma carpeta que este archivo.
  • self.add_action(icon_path, text=self.tr(u'Mi complemento'), callback=self.run, parent=self.iface.mainWindow()): crea el botón y la opción de menú. callback=self.run pasa el método run sin paréntesis: se ejecutará más tarde, cuando se presione el botón. parent es la ventana principal de QGIS. Personalizado: 'Mi complemento' es el texto del menú escrito en el formulario.
  • self.first_start = True: marca que la ventana todavía no se ha creado.

3.6 · Para qué sirve el método unload

complemento_pruebas.py (fragmento)
    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(
                self.tr(u'&Complemento de prueba'),
                action)
            self.iface.removeToolBarIcon(action)
  • def unload(self):: QGIS lo llama al desactivar el complemento (o al recargarlo con Plugin Reloader). Hace lo contrario de initGui.
  • for action in self.actions:: recorre las acciones guardadas en la lista.
  • self.iface.removePluginMenu(...): elimina la opción del menú. Personalizado: repite el nombre del menú; debe ser idéntico al de self.menu, de lo contrario la opción no se elimina.
  • self.iface.removeToolBarIcon(action): elimina el botón de la barra de herramientas. Sin este método quedarían botones "huérfanos" tras desactivar el complemento.

3.7 · Para qué sirve el método run

complemento_pruebas.py (fragmento)
    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 = ComplementoPruebaDialog()

        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            # Do something useful here - delete the line containing pass and
            # substitute with your code.
            pass
  • def run(self):: se ejecuta cada vez que el usuario presiona el botón del complemento. Es el lugar donde debe programarse lo que el complemento hace.
  • if self.first_start == True:: solo la primera vez se crea la ventana, self.dlg = ComplementoPruebaDialog(), y la bandera pasa a False. Así la ventana se crea una sola vez y se reutiliza.
  • self.dlg.show(): muestra la ventana.
  • result = self.dlg.exec_(): ejecuta la ventana como diálogo modal: el programa espera aquí hasta que el usuario la cierre. Devuelve un valor verdadero si presionó OK y falso si presionó Cancel.
  • if result:: solo si se presionó OK se entra al bloque.
  • pass: instrucción que no hace nada. Aquí termina lo que generó la plantilla: el creador todavía no escribió la lógica propia del complemento. Es el punto de partida para agregar la lógica propia: aquí iría el código que aproveche lo que el usuario eligió en la ventana.

Por ejemplo (ejemplo ilustrativo, no forma parte de los archivos del ejercicio), así se podría leer la casilla de la ventana después de presionar OK:

Ejemplo ilustrativo
        if result:
            if self.dlg.checkBox.isChecked():
                self.iface.messageBar().pushInfo('Complemento de prueba', 'La casilla estaba marcada')

Sección 4 · complemento_pruebas_dialog.py: la clase de la ventana

complemento_pruebas_dialog.py
# -*- coding: utf-8 -*-
"""
/***************************************************************************
 ComplementoPruebaDialog
                                 A QGIS plugin
 Este complemento es para aprender como crear mi primer complemento
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                             -------------------
        begin                : 2026-09-11
        git sha              : $Format:%H$
        copyright            : (C) 2026 by UAEMéx
        email                : complementos_qgis@uaemex.mx
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import os

from qgis.PyQt import uic
from qgis.PyQt import QtWidgets

# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(
    os.path.join(
        os.path.dirname(__file__),
        'complemento_pruebas_dialog_base.ui'),
    from_imports=True,
    import_from='complemento_pruebas')


class ComplementoPruebaDialog(QtWidgets.QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """Constructor."""
        super(ComplementoPruebaDialog, self).__init__(parent)
        # Set up the user interface from Designer through FORM_CLASS.
        # After self.setupUi() you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

4.1 · Para qué sirve cada biblioteca importada

  • import os: para construir la ruta del archivo .ui sin importar el sistema operativo.
  • from qgis.PyQt import uic: el módulo uic (user interface compiler) sabe leer un archivo .ui y convertirlo en una clase de Python mientras el programa se ejecuta.
  • from qgis.PyQt import QtWidgets: contiene los elementos visuales de Qt; de aquí se toma QDialog, la clase base de las ventanas de diálogo.

4.2 · Cargar el diseño y declarar la clase

  • FORM_CLASS, _ = uic.loadUiType(...): lee el archivo complemento_pruebas_dialog_base.ui y devuelve dos valores; el primero, FORM_CLASS, es una clase que ya sabe construir todos los elementos del diseño. El segundo no se necesita y se descarta con el nombre _. Personalizado: el nombre del archivo .ui y import_from='complemento_pruebas' dependen del nombre del módulo escrito en el formulario.
  • os.path.join(os.path.dirname(__file__), '...'): ruta del .ui, que está en la misma carpeta.
  • class ComplementoPruebaDialog(QtWidgets.QDialog, FORM_CLASS):: herencia múltiple. Es una ventana de diálogo (QDialog) que además incluye todo el diseño del .ui (FORM_CLASS).
  • def __init__(self, parent=None):: constructor; parent es la ventana "padre" y es opcional.
  • super(ComplementoPruebaDialog, self).__init__(parent): ejecuta el constructor de las clases padre.
  • self.setupUi(self): método que viene de FORM_CLASS; construye los elementos visuales en la ventana. Después de esta línea, cada elemento del diseño puede usarse como atributo: self.button_box, self.checkBox.

Sección 5 · complemento_pruebas_dialog_base.ui: el diseño de la ventana

Este archivo lo crea y edita Qt Designer; es XML, aunque rara vez se escribe a mano. Describe qué elementos tiene la ventana, dónde están y cómo se conectan.

complemento_pruebas_dialog_base.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>ComplementoPruebaDialogBase</class>
 <widget class="QDialog" name="ComplementoPruebaDialogBase">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>398</width>
    <height>293</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Complemento de prueba</string>
  </property>
  <widget class="QDialogButtonBox" name="button_box">
   <property name="geometry">
    <rect>
     <x>30</x>
     <y>240</y>
     <width>341</width>
     <height>32</height>
    </rect>
   </property>
   <property name="orientation">
    <enum>Qt::Horizontal</enum>
   </property>
   <property name="standardButtons">
    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
   </property>
  </widget>
  <widget class="QCheckBox" name="checkBox">
   <property name="geometry">
    <rect>
     <x>170</x>
     <y>90</y>
     <width>64</width>
     <height>18</height>
    </rect>
   </property>
   <property name="text">
    <string>CheckBox</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>button_box</sender>
   <signal>accepted()</signal>
   <receiver>ComplementoPruebaDialogBase</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>20</x>
     <y>20</y>
    </hint>
    <hint type="destinationlabel">
     <x>20</x>
     <y>20</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>button_box</sender>
   <signal>rejected()</signal>
   <receiver>ComplementoPruebaDialogBase</receiver>
   <slot>reject()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>20</x>
     <y>20</y>
    </hint>
    <hint type="destinationlabel">
     <x>20</x>
     <y>20</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>
  • <widget class="QDialog" name="ComplementoPruebaDialogBase">: la ventana principal. geometry define su tamaño (398 × 293 píxeles) y windowTitle el título (personalizado: "Complemento de prueba").
  • <widget class="QDialogButtonBox" name="button_box">: la pareja de botones OK y Cancel (standardButtons). Viene de la plantilla.
  • <widget class="QCheckBox" name="checkBox">: una casilla de verificación. Fue agregada por el creador en Qt Designer (franja azul): la plantilla solo trae los botones. Conserva el nombre (checkBox) y el texto (CheckBox) que Qt Designer asigna por defecto, lo que indica que es un elemento de práctica. Gracias a setupUi, en Python se accede a ella como self.dlg.checkBox.
  • <connections>: conexiones de señales ya hechas en el diseño. accepted() de button_box (se presionó OK) llama al método accept() de la ventana, y rejected() (se presionó Cancel) llama a reject(). Por eso la ventana se cierra y exec_() devuelve verdadero o falso, sin escribir código.

Sección 6 · pb_tool.cfg: configuración para desplegar

Lo usa la herramienta pb_tool (paso 5 de los pasos generales) para copiar el complemento a la carpeta de complementos de QGIS con pb_tool deploy.

pb_tool.cfg
#/***************************************************************************
# ComplementoPrueba
#
# Configuration file for plugin builder tool (pb_tool)
# Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
#                            -------------------
#       begin               : 2026-09-11
#       copyright           : (C) 2026 by UAEMéx
#       email               : complementos_qgis@uaemex.mx
# ***************************************************************************/
#
#/***************************************************************************
# *                                                                         *
# *   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.                                   *
# *                                                                         *
# ***************************************************************************/
# 
# 
# You can install pb_tool using:
#  pip install http://geoapt.net/files/pb_tool.zip
# 
# Consider doing your development (and install of pb_tool) in a virtualenv.
# 
# For details on setting up and using pb_tool, see:
#  http://g-sherman.github.io/plugin_build_tool/
#
# Issues and pull requests here:
# https://github.com/g-sherman/plugin_build_tool:
# 
# Sane defaults for your plugin generated by the Plugin Builder are
# already set below.
# 
# As you add Python source files and UI files to your plugin, add
# them to the appropriate [files] section below.

[plugin]
# Name of the plugin. This is the name of the directory that will
# be created in .qgis2/python/plugins
name: complemento_pruebas

# Full path to where you want your plugin directory copied. If empty,
# the QGIS default path will be used. Don't include the plugin name in
# the path.
plugin_path: C:/Users/rico_/AppData/Roaming/QGIS/QGIS3/profiles/default/python/plugins

[files]
# Python  files that should be deployed with the plugin
python_files: __init__.py complemento_pruebas.py complemento_pruebas_dialog.py

# The main dialog file that is loaded (not compiled)
main_dialog: complemento_pruebas_dialog_base.ui

# Other ui files for dialogs you create (these will be compiled)
compiled_ui_files: 

# resource_files:
resource_files:

# Other files required for the plugin
extras: metadata.txt icon.png

# Other directories to be deployed with the plugin.
# These must be subdirectories under the plugin directory
extra_dirs:

# ISO code(s) for any locales (translations), separated by spaces.
# Corresponding .ts files must exist in the i18n directory
locales:

[help]
# the built help directory that should be deployed with the plugin
dir: help/build/html
# the name of the directory to target in the deployed plugin 
target: help


  • [plugin] · name: nombre de la carpeta que se creará al desplegar. Personalizado (módulo del formulario).
  • plugin_path: carpeta de complementos del perfil de QGIS del usuario; es la ruta destino del despliegue.
  • [files] · python_files: archivos .py que se copian. Si se agrega un archivo nuevo al complemento, hay que añadirlo aquí. Personalizado (sus nombres salen del formulario).
  • main_dialog: el archivo .ui principal.
  • extras: otros archivos necesarios, como metadata.txt e icon.png.

Resumen: lo que personalizó el creador

  • Franja amarilla (formulario de Plugin Builder): el nombre de la clase y del módulo, el nombre del complemento ("Complemento de prueba"), el texto del menú ("Mi complemento"), la descripción, la autoría (UAEMéx), el correo, las direcciones web y las etiquetas. Aparecen repetidos en varios archivos porque Plugin Builder los copia a cada uno.
  • Franja azul (cambio a mano): la casilla checkBox agregada en Qt Designer.
  • Sin modificar: el método run conserva el pass de la plantilla, de modo que el complemento muestra su ventana pero todavía no realiza ninguna tarea.
Pon a prueba lo analizado
Volver a PASIG II