52_Building a Python Plugin QGIS3
原文链接: https://www.qgistutorials.com/en/docs/3/building_a_python_plugin.html
构建 Python 插件 (QGIS3)¶
插件是扩展 QGIS 功能的绝佳方式。您可以使用 Python 编写插件,从添加一个简单的按钮到复杂的工具包都可以。本教程将概述设置开发环境、设计插件用户界面以及编写与 QGIS 交互的代码所涉及的过程。请先阅读 Python 编程入门 (QGIS3) 教程以熟悉基础知识。
注意
如果您正在构建一个新插件,我强烈建议构建一个处理(Processing)插件,而不是本教程中描述的 GUI 插件。详情请参阅 构建处理插件 (QGIS3)。
任务概述¶
我们将开发一个名为 Save Attributes 的简单插件,允许用户选择一个矢量图层并将其属性写入 CSV 文件。
获取工具¶
Qt Creator¶
Qt 是一个软件开发框架,用于开发在 Windows、Mac、Linux 以及各种移动操作系统上运行的应用程序。QGIS 本身是使用 Qt 框架编写的。对于插件开发,我们将使用一个名为 Qt Creator 的应用程序来设计插件的界面。
从 Qt 离线安装程序 下载并安装 Qt Creator 安装程序。请确保您在下载页面上选择了 Qt Creator。请注意,您需要创建一个免费的 Qt 账户来安装该软件包。
注意
Windows 上的 QGIS OSGeo4w 安装程序包含一个 Qt Designer 程序,它是 Qt Creator 的轻量级版本,完全适合构建插件。您可以跳过下载 Qt Creator,转而使用 C:\OSGeo4W64\bin\qgis-designer 中的 Qt Designer。

Qt 的 Python 绑定¶
由于我们使用 Python 开发插件,因此需要安装 Qt 的 Python 绑定。安装这些绑定的方法取决于您所使用的平台。构建插件需要 pyrcc5 命令行工具。
Windows
相关的 Python 绑定已包含在 Windows 的 QGIS 安装中。但要从插件文件夹中使用它们,我们需要指明 QGIS 安装的路径。
创建一个包含以下内容的 Windows 批处理文件(扩展名为 .bat),并将其保存在您的计算机上,命名为 compile.bat。我们稍后将此文件复制到插件文件夹。如果您将 QGIS 安装在其他路径,请将 C:\OSGeo4W64\bin\ 替换为您的路径。
@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

