I'm using QGIS 3.22 trying to go through a tutorial (3.16 training manual) and I followed the popular Nathan Woodrow's blog setting custom form with python logic where I changed the PyQt4 imports for PyQt5. I set up a vector layer exactly as described by Woodrow and an UI form that works seamlessly.
from PyQt5.QtWidgets import QLineEdit, QDialogButtonBox, QMessageBox
nameField = None
myDialog = None
def formOpen(dialog, layerid, featureid):
global myDialog
myDialog = dialog
global nameField
nameField = dialog.findChild(QLineEdit, "Name")
buttonBox = dialog.findChild(QDialogButtonBox, "buttonBox")
# change buttonBox.accepted.disconnect(myDialog.accept)
buttonBox.accepted.disconnect()
buttonBox.accepted.connect(validate)
# change buttonBox.rejected.connect(myDialog.reject)
buttonBox.rejected.connect(myDialog.resetValues)
def validate():
if not len(nameField.text()) > 0:
QMessageBox.warning(None, "Attention", "Name can't be null", QMessageBox.Ok)
else:
# change myDialog.accept()
myDialog.save()
To get the Python logic working I figure out that QgsAttibuteForm
's accept
and reject
methods were deprecated and replaced by save
and resetValues
so I changed then accordingly.
Furthermore, when tried to place buttonBox.disconnect(myDialog.save)
I got
TypeError: disconnect failed beetween 'accepted' and 'save'
I just disconnect all signals by using disconnect()
.
Finally I got the form running and the validation working but even after showing the invalid entry, the feature is save with a null value after clicking the ok button.
I tried to place a myDialog.resetValues()
or myDialog.close()
at the validation method with the same results.
I'd like the form to revert to the feature values and stay open for the user to correct the entry.
Best Answer
I concur that there seems to be an issue with disconnecting the
save()
andresetValues()
slots from theaccepted
andrejected
signals of theQDialogButtonBox
ofQgsAttributeForm
. Indeed, perusing the docs, I saw that theQgsAttributeForm
class has a methoddisconnectButtonBox()
which should take care of this nicely. Inspecting the c++ source code you can see that this method should simply disconnect both those slots from the button box signals. Unfortunately, I was just not able to get it to work.Below is an effective workaround which makes use of the
QgsAttributeForm.hideButtonBox()
method which does work. The basic idea is that we add two new, separateQPushButtons
to the custom .ui, which we connect to our own slot functions. We also hide the existing button box (which is the workaround for disconnecting the slots from it's signals).It takes a little more effort to set up the custom .ui file in QtDesigner. Instead of using the 'create dialog with buttons bottom' option, I created a dialog without buttons. I then added the two
QPushButton
objects (with a horizontal box layout); the rest of the form dialog is much the same. You still need to make sure the line edit object names match your field names. Additionally, I renamed the two push buttonsbtn_OK
andbtn_Cancel
as we will be accessing them in the Python logic later. To make things look nice, I used a form layout for the whole dialog, set a maximum width for the two push buttons of 75, and added a couple of spacers. My dialog in QtDesigner looks like this:The object names are:
The Python file to add the logic looks like this:
After enabling macros in Settings-> Options-> General-> Project Files, I recorded the following two short screencasts showing the resulting behavior when adding and editing features.
Adding a new feature:
Editing an existing feature: