pyqtgraphpyqt6viewbox

Catching the position of the mouse on a keyPressEvent in pyqtgraph ViewBox


I'm trying to get the position of the mouse on a keyPressEvent of a custom pg.ViewBox. The only solution I've found so far is to create some crosshairs and get the position of those on a keyPressEvent. Is there a better way to do this without crosshairs?

Also, in the example I give, when I press a key, the first time nothing happens and the second time, I get a strange undecipherable error message, followed by the expected behavior (which is to print the position of the cursor) but repeated many many times:

Error in sys.excepthook:

Original exception was:
Mouse position from crosshairs: [0.3896103896103896, 0.5307017543859649]
Mouse position from crosshairs: [0.3896103896103896, 0.5307017543859649]
Mouse position from crosshairs: [0.3896103896103896, 0.5307017543859649]
...

I would be grateful for any clue about what is going on.

Here is a minimal working example:

from PyQt6 import QtWidgets
import pyqtgraph as pg
import sys

class CrosshairViewBox(pg.ViewBox):

    def __init__(self, *args, **kwds):
        pg.ViewBox.__init__(self, *args, **kwds)
    
        self.crosshair_x = pg.InfiniteLine(
            angle=90, movable=False
        )
        self.crosshair_y = pg.InfiniteLine(
            angle=0, movable=False
        )
        self.addItem(self.crosshair_x, ignoreBounds=True)
        self.addItem(self.crosshair_y, ignoreBounds=True)

    def activate_crosshairs(self):
        self.scene().sigMouseMoved.connect(self.move_crosshair)

    def move_crosshair(self, ev):
        """
            This is triggered on a sigMouseMoved which sends a position as an event
        """
        pos = ev
        if self.sceneBoundingRect().contains(pos):
            mousePoint = self.mapSceneToView(ev)
            self.crosshair_x.setPos(mousePoint.x())
            self.crosshair_y.setPos(mousePoint.y())

    def keyPressEvent(self, ev):
        """
            In this case, ev is a QKeyEvent, for which I cannot get
            the position of the mouse. So I have to use the crosshairs.
        """
        self.scene().keyPressEvent(ev)
        posx = self.crosshair_x.getPos()[0]
        posy = self.crosshair_y.getPos()[1]
        print(f"Mouse position from crosshairs: [{posx}, {posy}]")

def main():
    app = QtWidgets.QApplication(sys.argv)
    win = pg.GraphicsLayoutWidget(show=True)
    chvb = CrosshairViewBox()
    win.addItem(chvb)
    chvb.activate_crosshairs()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()


Solution

  • There is absolutely no need to complicate things by creating other items. Just store the mouse position as an instance attribute:

    class CrosshairViewBox(pg.ViewBox):
        def __init__(self, *args, **kwds):
            super().__init__(*args, **kwds)
            self.mousePoint = QtCore.QPointF()
    
        def itemChange(self, change, value):
            if change == self.GraphicsItemChange.ItemSceneChange and value:
                # automatically connect the signal when added to a scene
                value.sigMouseMoved.connect(self.mouse_moved)
                self.setFocus()
            return super().itemChange(change, value)
    
        def mouse_moved(self, pos):
            self.mousePoint = self.mapSceneToView(pos)
    
        def keyPressEvent(self, ev):
            print("Mouse position from crosshairs: [{}, {}]".format(
                self.mousePoint.x(), self.mousePoint.y()))
    

    Also, do not call self.scene().keyPressEvent(ev) as it would probably cause recursion (which may be what causes the error you are seeing).