Mac
安装 Homebrew 包管理器。通过运行以下命令安装 PyQt 包:
brew install pyqt
Linux
: 根据您的发行版,查找并安装 python-qt5 包。在 Ubuntu 和基于 Debian 的发行版上,可以运行以下命令:
sudo apt-get install python-qt5
注意
您可能会发现 QGIS 已经安装了这个包。
文本编辑器或 Python IDE¶
任何类型的软件开发都需要一个好的文本编辑器。如果您已经有一个喜欢的文本编辑器或 IDE(集成开发环境),您可以在本教程中使用它。否则,每个平台都提供各种各样免费或付费的文本编辑器选项。请选择适合您需求的编辑器。
本教程在 Windows 上使用 Notepad++ 编辑器。
Windows
Notepad++ 是 Windows 上一个很好的免费编辑器。 下载并安装 Notepad++ 编辑器。
注意
如果您使用 Notepad++,请确保进入 设置 ‣ 首选项 ‣ Tab 设置 并启用“用空格替换”。Python 对空格非常敏感,此设置将确保制表符和空格得到正确处理。
Plugin Builder 插件¶
有一个名为 Plugin Builder 的实用 QGIS 插件,它可以为插件创建所有必要的文件和样板代码。查找并安装 Plugin Builder 插件。有关如何安装插件的更多详细信息,请参阅 使用插件。
Plugins Reloader 插件¶
这是另一个辅助插件,它支持插件的迭代开发。使用此插件,您可以更改插件代码,并使其在 QGIS 中生效,而无需每次都重启 QGIS。查找并安装 Plugin Reloader 插件。有关如何安装插件的更多详细信息,请参阅 使用插件。
注意
Plugin Reloader 是一个实验性插件。如果找不到它,请确保已在插件管理器设置中勾选“同时显示实验性插件”。
操作步骤¶
- 打开 QGIS。转到 插件 ‣ Plugin Builder ‣ Plugin Builder。
- 您将看到带有表单的 QGIS Plugin Builder 对话框。您可以在表单中填写与我们插件相关的详细信息。类名将是包含插件逻辑的 Python 类的名称。这也将是包含所有插件文件的文件夹的名称。输入
SaveAttributes作为类名。插件名称是您的插件在插件管理器中显示的名称。输入名称Save Attributes。在描述字段中添加描述。模块名称将是插件主 python 文件的名称。将其输入为save_attributes。版本号保持默认,并在相应字段中输入您的姓名和电子邮件地址。点击下一步。
- 输入插件的简短描述(用于“关于”对话框),然后单击下一步。
- 从模板选择器中选择
Tool button with dialog。菜单项文本值将是用户在 QGIS 菜单中找到您插件的方式。输入Save Attributes as CSV。菜单字段将决定您的插件项添加到 QGIS 中的哪个位置。由于我们的插件用于矢量数据,请选择Vector。点击下一步。
- 插件构建器将提示您选择要生成的文件类型。保持默认选择,点击下一步。
- 由于我们不打算发布此插件,您可以将 Bug 跟踪器、仓库和主页的值保留为默认值。勾选底部的“将插件标记为实验性”框,然后单击下一步。
- 系统将提示您为插件选择一个目录。暂时将其保存到您计算机上易于查找的目录,然后单击生成。
- 接下来,按生成按钮。您的插件模板创建成功后,您将看到一个确认对话框。
注意
您可能会收到提示,说在路径中找不到 pyrcc5。您可以忽略此消息。
- 在我们能够使用新创建的插件之前,我们需要编译由 Plugin Builder 创建的
resources.qrc文件。该文件是 Qt 资源系统 的一部分,它引用了插件中使用的所有二进制文件。对于此插件,它将只包含插件图标。编译此文件会生成应用程序代码,该代码可在插件中独立于其运行的平台使用。请按照此步骤的特定平台说明进行操作。
Windows
您现在可以将 compile.bat 文件(在本教程开头的 Python Bindings for Qt 部分创建)复制到插件文件夹。复制后,双击该文件运行。如果运行成功,您将在文件夹中看到一个名为 resources.py 的新文件。
注意
如果此步骤失败,您可以启动 cmd.exe 并使用 cd 命令浏览到插件文件夹。运行批处理文件 compile.bat 查看错误信息。
Mac 和 Linux
您需要先安装 pb_tool。打开终端并通过 pip 安装它。
bash sudo pip3 install pb_tool
打开终端,进入插件目录,输入 pb_tool compile。这将运行我们在 Python Bindings for Qt 部分安装的 pyrcc5 命令。
bash pb_tool compile
- QGIS 中的插件存储在一个特殊的文件夹中。我们必须先将插件目录复制到该文件夹,然后才能使用它。在 QGIS 中,通过转到 设置 ‣ 用户配置文件 ‣ 打开活动配置文件文件夹 来定位您当前的配置文件文件夹。
- 在配置文件文件夹中,将插件文件夹复制到 python ‣ plugins 子文件夹。
- 现在我们准备第一次查看我们创建的全新插件。关闭 QGIS 并重新启动它。转到 插件 ‣ 管理和安装插件,然后在已安装选项卡中启用
Save Attributes插件。
- 您会注意到插件工具栏中出现了一个新图标,并且 Vector ‣ Save Attributes ‣ Save Attributes as CSV` 下出现了一个新的菜单项。选择它以启动插件对话框。
- 您将看到一个名为 Save Attributes 的新空白对话框。关闭此对话框。
- 我们现在将设计我们的对话框,并向其中添加一些用户界面元素。打开
Qt Creator程序,转到 文件 ‣ 打开文件或项目。
- 浏览到插件目录,选择
save_attributes_dialog_base.ui文件。点击打开。
注意
Windows 隐藏了 AppData 文件夹,因此您可能在文件选择器对话框中看不到它。您可以在其父目录的文件名提示中输入 AppData 来打开它。
- 您将看到插件中的空白对话框。您可以将左侧面板中的元素拖放到对话框上。我们将添加一个 输入部件 类型的 组合框。将其拖到插件对话框上。
- 调整组合框的大小。现在将一个 标签 类型的显示部件拖到对话框上。
- 单击标签文本并输入
Select a layer。
- 通过转到 文件 ‣ 保存 save_attributes_dialog_base.ui 来保存此文件。注意组合框对象的名称是
comboBox。要使用 python 代码与此对象交互,我们必须通过此名称来引用它。
- 让我们重新加载我们的插件,以便我们可以在对话框窗口中看到更改。转到 插件 ‣ Plugin Reloader ‣ Choose a plugin to be reloaded。在配置插件重新加载器对话框中选择
SaveAttributes。
- 点击重新加载插件按钮以加载最新版本的插件。点击保存属性为CSV按钮打开新设计的对话框。
-
让我们为插件添加一些逻辑,用 QGIS 中加载的图层填充组合框。转到插件目录并在文本编辑器中加载文件
save_attributes.py。首先,在文件顶部的其他导入语句处添加:bash from qgis.core import QgsProject然后向下滚动到末尾,找到
run(self)方法。当您单击工具栏按钮或选择插件菜单项时,将调用此方法。在方法开头添加以下代码。此代码获取 QGIS 中加载的图层并将其添加到插件对话框的comboBox对象中。```bash
获取当前加载的图层
layers = QgsProject.instance().layerTreeRoot().children()
清除先前运行后 comboBox 的内容
self.dlg.comboBox.clear()
使用所有加载图层的名称填充 comboBox
self.dlg.comboBox.addItems([layer.name() for layer in layers]) ```
- 回到主 QGIS 窗口,点击重新加载插件按钮重新加载插件。要测试此新功能,我们必须在 QGIS 中加载一些图层。加载一些图层后,通过转到 矢量 ‣ Save Attributes ‣ Save Attributes as CSV 启动插件。您将看到我们的组合框现在填充了 QGIS 中加载的图层名称。
- 让我们添加剩余的用户界面元素。切换回 Qt Creator 并加载
save_attributes_dialog_base.ui文件。添加一个 标签 显示部件,并将文本更改为Select output file。添加一个 行编辑 类型的输入部件,它将显示用户选择的输出文件路径。接下来,添加一个 按钮 类型的 推送按钮,并将按钮标签更改为...。注意我们需要与之交互的部件的对象名称。保存文件。
- 我们现在将添加 python 代码,以便当用户点击
...按钮时打开文件浏览器,并在行编辑部件中显示所选路径。在文本编辑器中打开save_attributes.py文件。在文件顶部的导入列表中添加QFileDialog到QtWidgets中。
- 添加一个名为
select_output_file的新方法,包含以下代码。此代码将打开一个文件浏览器,并用用户选择的文件路径填充行编辑部件。注意getSaveFileName如何返回一个包含文件名和所用过滤器的元组。
bash def select_output_file(self): filename, _filter = QFileDialog.getSaveFileName( self.dlg, "Select output file ","", '*.csv') self.dlg.lineEdit.setText(filename)
- 现在我们需要添加代码,以便在点击 … 按钮时调用
select_output_file方法。向下滚动到run方法,并在初始化对话框的代码块中添加以下行。此代码将把select_output_file方法连接到推送按钮部件的clicked信号。
bash self.dlg.pushButton.clicked.connect(self.select_output_file)
- 回到 QGIS,重新加载插件并运行它。如果一切顺利,您将能够点击
...按钮并从磁盘中选择一个输出文本文件。
-
当您点击插件对话框上的确定时,什么也不会发生。这是因为我们还没有添加从图层拉取属性信息并将其写入文本文件的逻辑。我们现在已经具备了实现此功能的所有条件。在
run方法中找到写着pass的地方。用下面的代码替换它。此代码的解释可以在 Python 编程入门 (QGIS3) 中找到。bash 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()] # 写入表头 line = ','.join(name for name in fieldnames) + '\n' output_file.write(line) # 写入要素属性 for f in selectedLayer.getFeatures(): line = ','.join(str(f[name]) for name in fieldnames) + '\n' output_file.write(line)
31. 我们还需要添加最后一件事。当操作成功完成时,我们应该向用户提示这一点。在 QGIS 中向用户发出通知的首选方式是通过 self.iface.messageBar().pushMessage()方法。在文件顶部的导入列表中将Qgis添加到qgis.core中,并在run方法的末尾添加以下代码。
bash self.iface.messageBar().pushMessage( "Success", "Output file written at " + filename, level=Qgis.Success, duration=3)
![]()
- 现在我们的插件已经准备好了。重新加载插件并试用它。您会发现您选择的输出文本文件将包含矢量图层的属性。
- 您可以压缩插件目录并与您的用户分享。他们可以将内容解压缩到他们的插件目录并试用您的插件。如果这是一个真正的插件,您可以将其上传到 QGIS 插件仓库,以便所有 QGIS 用户都能找到并下载您的插件。
注意
此插件仅用于演示目的。请不要发布此插件或将其上传到 QGIS 插件仓库。
以下是完整的 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)
如果您想反馈或分享您对此教程的体验,请在下方评论。(需要 GitHub 账户)






























