Better QGIS forms, part one🔗
Introduction
Today, I am starting a serie of articles about QGIS forms. QGIS is probably the best GIS software for easy attributes edition. You can build almost standalone GIS applications just by triggering some parameters in fields dialog, sometimes with a little bit of code, of course.
I've built a whole application using only forms. On the GIS side, it wasn't very complicated: 8 layers to edit with simple geometries. But on the attributes side, it was a real challenge:
- About one hundred attributes tables stored on Oracle Database.
- Some layers can have about 80 form controls.
- n,n relations.
- Specific tables to store tree data (more on this later).
- Complete custom form controls.
- We needed subforms to handle photos.
- Of course we needed a true search engine on all of the attribute fields.
Building such an application wasn't very easy even if you already know QGIS well. At the beginning of the project I was not really sure that QGIS would be the right tool.
In the end, I just want to say that QGis works perfectly well on this application even with a lot of data loaded.
When I started to implement features into this QGis project, I often faced QGIS lack of features on the forms. I've made some bug or features report on http://hub.qgis.org. In this serie of articles, I will try to show you how to circumvent the different problems I faced.
About QGIS forms
Before diving into code, let's have some basic informations about QGIS forms…
Forms are built by QGIS in order to edit (non-geometric) attributes for an object on a defined layer. You can read more about the process in the official QGis documentation.
You need to understand how forms are working in QGIS. To my mind, there are three parts on this subject:
- Data fields: the goal of the form is to edit the values of the attributes in the layer. Data fields are named with the attributes name of the layer.
- GUI: This is the part that QGIS show to the user to edit alphanumerical data. You can choose between three types: auto-generated (QGIS build everything, for basic forms), drag'n drop designed (you build the form using tabs and groupboxes inside QGIS) or customised (you need to use QtCreator to build a
.ui
file that will be displayed in QGIS). To make link between form controls and fields values, there is a trivial rule: form controls are named like the field they are linked to. - Python code: QGIS allows you to add Python logic to the GUI part of the form. Whenever the form opens, you can call a Python function. This function can access to GUI objects using Qt functions but you also have the full power of QGIS Python API.
For Data fields definition, you have nothing to do: QGIS will use the layer definition to name attributes.
For GUI, you have to configure (per-layer) the method (auto-generated/drag'n drop/custom) on the Fields tab of the layer properties dialog. You can read this introduction article to have further details…
For Python code, you just have to name the Python file (module) that will be used and the function that will be launched. Read this reference article to dig a little bit more.
Show a clickable URL on the form
Introduction
In QGIS forms, you can specify that a field will store a path to a file. You just have to use "File Name" for the Edit Widget. There is no option for this type of widget:
When you create a new feature on the layer, you will see the following control:
Push the "…" button and a file dialog will be opened (like every Qt file dialog). When you have chosen the file, the path (/home/medspx/clint_eastwood.ods
) will be stored in the line edit form control (a QLineEdit) just right to the field name (DOCUMENT). But what if you want to open the file and not just store the path ? You have to trick QGIS a little bit.
Build a custom form like this
First of all, you have to use custom form. Otherwise, QGis will use a QLineEdit to display file path. We need to use a QLabel as only QLabel are able to display properly a URL link. In your .ui
file just add the following to your layout:
As you can see, we have a QLabel to name the field ("Document"). Then you have another QLabel which contains "No file selected". Name this QLabel with a dedicated name (the objectName property will be DOCUMENT_URL). This QLabel will make the job of link URL presentation. Be careful to have a field which is named like the objectName otherwise, QGIS will not save the data into the layer.
You have to check two properties for this QLabel:
- openExternalLinks: checked (otherwise, you will not be able to open the file with a click).
- textInteractionFlags: LinksAccessibleByMouse.
Then we have the QPushButton which will be named DOCUMENT_URL_B. And that's all for the ui part. Now, it is time to dive into Python…
Code to show the file path as clickable link
First, the code:
def manageURL(dialog, layerid=None, featureid=None): """ General function to manage URL in subforms """ # Manage URL buttons for child in [f for f in dialog.findChildren(QPushButton) if u"URL_B" in f.objectName()]: try: child.clicked.disconnect() except: pass child.clicked.connect(partial(chooseFile, dialog, child.objectName()[:-2])) # Make URL fields always clickable (even in non-edit mode) for child in [f for f in dialog.findChildren(QLabel) if f.objectName()[-3:] == u"URL"]: child.setEnabled(True) def chooseFile(dialog, field): """ Open a dedicated file picker form """ lineEdit = dialog.findChild(QLabel, field) if lineEdit is None: QgsMessageLog.logMessage(u"chooseFile: There is no QLabel for field {0} !".format(field), "DBPAT", QgsMessageLog.INFO) filename = QtGui.QFileDialog.getOpenFileName(dialog, u'Choisir un fichier…') if filename is not None: # parse the result to make a link basename = os.path.basename(filename) link = u"<a href=\"file:///{0}\">{1}</a>".format(filename,basename) lineEdit.setText(link)
When you want to display links, you just have to use manageURL
function to do the job.
This function will try to find all of the QPushButton that are named like "SOMETHING_URL_B". Those buttons will be disconnected from their previous "clicked" signal. Then, we will re-connect the "clicked" signal to our "chooseFile" function.
This function will do the job of building the link from the file path. manageURL ends with another trick: if you are not in edit mode, QLabel with links are not clickable. So we have to manually enable every QLabel that are links (ends with "URL" in our case).
chooseFile
function opens a file chooser dialog (a standard QFileDialog from Qt). If the file path is valid, we extract the filename (the basename, only the last part of the path) from the file path. And then we build a link which is a simple . At last, we update the QLabel with the link content.
Here is a rendered thing of the link:
You can click on the link and it will open the file with the default operating system defined viewer.
Conclusion
This first article just demonstrate that QGIS is so wide open to external code that you can do nearly everything you want with the forms. Embedding PyQt4 and giving access to the full QGIS API from Python is very interesting because you can go further than standard QGIS forms without recompiling QGIS.
As you can see, once you have written a dedicated function to handle URL links for File controls, it is very easy to expand it to any layer you want: just correctly name a form control on your custom form file and add the correct path to the function.
In the following article, we will focus on Photo edit widget and we will try to use it in a custom form with a little bit of code as an extra…