PyQt : Représenter un point dans une QGraphicsView

Imaginons que l'on souhaite représenter un point dans une QGraphicsView. Il sera représenté par un cercle, dont on souhaite que le diamètre apparent ne change pas lorsqu'on zoome ou dézoome.

Commençons par ajouter une QGraphicsEllipseItem à la scène :

graphicsScene = QGraphicsScene()
graphicsView.setScene(graphicsScene) # La variable "graphicsView" contient une QGraphicsView

# On ajoute un rectangle dont le coin haut gauche est à (0, 0), faisant 10 de large et 5 de haut :
graphicsScene.addRect(QRectF(0, 0, 10, 5))

# On ajoute un cerle de diamètre 8 centré autour de (0, 0)
point = graphicsScene.addEllipse(QRectF(-4, -4, 8, 8), QPen(), QBrush(QtCore.Qt.red, QtCore.Qt.SolidPattern))

# On ajuste la dimention de la scène pour englober tous les éléments qu'elle contient :
graphicsScene.setSceneRect(graphicsScene.itemsBoundingRect())
# On zoome de manière à voir l'ensemble de la scène, et pas plus :
graphicsView.fitInView(graphicsScene.sceneRect(), QtCore.Qt.KeepAspectRatio)
# On peut éventuellement dézommer un peu pour avoir une marge autour des éléments
graphicsView.scale(0.875, 0.875)

On obtient :
QGraphicsEllipseItem dans une QGraphicsView

Le "point" (cercle) est au bon endroit, mais on souhaite maintenant qu'il ait toujours un diamètre apparent de 8x8 pixels, quel que soit le niveau de zoom.

Pour cela il faut ajouter :

point.setFlag(QGraphicsItem.ItemIgnoresTransformations)

Après le addEllipse()

On obtient :
QGraphicsEllipseItem dans une QGraphicsView

Le point a maintenant la dimension apparente que l'on souhaite, mais un autre problème se pose : il y a une énorme marge autour de lui.

Cela se produit car le flag ItemIgnoresTransformations ne s'applique qu'à l'apparence de l'élément, et pas à son rectangle délimitant ("bounding rectangle").

On peut remédier à cela en créant notre propre classe, qui va hériter de QGraphicsEllipseItem, et redéfinir la méthode boundingRect() :

class ZeroSizedGraphicsEllipseItem(QGraphicsEllipseItem):
    def boundingRect(self):
        """Bounding rect infinitésimal (infiniment petit)"""
        # On retourne un "bounding rect" de taille 0 x 0 :
        return QRectF(self.x(), self.y(), 0, 0)

Ainsi, le code devient :

graphicsScene = QtGui.QGraphicsScene()
graphicsView.setScene(graphicsScene) # La variable "graphicsView" contient une QGraphicsView

# On ajoute un rectangle dont le coin haut gauche est à (0, 0), faisant 10 de large et 5 de haut :
graphicsScene.addRect(QRectF(0, 0, 10, 5))

# On ajoute un cerle de diamètre 8 centré autour de (0, 0) :
point = ZeroSizedGraphicsEllipseItem(QRectF(-4, -4, 8, 8))
point.setBrush(QBrush(QtCore.Qt.red, QtCore.Qt.SolidPattern))
graphicsScene.addItem(point)
# Ne pas le redimensionner quand on zoome / dézoome :
point.setFlag(QGraphicsItem.ItemIgnoresTransformations)

# On ajuste la dimention de la scène pour englober tous les éléments qu'elle contient :
graphicsScene.setSceneRect(graphicsScene.itemsBoundingRect())
# On zoome de manière à voir l'ensemble de la scène, et pas plus :
graphicsView.fitInView(graphicsScene.sceneRect(), QtCore.Qt.KeepAspectRatio)
# On peut éventuellement dézommer un peu pour avoir une marge autour des éléments
graphicsView.scale(0.875, 0.875)

On obtient ainsi l'image suivante :
QGraphicsEllipseItem dans une QGraphicsView

Tags :
Python
Qt
PyQt4
QGraphicsView
QGraphicsScene
QGraphicsEllipseItem