如何将 qraphicsitems 的位置保存和加载到 qraphicscene 或(正确地卓尔项目) [英] How to save and load position of qraphicsitems to qraphicscene or (correctly drow item)

查看:81
本文介绍了如何将 qraphicsitems 的位置保存和加载到 qraphicscene 或(正确地卓尔项目)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

原则上,这不是一项艰巨的任务,但一切都没有看起来那么容易.理解坐标非常困难.因为它们一共有三对:模型、项目、视图.我保留 x 和 y 位置以及类属性的其余部分.然后我尝试使用当前位置将它们添加回来.这就是魔法开始的地方.元素占据了以前明显不存在的位置.这也显然与移动物品功能的实现有关.但是,它不是我的,我不明白如何在开始和结束时正确设置坐标.还有一个放大缩小场景的功能,是可以的,也会影响后续的加载,不过我已经没有想法了.

In principle, not a difficult task, but everything turned out to be not as easy as it seems. It is very difficult to understand the coordinates. For there are three pairs of them: the model, the Item, the view. I am keeping the x and y positions and the rest of the class properties. Then I try to add them back using the current position. This is where the magic begins. Elements take positions that were clearly not there before. This is also clearly related to the implementation of the move items function. However, it is not mine and I do not understand how to correctly set the coordinates, both at the beginning and at the end. There is also a function to zoom in and out of the scene, it is possible and it also affects the subsequent loading, however I already have no ideas.

加载前

加载后

我的 MRE 工具

import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from  math import pi
import os
from pickle import load,dump

class MyQGraphicsView(QGraphicsView):

    def __init__ (self, parent=None):
        super(MyQGraphicsView, self).__init__ (parent)


    def wheelEvent(self, event):

        if QApplication.keyboardModifiers() == Qt.ControlModifier:
            print('Control+Click')
        # Zoom Factor
            zoomInFactor = 1.25
            zoomOutFactor = 1 / zoomInFactor

            # Set Anchors
            self.setTransformationAnchor(QGraphicsView.NoAnchor)
            self.setResizeAnchor(QGraphicsView.NoAnchor)

            # Save the scene pos
            oldPos = self.mapToScene(event.pos())

            # Zoom
            if event.delta() > 0:
                zoomFactor = zoomInFactor
            else:
                zoomFactor = zoomOutFactor
            self.scale(zoomFactor, zoomFactor)

            # Get the new position
            newPos = self.mapToScene(event.pos())

            # Move scene to old position
            delta = newPos - oldPos
            self.translate(delta.x(), delta.y())

class Communicate(QObject):
    closeApp = Signal()
    add_delete_row = Signal(QModelIndex, str)

