如何将 qraphicsitems 的位置保存和加载到 qraphicscene 或(正确地卓尔项目) [英] How to save and load position of qraphicsitems to qraphicscene or (correctly drow item)
问题描述
原则上,这不是一项艰巨的任务,但一切都没有看起来那么容易.理解坐标非常困难.因为它们一共有三对:模型、项目、视图.我保留 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()
ey()
是所有 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()
ey()
are existing functions of all QGraphicsItems, and while you can still access them throughself.pos().x()
andself.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屋!