I am working on a project that requires me to create a custom QGraphicsItemGroup that snaps to a grid when a widget is checked. The snap to grid logic works (partially) as when I move the item, it moves 10 pixels per move (snapping). However, it moves from 0, 0 in the scene, meaning if the group is created at 100, 100, it will move as if it where created at 0, 0. I don't know if I really explained the problem well, so feel free to play with it yourself.
class CustomGraphicsItemGroup(QGraphicsItemGroup):
def __init__(self, widget):
super().__init__()
self.widgets = widget
def paint(self, painter, option, widget=None):
# Call the parent class paint method first
super().paint(painter, option, widget)
# If the item is selected, draw a custom selection highlight
if option.state & QStyle.State_Selected:
pen = painter.pen()
pen.setColor(QColor('#e00202'))
pen.setWidth(2)
pen.setCapStyle(Qt.RoundCap)
painter.setPen(pen)
painter.drawRect(self.boundingRect())
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
if self.widgets.isChecked():
block_size = 10
# Calculate the position relative to the scene's coordinate system
scene_pos = event.scenePos()
x = int(scene_pos.x() / block_size) * block_size
y = int(scene_pos.y() / block_size) * block_size
# Set the position relative to the scene's coordinate system
self.setPos(x, y)
else:
# Call the superclass's mouseMoveEvent to move the item as normal
super().mouseMoveEvent(event)
I tried using different numbers, division, multiplication, and nothing worked. Any help or code snippits are appreciated.
So first off all, when inheriting the QGraphicsItemGroup
, one should consider the concepts of it. The group does not want to have a child in the constructor. Instead, items are added to the group. The group itself is then a proxy for all added items.
There are two ways to create a group in the scene. The option with which custom groups can be created in the scene is as follows.
group = CustomGraphicsItemGroup()
group.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
group.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
scene.addItem(group)
group.addToGroup(red_item)
group.addToGroup(blue_item)
You should always use the tools that are available to Qt and not reinvent the wheel. It is important to set the right flags so that an item (or a group) is selectable and movable. Then use the self.isSelected
of the item itself.
To your question, an offset for the mouse must be applied somehow so that no sudden jumps occur. This can be stored in the mousePressEvent
.
import sys
from PySide6.QtCore import QRect, QPoint
from PySide6.QtGui import QColor, Qt
from PySide6.QtWidgets import (
QGraphicsItemGroup,
QStyle,
QGraphicsItem,
QGraphicsScene,
QGraphicsView,
QApplication,
QGraphicsRectItem,
)
class CustomGraphicsItemGroup(QGraphicsItemGroup):
def __init__(self, parent=None):
super().__init__(parent)
self.mouse_offset = QPoint(0, 0)
self.block_size = 10
def paint(self, painter, option, widget=None):
# Call the parent class paint method first
super().paint(painter, option, widget)
# If the item is selected, draw a custom selection highlight
if option.state & QStyle.State_Selected:
pen = painter.pen()
pen.setColor(QColor("#e00202"))
pen.setWidth(2)
pen.setCapStyle(Qt.RoundCap)
painter.setPen(pen)
painter.drawRect(self.boundingRect())
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.mouse_offset = event.pos()
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.isSelected():
# Calculate the position relative to the scene's coordinate system
scene_pos = event.scenePos()
x = (
int(scene_pos.x() / self.block_size) * self.block_size
- self.mouse_offset.x()
)
y = (
int(scene_pos.y() / self.block_size) * self.block_size
- self.mouse_offset.y()
)
# Set the position relative to the scene's coordinate system
self.setPos(x, y)
else:
# Call the superclass's mouseMoveEvent to move the item as normal
super().mouseMoveEvent(event)
if __name__ == "__main__":
app = QApplication(sys.argv)
scene = QGraphicsScene()
view = QGraphicsView(scene)
scene.setSceneRect(QRect(0, 0, 500, 500))
view.setFixedWidth(500)
view.setFixedHeight(500)
view.show()
group = CustomGraphicsItemGroup()
group.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
group.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
scene.addItem(group)
red_item = QGraphicsRectItem()
red_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
red_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
red_item.setBrush(QColor("red"))
red_item.setRect(0, 0, 80, 80)
red_item.setPos(150, 150)
group.addToGroup(red_item)
blue_item = QGraphicsRectItem()
blue_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, True)
blue_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True)
blue_item.setBrush(QColor("blue"))
blue_item.setRect(0, 0, 100, 100)
blue_item.setPos(100, 100)
group.addToGroup(blue_item)
scene.addItem(blue_item)
scene.addItem(red_item)
sys.exit(app.exec())