Search code examples
pythonpyside2

Moving ellipse moves other elements in QGraphicsScene


I'm writing a simple gui with a QGraphicsScene. Inside this scene i have 2 simple XY axis and I have a function to retrieve the position of my mouse click on the scene.

I added a simple movable ellipse to my scene and I'm retrieving it's position (at the moment it's only an on click detection, later I will implement a signal for its movement)

Here's the code:

View.py:

import math
import sys

from PySide2.QtCore import Signal, QPointF
from PySide2 import QtCore
from PySide2.QtGui import QColor, QPainterPath
from PySide2.QtWidgets import (QGraphicsItem,
    QApplication,
    QGraphicsScene,
    QGraphicsView,
    QHBoxLayout,
    QMainWindow,
    QPushButton,
    QWidget,
    QSizeGrip
)
from PySide2 import QtGui
from PySide2.QtGui import QIcon


class GraphicsScene(QGraphicsScene): # Used to indicate inheritance from parent class
    clicked = Signal(QPointF)

    def drawBackground(self, painter, rect):
        l = min(rect.width(), rect.height()) / 30

        x_left = QPointF(rect.left(), 0)
        x_right = QPointF(rect.right(), 0)
        painter.drawLine(x_left, x_right)

        right_triangle = QPainterPath()
        right_triangle.lineTo(-0.5 * math.sqrt(3) * l, 0.5 * l)
        right_triangle.lineTo(-0.5 * math.sqrt(3) * l, -0.5 * l)
        right_triangle.closeSubpath()
        right_triangle.translate(x_right)

        painter.setBrush(QColor("black"))
        painter.drawPath(right_triangle)

        y_top = QPointF(0, rect.top())
        y_bottom = QPointF(0, rect.bottom())
        painter.drawLine(y_top, y_bottom)

        top_triangle = QPainterPath()
        top_triangle.lineTo(.5*l, -0.5 * math.sqrt(3) * l)
        top_triangle.lineTo(-.5*l, -0.5 * math.sqrt(3) * l)
        top_triangle.closeSubpath()
        top_triangle.translate(y_bottom)

        painter.setBrush(QColor("black"))
        painter.drawPath(top_triangle)



    def mousePressEvent(self, event):
        sp = event.scenePos()
        self.clicked.emit(sp)
        super().mousePressEvent(event)


class MyView(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("8D.me")
        self.setFixedSize(800, 500)

        self.btn = QPushButton("test2")
        self.btn.setStyleSheet( "color: white; border-radius: 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); background: rgb(66, 184, 221); min-width:100px;min-height:30px")

        self.view = QGraphicsView()

        print(self.view.size())
        self.view.scale(1, -1)
        self.scene = GraphicsScene()
        self.view.setScene(self.scene)
        self.setIcon()
        self.ellipse=self.scene.addEllipse(10,10,10,10)
        self.ellipse.setFlag(QGraphicsItem.ItemIsMovable)


        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QHBoxLayout(central_widget)
        layout.addWidget(self.btn)
        layout.addWidget(self.view)
        #flags = QtCore.Qt.WindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
        #self.setWindowFlags(flags)
        self.scene.clicked.connect(self.handle_clicked)

    def handle_clicked(self, p):
        print("position",self.ellipse.pos())
        print("clicked", p.x(), p.y())
        print(self.view.size())

# Insert methods for creating/adding elements to the default view.
# Mehods....
    def setIcon(self):
        appIcon = QIcon('logo')
        self.setWindowIcon(appIcon)

#Insert here the public methods called by the Controller to update the view

main.py:

import View
#import Model
#import Controller

#Useful example: https://realpython.com/python-pyqt-gui-calculator/
def main():
    """Main function."""
    # Create an instance of QApplication
    app = QApplication([])

    # Show the GUI
    mainwindow =View.MyView()
    mainwindow.show()

    # Create instances of model and controller
    #model = Model.myModel()
    #Controller.myController(view=mainwindow,model=model)
    return app.exec_()

if __name__ == '__main__':
    main()

EDIT: I added also the main code. I have the View in a separate file since I am using a MVC design pattern.

My GUI looks like this:

enter image description here

And it works so far.

The problem is if I move the ellipse (which moves correctly) my XY axis moves too, and I can't figure out why.

For example this is how my GUI changes if I move the ellipse toward the upper right angle:

enter image description here

What's the problem? How do I "fix" the XY axis? I would like to keep my axis still and move only the ellipse inside the scene region.


Solution

  • I solved the problem following this: https://forum.qt.io/topic/48564/force-qgraphicsview-size-to-match-qgraphicsscene-size/7

    Basically i added the following lines to my code:

    self.view.setFixedSize(600,480)       
    self.view.setSceneRect(-300,-220,600,480)      
    self.view.fitInView(0,0,600,480, Qt.KeepAspectRatio)
    

    So the new View.py is:

    import math
    import sys
    from PySide2.QtCore import Signal, QPointF, Qt
    from PySide2 import QtCore
    from PySide2.QtGui import QColor, QPainterPath
    from PySide2.QtWidgets import (QGraphicsItem,
        QApplication,
        QGraphicsScene,
        QGraphicsView,
        QHBoxLayout,
        QMainWindow,
        QPushButton,
        QWidget,
        QSizeGrip
    )
    from PySide2 import QtGui
    from PySide2.QtGui import QIcon
    
    
    class GraphicsScene(QGraphicsScene): # Used to indicate inheritance from parent class
        clicked = Signal(QPointF)
    
        def drawBackground(self, painter, rect):
            #print('Pinto rect',rect.width(),rect.height())
            l = min(rect.width(), rect.height()) / 30
            #print('printo l',l)
    
            #Qui c'è il bug. La funzione drawbackground viene chiamata a ogni modifica della scene.
            #Lo starting point del drawing della line cambia nel tempo, ecco perchè mi si spostano gli assi
            x_left = QPointF(rect.left(), 0)
            x_right = QPointF(rect.right(), 0)
            print('printo gli x',rect.left(),rect.height())
            painter.drawLine(x_left, x_right)
    
            right_triangle = QPainterPath()
            right_triangle.lineTo(-0.5 * math.sqrt(3) * l, 0.5 * l)
            right_triangle.lineTo(-0.5 * math.sqrt(3) * l, -0.5 * l)
            right_triangle.closeSubpath()
            right_triangle.translate(x_right)
    
            painter.setBrush(QColor("black"))
            painter.drawPath(right_triangle)
    
            y_top = QPointF(0, rect.top())
            y_bottom = QPointF(0, rect.bottom())
            painter.drawLine(y_top, y_bottom)
    
            top_triangle = QPainterPath()
            top_triangle.lineTo(.5*l, -0.5 * math.sqrt(3) * l)
            top_triangle.lineTo(-.5*l, -0.5 * math.sqrt(3) * l)
            top_triangle.closeSubpath()
            top_triangle.translate(y_bottom)
    
            painter.setBrush(QColor("black"))
            painter.drawPath(top_triangle)
    
    
    
        def mousePressEvent(self, event):
            sp = event.scenePos()
            self.clicked.emit(sp)
            super().mousePressEvent(event)
    
    
    class MyView(QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setWindowTitle("8D.me")
            self.setFixedSize(800, 500)
    
            self.btn = QPushButton("test2")
            self.btn.setStyleSheet( "color: white; border-radius: 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); background: rgb(66, 184, 221); min-width:100px;min-height:30px")
    
            self.view = QGraphicsView()
            print(self.view.size())
    
            self.view.setFixedSize(600,480)
            self.view.setSceneRect(-300,-220,590,470)
            self.view.fitInView(0,0,600,480, Qt.KeepAspectRatio)
    
            self.view.scale(1, -1)
            self.scene = GraphicsScene()
            self.view.setScene(self.scene)
    
            self.setIcon()
            self.ellipse=self.scene.addEllipse(-5,-5,10,10)
            self.ellipse.setFlag(QGraphicsItem.ItemIsMovable)
    
    
            central_widget = QWidget()
            self.setCentralWidget(central_widget)
            layout = QHBoxLayout(central_widget)
            layout.addWidget(self.btn)
            layout.addWidget(self.view)
            #flags = QtCore.Qt.WindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
            #self.setWindowFlags(flags)
            self.scene.clicked.connect(self.handle_clicked)
    
        def handle_clicked(self, p):
            print("position",self.ellipse.pos())
            print("clicked", p.x(), p.y())
            print(self.view.size())
    
    # Insert methods for creating/adding elements to the default view.
    # Mehods....
        def setIcon(self):
            appIcon = QIcon('logo')
            self.setWindowIcon(appIcon)
    
    #Insert here the public methods called by the Controller to update the view