Créer une extension QGIS avec une interface graphique#
Pour faire ce chapitre, il faut d'abord avoir une extension de base, à l'aide du chapitre précédent.
QtDesigner#
Premier dialogue#
Créons un fichier QtDesigner comme-ceci :
Découverte de l'interface QtDesigner#
- Faire le tour des "widgets", de "l'object inspector" et des "property"
- Privilégier les "widgets QGIS" si possible
Tip
Privilégier aussi les "Item Widgets (Item Based)" plutôt que les "Item Views (Model Based) pour débuter.
Ajoutons les "widgets"#
Important
Ne tenez pas compte de l'alignement des widgets pour le moment. On fait juste un placement "rapide" vertical des widgets.
Dans l'ordre vertical, ce sont ces classes :
Classe |
---|
QLabel |
QLineEdit |
QgsMapLayerComboBox |
QPlainTextEdit |
Vertical spacer |
QDialogButtonBox |
Une fois que l'ensemble des "widgets" sont présents, on peut faire un clic droit à droite sur notre QDialog
,
puis Mise en page
et enfin Verticalement
🚀
Ajout d'un bouton dans notre "ButtonBox" en bas#
Ajoutons le bouton d'aide, dans les propriétés de notre widget QDialogButtonBox
.
Nommage de nos "widgets"#
Pour chacun de nos widgets, changeons le nom par défaut de l'objet, propriété objectName
tout en haut :
Classe | Nom par défaut de objectName |
Nouveau nom pour objectName |
---|---|---|
QLineEdit |
lineEdit |
input_text |
QgsMapLayerComboBox |
mMapLayerComboBox |
couche |
QPlainTextEdit |
plainTextEdit |
metadata |
QDialogButtonBox |
buttonBox |
button_box |
Cette propriété objectName
est très importante, car elle détermine l'appellation de notre propriété dans
l'objet self
pour la suite du TP.
Astuces#
- Ouvrir la page des "slots/signaux" depuis la barre d'outils et supprimer ceux qui existent.
Pourquoi supprimer les signaux de QtDesigner ?
Un fichier QtDesigner est "gros" fichier XML. Il est difficile, dans le temps, de suivre ces modifications, changement…
Il est, à mon avis, plus simple de garder le fichier XML le plus léger possible, et de garder la logique dans le code Python. Un signal en XML, c'est plusieurs lignes dans le fichier UI, alors que en Python, c'est une seule ligne.
On peut télécharger la solution si besoin.
- Enregistrer le fichier en
dialog.ui
dans votre dossier contenant l'ensemble des fichiers.
La classe qui accompagne#
Créons un fichier dialog.py
avec le contenu suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Modifions la méthode run
du fichier __init__.py
en
1 2 3 4 5 |
|
Relançons l'extension à l'aide du "plugin reloader" et cliquons sur le bouton.
Les signaux et les slots#
Signaux des boutons de la fenêtre#
Tip
N'hésitez pas à relire le chapitre sur les signaux.
Connectons le signal clicked
du bouton "Annuler" dans le constructeur __init__
:
1 |
|
On dit que clicked
est un signal, auquel on connecte le slot close
.
Connectons-le signal clicked
du bouton "Accepter" à notre propre slot (qui est une fonction) :
1 |
|
Ensuite, ajoutons notre propre fonction click_ok
pour quitter la fenêtre et en affichant la saisie de
l'utilisateur dans la QgsMessageBar de QGIS.
Le widget de saisie est un QLineEdit : documentation Qt
1 2 3 4 5 |
|
Faire le test dans QGIS avec une saisie de l'utilisateur et fermer la fenêtre.
Clic sur le bouton d'aide#
1 2 3 4 5 6 7 8 9 |
|
Signaux et propriétés du formulaire de saisie#
Continuons en rendant en lecture seule le gros bloc de texte et affichons à l'intérieur la description de la couche qui est sélectionnée dans le menu déroulant.
Documentation :
- QPlainTextEdit CPP, on va utiliser
appendPlainText
etclear
. - QgsMapLayerComboBox CPP / PyQGIS, on va utiliser
currentLayer
.
Dans la fonction __init__
du fichier dialog.py
:
1 2 |
|
Et la nouvelle fonction qui va se charger de mettre à jour le texte :
1 2 3 4 5 |
|
Danger
Que remarquez-vous en terme d'ergonomie, à l'ouverture de la fenêtre ?
La solution plus complète
1 2 3 4 5 |
|
On peut donc désormais cumuler l'ensemble des chapitres précédents pour lancer des algorithmes, manipuler les données, etc.
Bonus
Le texte actuel concernant les métadonnées est "limité". N'hésitez pas à compléter, un peu comme lors de l'exercice avec le fichier CSV, pour rappel :
- La source de la couche
- Le nombre d'entité
- Des informations que l'on retrouve dans le panneau "Informations" des propriétés d'une couche
- …
Solution#
Afficher
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
|
Organisation du code#
Il ne faut pas hésiter à créer des fichiers Python afin de séparer le code.
On peut aussi créer des dossiers afin d'y mettre plusieurs fichiers Python. Un dossier en Python se nomme un
module. Pour faire un module compatible, pour Python, il faut toujours avoir un fichier
__init__.py
même s’il n'y a rien dedans.
Warning
Il ne faut vraiment pas oublier le fichier __init__.py
. Cela peut empêcher Python de fonctionner
correctement. Un bon IDE peut signaler ce genre d'erreur.
Dans l'exemple ci-dessus, on peut diviser le code du fichier __init__.py
:
1 2 3 4 |
|
En faisant un couper/coller, enlever la classe MinimalPlugin
du fichier __init__.py
.
Tip
On essaie souvent d'avoir une classe par fichier en Python.
Créer un fichier plugin.py
et ajouter le contenu en collant. Il est bien de vérifier les imports dans les
deux fichiers.
Un dossier "resources"#
On peut créer un fichier qgis_plugin_tools.py
à la racine de notre extension afin d'y ajouter des outils :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
On peut ensuite créer un dossier resources
puis icons
afin d'y déplacer un fichier PNG, JPG, SVG.
Warning
Attention à la taille de vos fichiers pour une petite icône 😉
Pour les experts, solution pour faire une fonction qui charge un fichier UI
1 2 3 4 5 6 7 |
|
Dans une extension graphique pour les icônes#
1 2 3 4 5 6 7 8 9 10 |
|
Tip
Ce qu'il faut retenir, c'est l'usage de QIcon(str(resources_path('icons', 'icon.svg')))
si l'on souhaite utiliser
une icône dans autre endroit de l'extension.
Dans une extension "Processing"#
Dans le provider et les algorithmes :
1 2 3 4 5 6 |
|
Utilisation d'une icône provenant de QGIS#
À l'aide de l'extension "PyQGIS Resource Browser", rechercher une icône concordant avec bouton :
new
pour trouver les icônes ayant la petite étoile jauneselect
pour trouver les icônes ayant la notion de sélection, avec le fond jaunedelete
ouremove
pour la suppression- …
On peut ensuite faire un clic-droit, puis coller son chemin.
Ensuite, quand on souhaite utiliser l'icône :
1 2 3 4 5 6 7 8 |
|
Ajouter un bouton pour lancer Processing#
Nous souhaitons ajouter 2 boutons :
Classe | objectName |
---|---|
QPushButton |
btn_traitement_1 |
QPushButton |
btn_traitement_2 |
On peut faire la mise en page vertical dans le QGroupBox
.
Ajoutons les icônes et infobulles si nécessaires, dans le constructeur :
1 2 3 |
|
Lancer le dialogue de Processing#
Pour les imports :
1 2 |
|
Pour le code dans la fonction :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Si on ne souhaite pas afficher la fenetre Processing :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Pour rappel, nous ne sommes pas obligé d'ouvrir la fenêtre de Processing, on peut directement faire processing.run
,
lire le chapitre précédent. Il ne
faut pas oublier de donner la variable layer
à notre INPUT
si vous copiez/coller le code de processing.run
du
chapitre précédent.
1 2 3 4 5 6 7 8 9 10 11 |
|