插件开发指南

第十五章 插件开发指南

15.1 插件开发概述

15.1.1 QGIS插件类型

类型 语言 特点
Python插件 Python 开发简单,大多数插件
C++插件 C++ 高性能,核心扩展

15.1.2 插件功能类别

  • 数据处理工具
  • 可视化增强
  • Web服务集成
  • 分析算法
  • 界面定制
  • 数据导入导出

15.2 开发环境准备

15.2.1 必要工具

  • QGIS(最新版本)
  • Python 3.x
  • Qt Designer
  • 文本编辑器/IDE
  • Plugin Builder插件

15.2.2 安装Plugin Builder

插件 > 管理和安装插件 > 搜索"Plugin Builder"

15.2.3 推荐IDE配置

VS Code配置

{
    "python.pythonPath": "/path/to/qgis/python",
    "python.autoComplete.extraPaths": [
        "/path/to/qgis/python",
        "/path/to/qgis/python/plugins"
    ]
}

15.3 插件结构

15.3.1 基本目录结构

my_plugin/
├── __init__.py           # 插件入口
├── metadata.txt          # 元数据
├── my_plugin.py          # 主模块
├── my_plugin_dialog.py   # 对话框类
├── my_plugin_dialog_base.ui  # UI文件
├── resources.qrc         # Qt资源
├── resources_rc.py       # 编译的资源
├── icon.png              # 插件图标
├── help/                 # 帮助文档
│   └── index.html
├── i18n/                 # 翻译文件
│   └── my_plugin_zh.ts
└── test/                 # 测试代码
    └── test_my_plugin.py

15.3.2 metadata.txt

[general]
name=My Plugin
qgisMinimumVersion=3.0
qgisMaximumVersion=3.99
description=This is my awesome plugin
version=1.0.0
author=Your Name
email=your.email@example.com

about=Detailed description of what this plugin does.
    Can span multiple lines.

tracker=https://github.com/user/repo/issues
repository=https://github.com/user/repo

tags=analysis,vector,tool

homepage=https://example.com/my-plugin
category=Plugins
icon=icon.png

experimental=False
deprecated=False

changelog=
    1.0.0 - Initial release
    0.9.0 - Beta version

15.3.3 init.py

def classFactory(iface):
    """加载插件类
    
    :param iface: QgisInterface实例
    :return: 插件实例
    """
    from .my_plugin import MyPlugin
    return MyPlugin(iface)

15.3.4 主插件类

# my_plugin.py
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction

from .resources import *
from .my_plugin_dialog import MyPluginDialog

import os.path


class MyPlugin:
    """QGIS插件实现"""
    
    def __init__(self, iface):
        """构造函数
        
        :param iface: QgisInterface实例
        """
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)
        
        # 初始化翻译
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir, 'i18n',
            f'MyPlugin_{locale}.qm'
        )
        
        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            QCoreApplication.installTranslator(self.translator)
        
        self.actions = []
        self.menu = self.tr('&My Plugin')
        self.toolbar = self.iface.addToolBar('MyPlugin')
        self.toolbar.setObjectName('MyPlugin')
    
    def tr(self, message):
        """获取翻译后的字符串"""
        return QCoreApplication.translate('MyPlugin', 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):
        """添加工具栏图标和菜单项"""
        
        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)
        
        if status_tip:
            action.setStatusTip(status_tip)
        if whats_this:
            action.setWhatsThis(whats_this)
        
        if add_to_toolbar:
            self.toolbar.addAction(action)
        if add_to_menu:
            self.iface.addPluginToMenu(self.menu, action)
        
        self.actions.append(action)
        return action
    
    def initGui(self):
        """初始化GUI"""
        icon_path = ':/plugins/my_plugin/icon.png'
        self.add_action(
            icon_path,
            text=self.tr('My Plugin'),
            callback=self.run,
            parent=self.iface.mainWindow()
        )
    
    def unload(self):
        """卸载插件"""
        for action in self.actions:
            self.iface.removePluginMenu(self.tr('&My Plugin'), action)
            self.iface.removeToolBarIcon(action)
        del self.toolbar
    
    def run(self):
        """运行插件"""
        dlg = MyPluginDialog()
        result = dlg.exec_()
        
        if result:
            # 执行操作
            pass

15.4 UI设计

15.4.1 使用Qt Designer

打开Qt Designer设计界面:

# Linux
designer

# Windows (OSGeo4W)
C:\OSGeo4W\bin\designer.exe

15.4.2 常用控件

控件 用途
按钮 QPushButton 触发操作
标签 QLabel 显示文本
文本框 QLineEdit 单行输入
文本区 QTextEdit 多行输入
下拉框 QComboBox 选择选项
复选框 QCheckBox 布尔选择
单选框 QRadioButton 互斥选择
数值框 QSpinBox 整数输入
滑块 QSlider 数值选择
列表 QListWidget 列表显示
表格 QTableWidget 表格显示

15.4.3 QGIS专用控件

from qgis.gui import (
    QgsMapLayerComboBox,      # 图层选择
    QgsFieldComboBox,          # 字段选择
    QgsFileWidget,             # 文件选择
    QgsProjectionSelectionWidget,  # CRS选择
    QgsColorButton,            # 颜色选择
    QgsSpinBox,                # 数值输入
    QgsDoubleSpinBox           # 浮点输入
)