class Drow_equipent(QGraphicsItem):
    def __init__(self, x, y, w, h,name,brush=Qt.blue,type='эллипс'):
        super().__init__()
        self.setPos(x, y)
        self.penWidth = 1
        self.name=name
        self.x,self.y,self.h,self.w=x,y,h,w
        self.setAcceptHoverEvents(True)
        self.signal=Communicate()
        self.setFlags(QGraphicsItem.ItemSendsGeometryChanges|QGraphicsItem.ItemIsSelectable)
        self.rotation_=False
        self._brush=QBrush(brush)
        self.pen_color=Qt.black
        self.type_obj=type
        self.types = {'эллипс': lambda x: x.drawEllipse(self.x, self.y, self.w, self.h),'прямоугольник':lambda x: x.drawRect(self.x, self.y, self.w, self.h)}
                                            # x.drawLine(QPoint(self.x+self.x*2,self.y+self.y*2),QPoint(self.w_eq-self.w_eq/8,self.h_eq-self.h_eq/8)),
                                            # x.drawLine(QPoint(self.x+self.x*8,self.y+self.y*8),QPoint(self.x+self.x*4,self.y+self.y*4+self.h_eq/8)),
                                            # x.drawLine(QPoint(self.x, self.y), QPoint(5, self.y)),
                                            # x.drawLine(QPoint(self.w_eq,self.h_eq), QPoint(self.w_eq, self.h_eq-5)),
                                            # x.drawLine(QPoint(self.w_eq, self.h_eq), QPoint(self.w_eq-5, self.h_eq)),





    def get_file_settings(self):

        c=self.scenePos()


        return {'x':c.x(),'y':c.y(),'h':self.h,'w':self.w,'name':self.name,'rotation':self.rotation(),
                'brush_color':self._brush.color().toTuple(),'type_obj':self.type_obj}


    def createDefaultContextMenu(self):
        menu = QMenu()
        menu.addAction('Повернуть').triggered.connect(lambda: self.setMode('scale'))
        return menu

    def hoverEnterEvent(self, event):
        QApplication.instance().setOverrideCursor(Qt.OpenHandCursor)

    def hoverLeaveEvent(self, event):
        QApplication.instance().restoreOverrideCursor()

    def mouseMoveEvent(self, event):

        if self.rotation_:
            self.my_rotation(self.rotation()+1)
            return True


        orig_cursor_position = event.lastScenePos()
        updated_cursor_position = event.scenePos()
        orig_position = self.scenePos()
        updated_cursor_x = updated_cursor_position.x() - orig_cursor_position.x() + orig_position.x()
        updated_cursor_y = updated_cursor_position.y() - orig_cursor_position.y() + orig_position.y()



        self.setPos(QPointF(updated_cursor_x, updated_cursor_y))




    def createDefaultContextMenu(self):
        menu = QMenu()

        menu.addAction('Повернуть').triggered.connect(lambda: self.setMode(True))

        return menu



    def contextMenuEvent(self, event):
            menu = self.createDefaultContextMenu()

            menu.exec_(event.screenPos())

    def setMode(self, mode):
        self.rotation_ = mode

    def mouseReleaseEvent(self, event):
        print('x: {0}, y: {1}'.format(self.pos().x(), self.pos().y()))
        if self.rotation_:
            self.rotation_=False

    def my_rotation(self,angle):
        # self.prepareGeometryChange()
        c=self.mapToScene(self.boundingRect().center())
        self.setRotation(angle)
        cNew = self.mapToScene(self.boundingRect().center())
        offset = c - cNew
        self.moveBy(offset.x(), offset.y())


    def mousePressEvent(self, event):

        print(event.pos())


    def boundingRect(self):
        # return QRectF(-10 - penWidth / 2, -10 - penWidth / 2,
        #               20 + penWidth, 20 + penWidth)

        return QRectF(self.x,self.y,self.w,self.h)
        # return QRectF(self.x,self.y,self.w_eq,self.h_eq)

    def paint(self, painter, option, widget, PySide2_QtWidgets_QWidget=None, *args, **kwargs):
        # painter.drawRoundedRect(-10, -10, 20, 20, 5, 5)
        # painter.setBrush()

        painter.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing,True)

        painter.setPen(self.pen_color)

        painter.setBrush(self._brush)


        self.types[self.type_obj](painter)
        self._brush = painter.brush()



    def setBrush(self,brush):
        self._brush.setColor(brush)

    def get_square(self):
        type={'эллипс': round ((((pi*self.w*self.h)/4)/10000),4),
              'прямоугольник':(self.h*self.w)/10000}
        return  type[self.type_obj]

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        self.centralwidget = QWidget(self)
        self.centralwidget.setGeometry(0, 0, 600, 700)
        self.centralwidget.setObjectName(u"centralwidget")
        self.gridLayout = QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName(u"gridLayout")
        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.setObjectName(u"verticalLayout")
        self.graphicsView = MyQGraphicsView(self.centralwidget)
        self.graphicsView.setObjectName(u"graphicsView")
        self.scene = QGraphicsScene(self.graphicsView)
        self.scene.setObjectName('scene')
        self.scene.setSceneRect(0, 0, 500, 500)
        self.graphicsView.setScene(self.scene)
        self.graphicsView.setMouseTracking(True)
        self.verticalLayout.addWidget(self.graphicsView)

        self.horizontalLayout = QHBoxLayout()
        self.horizontalLayout.setObjectName(u"horizontalLayout")
        self.add_item = QPushButton(self.centralwidget)
        self.add_item.setObjectName(u"add_item")

        self.horizontalLayout.addWidget(self.add_item)

        self.save_items = QPushButton(self.centralwidget)
        self.save_items.setObjectName(u"save_items")

        self.horizontalLayout.addWidget(self.save_items)

        self.load_items = QPushButton(self.centralwidget)
        self.load_items.setObjectName(u"load_items")

        self.horizontalLayout.addWidget(self.load_items)

        self.verticalLayout.addLayout(self.horizontalLayout)

        self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)

        self.setGeometry(0, 0, 600, 700)

        self.add_item.pressed.connect(self.add_equipment)
        self.save_items.pressed.connect(self.save_items_)
        self.load_items.pressed.connect(self.load_items_)

        self.retranslateUi()


    # setupUi


    def retranslateUi(self):

        self.add_item.setText(QCoreApplication.translate("MainWindow", u"add item", None))
        self.save_items.setText(QCoreApplication.translate("MainWindow", u"save items", None))
        self.load_items.setText(QCoreApplication.translate("MainWindow", u"load items", None))
    # retranslateUi


    def add_equipment(self):
        self.eq_obj = Drow_equipent(30, 40, 20,
                                    20, 'block', Qt.blue,'прямоугольник')

        self.scene.addItem(self.eq_obj)
        self.last_selected_item = self.eq_obj


    def load_items_(self):
        with open(os.path.join(os.getcwd(), 'config.ini'), 'rb') as f:
            settings = load(f)
        print(settings)

        items_keys = [key for key in settings.keys() if key != 'grid']
        print(items_keys)
        for item in items_keys:
            self.eq_obj = Drow_equipent(settings[item]['x'], settings[item]['y'],
                                        settings[item]['w'], settings[item]['h'],
                                        settings[item]['name'],
                                        QBrush(QColor().fromRgb(*settings[item]['brush_color'])),
                                        settings[item]['type_obj'])
            
            self.scene.addItem(self.eq_obj)


    def save_items_(self):
        settings = {}
        number_item = 0
        for item in self.scene.items():
            settings[f'item_№:{number_item}'] = item.get_file_settings()
            number_item += 1
        with open(os.path.join(os.getcwd(), 'config.ini'), 'wb') as f:
            dump(settings, f)


