Today I wanna share a solution for a problem we faced during the implementation of a tool for our QGIS plugin, DsgTools. The tool in question captures the signal featureAdded to get access to the newly created features for a particular QgsVectorLayer and change their attributes.
But, during our code testing we found a major problem. During the edition, when the undo button was pressed QGIS crashed big time!
So, how can you solve this problem? Surfing the web, lcoandrade and I have come across this: http://gis.stackexchange.com/questions/183423/qgis-crashes-when-doing-a-rollback-after-modifying-values-of-an-user-added-featu
By studing the link above and some other related, we have discovered that our function connected to featureAdded was messing things up, but why? Sure, we have learned some “kludges” or like some might say “McGyver Programming Stunts” to solve the problem, but what is the magic that happens when a user finishes adding a feature in QGIS? We need to understand that in order to code in an elegant way!
First of all, featureAdded() is triggered:
This triggers QgsMapToolAddFeature::addFeature which does the following
QgsFeatureAction *action = new QgsFeatureAction( tr( "add feature" ), *f, vlayer, QString(), -1, this ); bool res = action->addFeature( QgsAttributeMap(), showModal );
Which calls QgsFeatureAction::addFeature
mLayer->beginEditCommand( text() ); mFeatureSaved = mLayer->addFeature( *mFeature ); if ( mFeatureSaved ) mLayer->endEditCommand(); else mLayer->destroyEditCommand();
mLayer->beginEditCommand( text() );
if ( mFeatureSaved ) mLayer->endEditCommand(); else mLayer->destroyEditCommand();
QgsVectorLayer::addFeature is called
mFeatureSaved = mLayer->addFeature( *mFeature );
Which calls QgsVectorLayerEditBuffer::addFeature. It is only inside this method that the undo stack is updated.
L->undoStack()->push( new QgsVectorLayerUndoCommandAddFeature( this, f ) );
But, until endEditCommand() is called it is not possible to perform undo operations (http://pyqt.sourceforge.net/Docs/PyQt4/qundostack.html#beginMacro). Ok?
So let’s go back to our former coding, keeping in mind that if you decide to perform an Undo operation QGIS will Crash big time (http://qgis-developer.osgeo.narkive.com/5wnziigA/wrapping-changeattributevalue-between-begin-and-end-editcommand#post2).
Ok, on one hand I want to manipulate feature’s attributes when they are added, but on the other hand, if I try to do so, I’ll mess up the undo stack. How should we proceed? What if I only peep while QGIS does its magic and then I barge in and do my own stuff? So I have to have one slot connected to featureAdded signal just to let me know which features should I visit on the editBuffer and another slot connected to editCommandEnded signal, so that I am sure that the undoStack is properly built allowing me to revisit the added features to change its attributes.
Something like this:
myLayer.featureAdded.connect(self.storeFeaturesIds) mylayer.editCommandEnded.connect(self.updateAttributesAfterAdding) @pyqtSlot(int) def storeFeaturesIds(self, featId): """ This method only stores featIds in a class variable """ self.addedFeatures.append(featId) def updateAttributesAfterAdding(self): layer = self.sender() #here I can get the layer that has sent the signal while self.addedFeatures: featureId = self.addedFeatures.pop() #begining the edit command layer.beginEditCommand('Your command message here')) #Do your stuff here layer.endEditCommand()
So there it is, how to access attributes on the fly and not explode your QGIS while doing it!