15.4.4 对话框类

# my_plugin_dialog.py
from qgis.PyQt import uic
from qgis.PyQt.QtWidgets import QDialog

import os

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


class MyPluginDialog(QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        """构造函数"""
        super(MyPluginDialog, self).__init__(parent)
        self.setupUi(self)
        
        # 连接信号
        self.layerComboBox.layerChanged.connect(self.on_layer_changed)
        self.runButton.clicked.connect(self.on_run)
    
    def on_layer_changed(self, layer):
        """图层改变时更新字段列表"""
        self.fieldComboBox.setLayer(layer)
    
    def on_run(self):
        """执行操作"""
        layer = self.layerComboBox.currentLayer()
        field = self.fieldComboBox.currentField()
        # 处理逻辑
        self.accept()

15.5 资源管理

15.5.1 资源文件

<!-- resources.qrc -->
<!DOCTYPE RCC>
<RCC version="1.0">
    <qresource prefix="/plugins/my_plugin">
        <file>icon.png</file>
        <file>images/tool.png</file>
    </qresource>
</RCC>

15.5.2 编译资源

pyrcc5 -o resources_rc.py resources.qrc

15.5.3 使用资源

# 在代码中使用
from . import resources_rc  # 导入资源

icon_path = ':/plugins/my_plugin/icon.png'
icon = QIcon(icon_path)

15.6 国际化

15.6.1 标记可翻译字符串

# 使用self.tr()
message = self.tr("Hello World")

# 或直接使用QCoreApplication.translate
from qgis.PyQt.QtCore import QCoreApplication
message = QCoreApplication.translate("MyPlugin", "Hello World")

15.6.2 提取翻译

# 创建.ts文件
pylupdate5 -verbose my_plugin.py my_plugin_dialog.py -ts i18n/my_plugin_zh.ts

15.6.3 翻译

使用Qt Linguist编辑.ts文件:

linguist i18n/my_plugin_zh.ts

15.6.4 编译翻译

lrelease i18n/my_plugin_zh.ts

15.7 设置存储

15.7.1 使用QSettings

from qgis.PyQt.QtCore import QSettings

settings = QSettings()

# 保存设置
settings.setValue("my_plugin/last_path", "/path/to/file")
settings.setValue("my_plugin/buffer_distance", 100)

# 读取设置
last_path = settings.value("my_plugin/last_path", "")
distance = settings.value("my_plugin/buffer_distance", 50, type=int)

15.7.2 项目级设置

from qgis.core import QgsProject

project = QgsProject.instance()

# 保存到项目
project.writeEntry("my_plugin", "setting1", "value1")

# 从项目读取
value, ok = project.readEntry("my_plugin", "setting1", "default")

15.8 发布插件

15.8.1 准备发布

  1. 完善metadata.txt
  2. 添加帮助文档
  3. 添加图标
  4. 测试功能
  5. 检查代码质量

15.8.2 创建发布包

# 创建ZIP包
cd /path/to/plugins
zip -r my_plugin.zip my_plugin -x "*.pyc" -x "*__pycache__*"

15.8.3 上传到官方仓库

  1. 访问 https://plugins.qgis.org
  2. 注册/登录账户
  3. 上传插件包
  4. 等待审核

15.8.4 自托管

# 创建plugins.xml
<?xml version='1.0' encoding='UTF-8'?>
<plugins>
    <pyqgis_plugin name="My Plugin" version="1.0.0">
        <description>Plugin description</description>
        <version>1.0.0</version>
        <qgis_minimum_version>3.0</qgis_minimum_version>
        <download_url>https://example.com/my_plugin.zip</download_url>
    </pyqgis_plugin>
</plugins>

15.9 测试

15.9.1 单元测试

# test/test_my_plugin.py
import unittest
from my_plugin.my_plugin import MyPlugin

class TestMyPlugin(unittest.TestCase):
    def setUp(self):
        """测试前准备"""
        pass
    
    def test_calculation(self):
        """测试计算功能"""
        result = some_function(10, 20)
        self.assertEqual(result, 30)
    
    def tearDown(self):
        """测试后清理"""
        pass

if __name__ == '__main__':
    unittest.main()

15.9.2 运行测试

python -m pytest test/

15.10 最佳实践

15.10.1 代码风格

  • 遵循PEP 8
  • 使用类型提示
  • 添加文档字符串

15.10.2 错误处理

try:
    result = some_operation()
except Exception as e:
    QgsMessageLog.logMessage(str(e), "MyPlugin", Qgis.Warning)
    iface.messageBar().pushMessage("错误", str(e), level=Qgis.Critical)

15.10.3 性能考虑

  • 使用进度反馈
  • 避免阻塞UI
  • 考虑使用QgsTask

15.11 小结

本章详细介绍了QGIS插件开发:

关键要点

  1. 理解插件结构和组件
  2. 掌握UI设计方法
  3. 了解资源管理和国际化
  4. 掌握设置存储方式
  5. 了解发布和测试流程

插件开发是扩展QGIS功能的重要途径。


上一章第14章 Python开发与PyQGIS

下一章第16章 数据库集成

posted @ 2026-01-08 14:04  我才是银古  阅读(21)  评论(0)    收藏  举报