Better QGIS forms, part one🔗

Posted by Médéric Ribreux 🗓 In blog/ Qgis/

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:

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:

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:

Fields dialog with File Name Edit Widget for DOCUMENT field
Fields dialog with File Name Edit Widget for DOCUMENT field

When you create a new feature on the layer, you will see the following control:

Choose a file dialog
Choose a file dialog

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:

QtCreator UI file for URL
QtCreator UI file for URL

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:

QLabel properties
QLabel properties

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:

URL link to open the file
URL link to open the file

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…