if __name__ == '__main__':

        app = QApplication(sys.argv)
        translator = QTranslator()
        if len(sys.argv) > 1:
            locale = sys.argv[1]
        else:
            locale = QLocale.system().name()
        translator.load('qt_%s' % locale,
                        QLibraryInfo.location(QLibraryInfo.TranslationsPath))
        app.installTranslator(translator)

        window = MainWindow()
        window.show()
        app.exec_()

推荐答案

一个非常要始终考虑的重要事情是 QGraphicsItem 的位置不必匹配其内容.

A very important thing to always consider is that the position of a QGraphicsItem doesn't have to match its contents.

考虑两个矩形项目:

rect1 = self.scene.addRect(5, 10, 20, 10)
rect2 = self.scene.addRect(0, 0, 20, 10)
rect2.setPos(5, 10)

虽然它们都显示在同一位置,但它们相同.这些参数实际上将成为这些项目的 boundingRect,但是虽然第一个的 pos 仍然在 (0, 0),但第二个已经被移动了,并且这是因为图形项总是的起始位置在坐标 (0, 0) 处,而边界矩形总是在项坐标中.

While they are both shown at the same position, they are not the same. Those parameters are actually what will become the boundingRect of those items, but while the pos of the first is still at (0, 0), the second has been moved, and that's because a graphics item always has its starting position at coordinates (0, 0), and the bounding rect is always in item coordinates.

创建新项目时,通常将它们的边界矩形设置为 (0, 0) 是一种很好的做法,除非需要特殊要求(例如,始终显示在其位置的居中的项目).

When creating new items it's usually good practice to set their bounding rect positioned at (0, 0), unless special requirements are needed (for instance, an item that is always shown centered on its position).

在展示如何更正您的代码之前,需要先解决一些其他问题.

Before showing how to correct your code, there are other issues it's important to clear out.

  • 如果您需要允许移动项目,通常不需要自己实现移动,因为 QGraphicsItem 标志 ItemIsMovable 就足够了;如果您需要对鼠标事件进行特殊控制,只需确保在需要移动时调用默认实现即可;
  • 这也意味着super().mousePressEvent() 必须被调用,因为mouseMoveEvents只有在按下鼠标按钮后才会收到;
  • x() e y() 是所有 QGraphicsItems 的现有函数,虽然你仍然可以通过 self.pos().x()self.pos().y() 覆盖这些属性确实没有任何好处;
  • 鼠标事件不应返回bool
  • if you need to allow moving items, it's usually not necessary to implement moving on your own, as the QGraphicsItem flag ItemIsMovable is enough; if you need special control over mouse events, just ensure that you call the default implementation whenever moving is required;
  • this also means that the super().mousePressEvent() must be called, since mouseMoveEvents are only received after a mouse button press;
  • x() e y() are existing functions of all QGraphicsItems, and while you can still access them through self.pos().x() and self.pos().y() there's really no benefit in overwriting those attributes;
  • mouse events should not return a bool;
class Drow_equipent(QGraphicsItem):
    def __init__(self, x, y, w, h, name, brush=Qt.blue, type='эллипс'):
        super().__init__()
        self.setPos(x, y)
        self.penWidth = 1
        self.name = name
        self.h, self.w = h, w
        self.setAcceptHoverEvents(True)
        self.signal = Communicate()
        self.setFlags(
            QGraphicsItem.ItemSendsGeometryChanges|
            QGraphicsItem.ItemIsSelectable|
            QGraphicsItem.ItemIsMovable)
        # ...
        self.types = {
            'эллипс': lambda x: x.drawEllipse(0, 0, self.w, self.h),
            'прямоугольник':lambda x: x.drawRect(0, 0, self.w, self.h)
        }

    # ...
    def mouseMoveEvent(self, event):
        if self.rotation_:
            self.my_rotation(self.rotation() + 1)
        else:
            super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if self.rotation_:
            self.rotation_ = False
        super().mouseReleaseEvent(event)

    def mousePressEvent(self, event):
        print(event.pos())
        super().mousePressEvent(event)

    def boundingRect(self):
        return QRectF(0, 0, self.w, self.h)

最后,将小部件创建为 QMainWindow 的子级是不够的,并且不受支持.您必须将其设置为中央小部件:

Finally, creating a widget as a child of a QMainWindow is not enough and is unsupported. You must set it as central widget:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.centralwidget = QWidget(self)
        self.setCentralWidget(self.centralwidget)

另请注意,虽然对于运行来说不是必需的,但始终建议使用良好的代码结构;逗号后应该总是有一个空格,等号周围应该有空格,否则阅读会更不舒服,因为能够立即区分对象非常重要:看到 self.x,self.y=x,y(这是)与self.x, self.y = x, y相同代码>(这是好的).阅读官方 Python 代码风格指南的更多信息.

Also note that, while not essential for running, good code stilying is always suggested; there should always be a space after a comma and spaces around equal signs, otherwise reading can be much more uncomfortable, as being able to instantly distinguish objects is very important: seeing self.x,self.y=x,y (which is bad) is not the same as self.x, self.y = x, y (which is good). Read more on the official Style Guide for Python Code.

这篇关于如何将 qraphicsitems 的位置保存和加载到 qraphicscene 或(正确地卓尔项目)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